3232scripts_dir = os .path .dirname (os .path .abspath (__file__ ))
3333api_dir = os .path .join (os .path .dirname (scripts_dir ), "data" , "api" )
3434
35+ # Finds a Hugo shortcode in a string.
36+ #
37+ # A shortcode is defined as (newlines and whitespaces for presentation purpose):
38+ #
39+ # {{%
40+ # <zero or more whitespaces>
41+ # <name of shortcode>
42+ # (optional <one or more whitespaces><list of parameters>)
43+ # <zero or more whitespaces>
44+ # %}}
45+ #
46+ # With:
47+ #
48+ # * <name of shortcode>: any word character and `-` and `/`. `re.ASCII` is used to only match
49+ # ASCII characters in the name.
50+ # * <list of parameters>: any character except `}`, must not start or end with a
51+ # whitespace.
52+ shortcode_regex = re .compile (r"""\{\{\% # {{%
53+ \s* # zero or more whitespaces
54+ (?P<name>[\w/-]+) # name of shortcode
55+ (?:\s+(?P<params>[^\s\}][^\}]+[^\s\}]))? # optional list of parameters
56+ \s* # zero or more whitespaces
57+ \%\}\} # %}}""" , re .ASCII | re .VERBOSE )
58+
59+ # Parses the parameters of a Hugo shortcode.
60+ #
61+ # For simplicity, this currently only supports the `key="value"` format.
62+ shortcode_params_regex = re .compile (r"(?P<key>\w+)=\"(?P<value>[^\"]+)\"" , re .ASCII )
63+
3564def prefix_absolute_path_references (text , base_url ):
3665 """Adds base_url to absolute-path references.
3766
@@ -44,17 +73,90 @@ def prefix_absolute_path_references(text, base_url):
4473 """
4574 return text .replace ("](/" , "]({}/" .format (base_url ))
4675
47- def edit_links (node , base_url ):
48- """Finds description nodes and makes any links in them absolute."""
76+ def replace_match (match , replacement ):
77+ """Replaces the regex match by the replacement in the text."""
78+ return match .string [:match .start ()] + replacement + match .string [match .end ():]
79+
80+ def replace_shortcode (shortcode ):
81+ """Replaces the shortcode by a Markdown fallback in the text.
82+
83+ The supported shortcodes are:
84+
85+ * boxes/note, boxes/rationale, boxes/warning
86+ * added-in, changed-in
87+
88+ All closing tags (`{{ /shortcode }}`) are replaced with the empty string.
89+ """
90+
91+ if shortcode ['name' ].startswith ("/" ):
92+ # This is the end of the shortcode, just remove it.
93+ return replace_match (shortcode , "" )
94+
95+ # Parse the parameters of the shortcode
96+ params = {}
97+ if shortcode ['params' ]:
98+ for param in shortcode_params_regex .finditer (shortcode ['params' ]):
99+ if param ['key' ]:
100+ params [param ['key' ]] = param ['value' ]
101+
102+ match shortcode ['name' ]:
103+ case "boxes/note" :
104+ return replace_match (shortcode , "**NOTE:** " )
105+ case "boxes/rationale" :
106+ return replace_match (shortcode , "**RATIONALE:** " )
107+ case "boxes/warning" :
108+ return replace_match (shortcode , "**WARNING:** " )
109+ case "added-in" :
110+ version = params ['v' ]
111+ if not version :
112+ raise ValueError ("Missing parameter `v` for `added-in` shortcode" )
113+
114+ return replace_match (shortcode , f"**[Added in `v{ version } `]** " )
115+ case "changed-in" :
116+ version = params ['v' ]
117+ if not version :
118+ raise ValueError ("Missing parameter `v` for `changed-in` shortcode" )
119+
120+ return replace_match (shortcode , f"**[Changed in `v{ version } `]** " )
121+ case _:
122+ raise ValueError ("Unknown shortcode" , shortcode ['name' ])
123+
124+
125+ def find_and_replace_shortcodes (text ):
126+ """Finds Hugo shortcodes and replaces them by a Markdown fallback.
127+
128+ The supported shortcodes are:
129+
130+ * boxes/note, boxes/rationale, boxes/warning
131+ * added-in, changed-in
132+ """
133+ # We use a `while` loop with `search` instead of a `for` loop with
134+ # `finditer`, because as soon as we start replacing text, the
135+ # indices of the match are invalid.
136+ while shortcode := shortcode_regex .search (text ):
137+ text = replace_shortcode (shortcode )
138+
139+ return text
140+
141+ def edit_descriptions (node , base_url ):
142+ """Finds description nodes and apply fixes to them.
143+
144+ The fixes that are applied are:
145+
146+ * Make links absolute
147+ * Replace Hugo shortcodes
148+ """
49149 if isinstance (node , dict ):
50150 for key in node :
51151 if isinstance (node [key ], str ):
52152 node [key ] = prefix_absolute_path_references (node [key ], base_url )
153+ node [key ] = find_and_replace_shortcodes (node [key ])
53154 else :
54- edit_links (node [key ], base_url )
155+ edit_descriptions (node [key ], base_url )
55156 elif isinstance (node , list ):
56157 for item in node :
57- edit_links (item , base_url )
158+ edit_descriptions (item , base_url )
159+
58160
59161parser = argparse .ArgumentParser (
60162 "dump-openapi.py - assemble the OpenAPI specs into a single JSON file"
@@ -164,7 +266,7 @@ def edit_links(node, base_url):
164266if untagged != 0 :
165267 print ("{} untagged operations, you may want to look into fixing that." .format (untagged ))
166268
167- edit_links (output , base_url )
269+ edit_descriptions (output , base_url )
168270
169271print ("Generating %s" % output_file )
170272
0 commit comments