Replace Hugo shortcodes in OpenAPI output (#2088)

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
pull/2144/head
Kévin Commaille 7 months ago committed by GitHub
parent a8c326962a
commit fe46e0c363
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -2,6 +2,7 @@ name: "Spec"
env: env:
HUGO_VERSION: 0.139.0 HUGO_VERSION: 0.139.0
PYTHON_VERSION: 3.13
on: on:
push: push:
@ -40,7 +41,7 @@ jobs:
- name: " Setup Python" - name: " Setup Python"
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: '3.9' python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip' cache: 'pip'
cache-dependency-path: scripts/requirements.txt cache-dependency-path: scripts/requirements.txt
- name: " Install dependencies" - name: " Install dependencies"
@ -59,7 +60,7 @@ jobs:
- name: " Setup Python" - name: " Setup Python"
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: '3.9' python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip' cache: 'pip'
cache-dependency-path: scripts/requirements.txt cache-dependency-path: scripts/requirements.txt
- name: " Install dependencies" - name: " Install dependencies"
@ -78,7 +79,7 @@ jobs:
- name: " Setup Python" - name: " Setup Python"
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: '3.9' python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip' cache: 'pip'
cache-dependency-path: scripts/requirements.txt cache-dependency-path: scripts/requirements.txt
- name: " Install dependencies" - name: " Install dependencies"
@ -120,7 +121,7 @@ jobs:
- name: " Setup Python" - name: " Setup Python"
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: '3.9' python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip' cache: 'pip'
cache-dependency-path: scripts/requirements.txt cache-dependency-path: scripts/requirements.txt
- name: " Install dependencies" - name: " Install dependencies"
@ -172,7 +173,7 @@ jobs:
- name: " Setup Python" - name: " Setup Python"
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: '3.9' python-version: ${{ env.PYTHON_VERSION }}
- name: " Install towncrier" - name: " Install towncrier"
run: "pip install 'towncrier'" run: "pip install 'towncrier'"
- name: "Generate changelog" - name: "Generate changelog"

@ -0,0 +1 @@
Replace Hugo shortcodes in OpenAPI output.

@ -32,6 +32,35 @@ import yaml
scripts_dir = os.path.dirname(os.path.abspath(__file__)) scripts_dir = os.path.dirname(os.path.abspath(__file__))
api_dir = os.path.join(os.path.dirname(scripts_dir), "data", "api") api_dir = os.path.join(os.path.dirname(scripts_dir), "data", "api")
# Finds a Hugo shortcode in a string.
#
# A shortcode is defined as (newlines and whitespaces for presentation purpose):
#
# {{%
# <zero or more whitespaces>
# <name of shortcode>
# (optional <one or more whitespaces><list of parameters>)
# <zero or more whitespaces>
# %}}
#
# With:
#
# * <name of shortcode>: any word character and `-` and `/`. `re.ASCII` is used to only match
# ASCII characters in the name.
# * <list of parameters>: any character except `}`, must not start or end with a
# whitespace.
shortcode_regex = re.compile(r"""\{\{\% # {{%
\s* # zero or more whitespaces
(?P<name>[\w/-]+) # name of shortcode
(?:\s+(?P<params>[^\s\}][^\}]+[^\s\}]))? # optional list of parameters
\s* # zero or more whitespaces
\%\}\} # %}}""", re.ASCII | re.VERBOSE)
# Parses the parameters of a Hugo shortcode.
#
# For simplicity, this currently only supports the `key="value"` format.
shortcode_params_regex = re.compile(r"(?P<key>\w+)=\"(?P<value>[^\"]+)\"", re.ASCII)
def prefix_absolute_path_references(text, base_url): def prefix_absolute_path_references(text, base_url):
"""Adds base_url to absolute-path references. """Adds base_url to absolute-path references.
@ -44,17 +73,90 @@ def prefix_absolute_path_references(text, base_url):
""" """
return text.replace("](/", "]({}/".format(base_url)) return text.replace("](/", "]({}/".format(base_url))
def edit_links(node, base_url): def replace_match(match, replacement):
"""Finds description nodes and makes any links in them absolute.""" """Replaces the regex match by the replacement in the text."""
return match.string[:match.start()] + replacement + match.string[match.end():]
def replace_shortcode(shortcode):
"""Replaces the shortcode by a Markdown fallback in the text.
The supported shortcodes are:
* boxes/note, boxes/rationale, boxes/warning
* added-in, changed-in
All closing tags (`{{ /shortcode }}`) are replaced with the empty string.
"""
if shortcode['name'].startswith("/"):
# This is the end of the shortcode, just remove it.
return replace_match(shortcode, "")
# Parse the parameters of the shortcode
params = {}
if shortcode['params']:
for param in shortcode_params_regex.finditer(shortcode['params']):
if param['key']:
params[param['key']] = param['value']
match shortcode['name']:
case "boxes/note":
return replace_match(shortcode, "**NOTE:** ")
case "boxes/rationale":
return replace_match(shortcode, "**RATIONALE:** ")
case "boxes/warning":
return replace_match(shortcode, "**WARNING:** ")
case "added-in":
version = params['v']
if not version:
raise ValueError("Missing parameter `v` for `added-in` shortcode")
return replace_match(shortcode, f"**[Added in `v{version}`]** ")
case "changed-in":
version = params['v']
if not version:
raise ValueError("Missing parameter `v` for `changed-in` shortcode")
return replace_match(shortcode, f"**[Changed in `v{version}`]** ")
case _:
raise ValueError("Unknown shortcode", shortcode['name'])
def find_and_replace_shortcodes(text):
"""Finds Hugo shortcodes and replaces them by a Markdown fallback.
The supported shortcodes are:
* boxes/note, boxes/rationale, boxes/warning
* added-in, changed-in
"""
# We use a `while` loop with `search` instead of a `for` loop with
# `finditer`, because as soon as we start replacing text, the
# indices of the match are invalid.
while shortcode := shortcode_regex.search(text):
text = replace_shortcode(shortcode)
return text
def edit_descriptions(node, base_url):
"""Finds description nodes and apply fixes to them.
The fixes that are applied are:
* Make links absolute
* Replace Hugo shortcodes
"""
if isinstance(node, dict): if isinstance(node, dict):
for key in node: for key in node:
if isinstance(node[key], str): if isinstance(node[key], str):
node[key] = prefix_absolute_path_references(node[key], base_url) node[key] = prefix_absolute_path_references(node[key], base_url)
node[key] = find_and_replace_shortcodes(node[key])
else: else:
edit_links(node[key], base_url) edit_descriptions(node[key], base_url)
elif isinstance(node, list): elif isinstance(node, list):
for item in node: for item in node:
edit_links(item, base_url) edit_descriptions(item, base_url)
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
"dump-openapi.py - assemble the OpenAPI specs into a single JSON file" "dump-openapi.py - assemble the OpenAPI specs into a single JSON file"
@ -164,7 +266,7 @@ for filename in os.listdir(selected_api_dir):
if untagged != 0: if untagged != 0:
print("{} untagged operations, you may want to look into fixing that.".format(untagged)) print("{} untagged operations, you may want to look into fixing that.".format(untagged))
edit_links(output, base_url) edit_descriptions(output, base_url)
print("Generating %s" % output_file) print("Generating %s" % output_file)

Loading…
Cancel
Save