refactor: move docs rewrite into mkdocs hooks

This will ensure `mkdocs build` does not rewrite content, making contribution easier.
This commit is contained in:
Jose Diaz-Gonzalez
2024-01-30 06:53:14 -05:00
parent b333334e72
commit 0c83356542
2 changed files with 283 additions and 309 deletions

View File

@@ -3,7 +3,6 @@ import os
import pathlib import pathlib
import shutil import shutil
import sys import sys
from collections.abc import Callable
import yaml import yaml
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
@@ -128,308 +127,6 @@ def generate_nav(src: str, dest: str) -> None:
yaml.dump(data, handler) yaml.dump(data, handler)
def modify_content_noop(lines: list[str], _) -> tuple[list[str], bool]:
"""
Simply returns the lines as is
"""
modified = False
updated_lines = []
for line in lines:
updated_lines.append(line)
return updated_lines, modified
def modify_content_links(lines: list[str], filename: str) -> tuple[list[str], bool]:
"""
Modifies links to be relative instead of absolute
"""
filename = filename.replace("/usr/src/source/docs/", "")
parts = filename.split("/")
parts.pop()
replacement = "](" + "/".join([".." for _ in parts]) + "/"
modified = False
updated_lines = []
for line in lines:
if "](/docs/" in line:
line = line.replace("](/docs/", replacement)
modified = True
updated_lines.append(line)
return updated_lines, modified
def modify_content_stripspace(lines: list[str], _) -> tuple[list[str], bool]:
"""
Removes trailing whitespace from each line
"""
modified = False
updated_lines = []
for line in lines:
line = line.rstrip()
updated_lines.append(line)
return updated_lines, modified
def modify_content_inject_newlines(lines: list[str], _) -> tuple[list[str], bool]:
"""
Ensures every line has a trailing newline character
"""
modified = False
updated_lines = []
for line in lines:
updated_lines.append(line.rstrip() + "\n")
return updated_lines, modified
def is_github_new(line: str, next_line: str | None) -> bool:
"""
Checks if a given line is a github "new as of" admonition
"""
if not next_line:
return False
return line.startswith("> [!IMPORTANT]") and "new as of" in next_line.lower()
def is_github_note(line: str) -> bool:
"""
Checks if a given line is a github "note" admonition
"""
return line.startswith("> [!NOTE]")
def is_github_warning(line: str) -> bool:
"""
Checks if a given line is a github "warning" admonition
"""
return line.startswith("> [!WARNING]")
def is_info(line: str) -> bool:
"""
Checks if a given line is an "info" admonition
"""
return line.startswith("> ")
def is_new(line: str) -> bool:
"""
Checks if a given line is a "new as of" admonition
"""
return line.startswith("> ") and "new as of" in line.lower()
def is_note(line: str) -> bool:
"""
Checks if a given line is a "note" admonition
"""
return line.startswith("> Note:")
def is_warning(line: str) -> bool:
"""
Checks if a given line is a "warning" admonition
"""
return line.startswith("> Warning:")
def modify_content_admonition(lines: list[str], _) -> tuple[list[str], bool]:
"""
Applies adminition info to each line in the output
"""
modified = False
updated_lines = []
admonition_lines = []
is_admonition = False
replace_new_in_next_line = False
for index, line in enumerate(lines):
next_line: str | None = None
if index + 1 < len(lines):
next_line = lines[index + 1]
if replace_new_in_next_line:
line = line.replace("New as of", "Introduced in")
line = line.replace("new as of", "introduced in")
replace_new_in_next_line = False
if is_github_new(line, next_line):
line = line.replace("> [!IMPORTANT]", '!!! tip "New"')
is_admonition = True
admonition_lines.append(line)
admonition_lines.append("")
replace_new_in_next_line = True
elif is_github_note(line):
line = line.replace("> [!NOTE]", '!!! note "Note"')
is_admonition = True
admonition_lines.append(line)
admonition_lines.append("")
elif is_github_warning(line):
line = line.replace("> [!WARNING]", '!!! warning "Warning"')
is_admonition = True
admonition_lines.append(line)
admonition_lines.append("")
elif is_new(line):
print("is_new")
line = line.replace("> ", '!!! tip "New"\n\n ')
line = line.replace("New as of", "Introduced in")
line = line.replace("new as of", "introduced in")
is_admonition = True
admonition_lines.append(line)
elif is_note(line):
print("is_note")
line = line.replace("> Note: ", '!!! note "Note"\n\n ')
is_admonition = True
admonition_lines.append(line)
elif is_warning(line):
print("is_warning")
line = line.replace("> Warning: ", '!!! warning "Warning"\n\n ')
is_admonition = True
admonition_lines.append(line)
elif is_info(line):
if not is_admonition:
line = line.replace("> ", '!!! info "Info"\n\n ')
elif line in [">", "> "]:
line = ""
else:
line = " " + line.removeprefix("> ")
is_admonition = True
admonition_lines.append(line)
elif is_admonition and line in [">", "> "]:
line = ""
admonition_lines.append(line)
elif is_admonition and line.startswith("> "):
line = " " + line.removeprefix("> ")
admonition_lines.append(line)
elif line == "":
is_admonition = False
if len(admonition_lines) > 0:
modified = True
updated_lines.extend(admonition_lines)
admonition_lines = []
updated_lines.append("")
else:
updated_lines.append(line)
return updated_lines, modified
def is_shell_codeblock_start(line: str) -> bool:
"""
Checks to see if a line starts a codeblock
"""
return line == "```shell"
def modify_content_terminal_example(lines: list[str], _) -> tuple[list[str], bool]:
"""
Modifies content so that terminal output is shown appropriately
"""
modified = False
updated_lines = []
command_block = []
example_block = []
in_command_block = False
in_example_block = False
previous_block = ""
next_line_must_be = None
for line in lines:
if is_shell_codeblock_start(line):
command_block.append(line)
modified = True
in_command_block = True
continue
elif in_command_block:
command_block.append(line)
if line == "```":
in_command_block = False
previous_block = "command_block"
next_line_must_be = ""
continue
elif line == "```":
if previous_block == "":
updated_lines.append(line)
continue
if previous_block == "command_block":
example_block.append(line)
if in_example_block:
previous_block = ""
in_example_block = False
updated_lines.append('=== "Shell"')
updated_lines.append("")
for command_line in command_block:
updated_lines.append(f" {command_line}")
command_block = []
updated_lines.append("")
updated_lines.append('=== "Output"')
updated_lines.append("")
for example_line in example_block:
updated_lines.append(f" {example_line}")
example_block = []
else:
in_example_block = True
continue
elif previous_block == "command_block":
if next_line_must_be is None:
if in_example_block:
example_block.append(line)
else:
updated_lines.extend(command_block)
updated_lines.append("")
updated_lines.append(line)
command_block = []
previous_block = ""
continue
if next_line_must_be == "":
if line == "":
next_line_must_be = None
continue
updated_lines.append(line)
if len(command_block) > 0:
updated_lines.extend(command_block)
return updated_lines, modified
def update_markdown(src):
"""
Updates all markdown files to be mkdocs compatible
"""
markdown_files = []
allowed_extensions = [".md"]
for subdir, _, files in os.walk(src):
for file in files:
ext = os.path.splitext(file)[-1].lower()
file_path = os.path.join(subdir, file)
if ext in allowed_extensions:
markdown_files.append(file_path)
modifiers: list[Callable[[list[str], str], tuple[list[str], bool]]] = [
modify_content_noop,
modify_content_links,
modify_content_stripspace,
modify_content_admonition,
modify_content_terminal_example,
modify_content_inject_newlines,
]
for file in markdown_files:
is_modified = False
lines = []
with open(file, "r", encoding="utf-8") as handler:
lines = handler.readlines()
for modifier in modifiers:
lines, modified = modifier(lines, file)
if modified:
is_modified = True
if is_modified:
with open(file, "w", encoding="utf-8") as handler:
handler.writelines(lines)
def main(): def main():
""" """
Main command that performs doc manipulation Main command that performs doc manipulation
@@ -438,11 +135,6 @@ def main():
print(" Generating navigation") print(" Generating navigation")
generate_nav("/usr/src/source/mkdocs.yml", "/usr/src/app/mkdocs.yml") generate_nav("/usr/src/source/mkdocs.yml", "/usr/src/app/mkdocs.yml")
print("----> Copying docs folder")
update_markdown(
"/usr/src/source/docs",
)
if os.path.exists("/usr/src/app/docs"): if os.path.exists("/usr/src/app/docs"):
print(" Removing old docs folder") print(" Removing old docs folder")
shutil.rmtree("/usr/src/app/docs") shutil.rmtree("/usr/src/app/docs")

284
docs/_build/hooks.py vendored
View File

@@ -3,13 +3,295 @@
import posixpath import posixpath
import re import re
from collections.abc import Callable
from re import Match from re import Match
from mkdocs.config.defaults import MkDocsConfig from mkdocs.config.defaults import MkDocsConfig
from mkdocs.structure.files import File, Files from mkdocs.structure.files import File, Files
from mkdocs.structure.pages import Page from mkdocs.structure.pages import Page
def on_page_markdown(markdown: str, *, page: Page, config: MkDocsConfig, files: Files): def on_page_markdown(markdown: str, *, page: Page, config: MkDocsConfig, files: Files) -> str | None:
resp = re_on_page_markdown(markdown, page=page, config=config, files=files)
if not resp:
return resp
modifiers: list[Callable[[list[str], str], tuple[list[str], bool]]] = [
modify_content_noop,
modify_content_links,
modify_content_stripspace,
modify_content_admonition,
modify_content_terminal_example,
]
is_modified = False
lines = resp.split("\n")
for modifier in modifiers:
lines, modified = modifier(lines, page.file.src_path)
if modified:
is_modified = True
if is_modified:
resp = "\n".join(lines)
return resp
def modify_content_noop(lines: list[str], _) -> tuple[list[str], bool]:
"""
Simply returns the lines as is
"""
modified = False
updated_lines = []
for line in lines:
updated_lines.append(line)
return updated_lines, modified
def modify_content_links(lines: list[str], filename: str) -> tuple[list[str], bool]:
"""
Modifies links to be relative instead of absolute
"""
filename = filename.replace("/usr/src/source/docs/", "")
parts = filename.split("/")
parts.pop()
replacement = "](" + "/".join([".." for _ in parts]) + "/"
modified = False
updated_lines = []
for line in lines:
if "](/docs/" in line:
line = line.replace("](/docs/", replacement)
modified = True
updated_lines.append(line)
return updated_lines, modified
def modify_content_stripspace(lines: list[str], _) -> tuple[list[str], bool]:
"""
Removes trailing whitespace from each line
"""
modified = False
updated_lines = []
for line in lines:
line = line.rstrip()
updated_lines.append(line)
return updated_lines, modified
def is_github_new(line: str, next_line: str | None) -> bool:
"""
Checks if a given line is a github "new as of" admonition
"""
if not next_line:
return False
return line.startswith("> [!IMPORTANT]") and "new as of" in next_line.lower()
def is_github_note(line: str) -> bool:
"""
Checks if a given line is a github "note" admonition
"""
return line.startswith("> [!NOTE]")
def is_github_warning(line: str) -> bool:
"""
Checks if a given line is a github "warning" admonition
"""
return line.startswith("> [!WARNING]")
def is_info(line: str) -> bool:
"""
Checks if a given line is an "info" admonition
"""
return line.startswith("> ")
def is_new(line: str) -> bool:
"""
Checks if a given line is a "new as of" admonition
"""
return line.startswith("> ") and "new as of" in line.lower()
def is_note(line: str) -> bool:
"""
Checks if a given line is a "note" admonition
"""
return line.startswith("> Note:")
def is_warning(line: str) -> bool:
"""
Checks if a given line is a "warning" admonition
"""
return line.startswith("> Warning:")
def modify_content_admonition(lines: list[str], filename: str) -> tuple[list[str], bool]:
"""
Applies adminition info to each line in the output
"""
modified = False
updated_lines = []
admonition_lines = []
is_admonition = False
replace_new_in_next_line = False
for index, line in enumerate(lines):
next_line: str | None = None
if index + 1 < len(lines):
next_line = lines[index + 1]
if replace_new_in_next_line:
line = line.replace("New as of", "Introduced in")
line = line.replace("new as of", "introduced in")
replace_new_in_next_line = False
if is_github_new(line, next_line):
line = line.replace("> [!IMPORTANT]", '!!! tip "New"')
is_admonition = True
admonition_lines.append(line)
admonition_lines.append("")
replace_new_in_next_line = True
elif is_github_note(line):
line = line.replace("> [!NOTE]", '!!! note "Note"')
is_admonition = True
admonition_lines.append(line)
admonition_lines.append("")
elif is_github_warning(line):
line = line.replace("> [!WARNING]", '!!! warning "Warning"')
is_admonition = True
admonition_lines.append(line)
admonition_lines.append("")
elif is_new(line):
print("is_new")
line = line.replace("> ", '!!! tip "New"\n\n ')
line = line.replace("New as of", "Introduced in")
line = line.replace("new as of", "introduced in")
is_admonition = True
admonition_lines.append(line)
elif is_note(line):
print("is_note")
line = line.replace("> Note: ", '!!! note "Note"\n\n ')
is_admonition = True
admonition_lines.append(line)
elif is_warning(line):
print("is_warning")
line = line.replace("> Warning: ", '!!! warning "Warning"\n\n ')
is_admonition = True
admonition_lines.append(line)
elif is_info(line):
if not is_admonition:
line = line.replace("> ", '!!! info "Info"\n\n ')
elif line in [">", "> "]:
line = ""
else:
line = " " + line.removeprefix("> ")
is_admonition = True
admonition_lines.append(line)
elif is_admonition and line in [">", "> "]:
line = ""
admonition_lines.append(line)
elif is_admonition and line.startswith("> "):
line = " " + line.removeprefix("> ")
admonition_lines.append(line)
elif line == "":
is_admonition = False
if len(admonition_lines) > 0:
modified = True
updated_lines.extend(admonition_lines)
admonition_lines = []
updated_lines.append("")
else:
updated_lines.append(line)
return updated_lines, modified
def is_shell_codeblock_start(line: str) -> bool:
"""
Checks to see if a line starts a codeblock
"""
return line == "```shell"
def modify_content_terminal_example(lines: list[str], _) -> tuple[list[str], bool]:
"""
Modifies content so that terminal output is shown appropriately
"""
modified = False
updated_lines = []
command_block = []
example_block = []
in_command_block = False
in_example_block = False
previous_block = ""
next_line_must_be = None
for line in lines:
if is_shell_codeblock_start(line):
command_block.append(line)
modified = True
in_command_block = True
continue
elif in_command_block:
command_block.append(line)
if line == "```":
in_command_block = False
previous_block = "command_block"
next_line_must_be = ""
continue
elif line == "```":
if previous_block == "":
updated_lines.append(line)
continue
if previous_block == "command_block":
example_block.append(line)
if in_example_block:
previous_block = ""
in_example_block = False
updated_lines.append('=== "Shell"')
updated_lines.append("")
for command_line in command_block:
updated_lines.append(f" {command_line}")
command_block = []
updated_lines.append("")
updated_lines.append('=== "Output"')
updated_lines.append("")
for example_line in example_block:
updated_lines.append(f" {example_line}")
example_block = []
else:
in_example_block = True
continue
elif previous_block == "command_block":
if next_line_must_be is None:
if in_example_block:
example_block.append(line)
else:
updated_lines.extend(command_block)
updated_lines.append("")
updated_lines.append(line)
command_block = []
previous_block = ""
continue
if next_line_must_be == "":
if line == "":
next_line_must_be = None
continue
updated_lines.append(line)
if len(command_block) > 0:
updated_lines.extend(command_block)
return updated_lines, modified
def re_on_page_markdown(markdown: str, *, page: Page, config: MkDocsConfig, files: Files) -> str | None:
""" """
This hook is called after the page's markdown is loaded from file and can be This hook is called after the page's markdown is loaded from file and can be
""" """