[doc] Created reggen preprocessor
The preprocessor finds ip configs in SUMMARY.md and converts them into a
html document with tables for hardware interfaces and registers.
Signed-off-by: Hugo McNally <hugo.mcnally@gmail.com>
diff --git a/book.toml b/book.toml
index 1a1eaf2..84676f5 100644
--- a/book.toml
+++ b/book.toml
@@ -18,3 +18,8 @@
[preprocessor.readme2index]
command = "./util/mdbook_readme2index.py"
+
+[preprocessor.reggen]
+command = "./util/mdbook_reggen.py"
+# Python RegEx identifying ip block config paths.
+ip-cfg-py-regex = '(ip|ip_autogen)/.+/data/.+\.hjson'
diff --git a/util/mdbook/utils.py b/util/mdbook/utils.py
index cf0c320..3533145 100644
--- a/util/mdbook/utils.py
+++ b/util/mdbook/utils.py
@@ -3,7 +3,50 @@
# SPDX-License-Identifier: Apache-2.0
"""Common utilities used by mdbook pre-processors."""
-from typing import List, Any, Dict, Generator
+import sys
+import re
+from os import path
+from typing import List, Any, Dict, Generator, Set
+from pathlib import Path
+
+LINK_PATTERN_STR = r"\[(.*?)\]\(([^#\?\)]*)(.*?)\)"
+LINK_PATTERN = re.compile(LINK_PATTERN_STR)
+
+
+def change_link_ext(
+ file_list: Set[Path],
+ content: str,
+ new_suffix: str,
+ book_root: Path,
+ page_path: Path,
+) -> str:
+ def suffix_swap(match: re.Match) -> str:
+ """Swaps the extension of the file being linked to if it is a ip block config."""
+ try:
+ # relative_to can fail with a value error, if it isn't a local link
+ book_relative_path = (page_path / match.group(2)).resolve().relative_to(book_root)
+ except ValueError:
+ return match.group(0)
+
+ if book_relative_path in file_list:
+ return "[{}]({}{}{})".format(
+ match.group(1),
+ path.splitext(match.group(2))[0],
+ new_suffix,
+ match.group(3),
+ )
+ else:
+ return match.group(0)
+
+ return LINK_PATTERN.sub(suffix_swap, content)
+
+
+def supports_html_only() -> None:
+ if len(sys.argv) > 2:
+ if (sys.argv[1], sys.argv[2]) == ("supports", "html"):
+ sys.exit(0)
+ else:
+ sys.exit(1)
def chapters(items: List[Dict[str, Any]]) -> Generator[Dict[str, Any], None, None]:
diff --git a/util/mdbook_reggen.py b/util/mdbook_reggen.py
new file mode 100755
index 0000000..cbee3a2
--- /dev/null
+++ b/util/mdbook_reggen.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python3
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+"""mdbook preprocessor that generates interface and register tables for ip blocks.
+
+The preprocessor finds ip configs in SUMMARY.md and converts them into a html document
+with tables for hardware interfaces and registers.
+"""
+
+import json
+import sys
+import re
+import io
+from pathlib import Path
+
+from mdbook import utils as md_utils
+from reggen.ip_block import IpBlock
+import reggen.gen_cfg_html as gen_cfg_html
+import reggen.gen_html as gen_html
+
+REGREF_PATTERN = re.compile(r"\{\{#regref\s+?(.+?)\s*?\}\}")
+
+
+def main() -> None:
+ md_utils.supports_html_only()
+
+ # load both the context and the book from stdin
+ context, book = json.load(sys.stdin)
+ book_root = context["root"]
+
+ try:
+ ip_cfg_str = context["config"]["preprocessor"]["reggen"]["ip-cfg-py-regex"]
+ ip_cfg_pattern = re.compile(ip_cfg_str)
+ except KeyError:
+ sys.exit(
+ "No RegEx pattern given in book.toml to identify ip block configuration files.\n"
+ "Provide regex as preprocessor.reggen.ip-cfg-py-regex .",
+ )
+
+ name2path = {}
+ for chapter in md_utils.chapters(book["sections"]):
+ src_path = chapter["source_path"]
+ if not src_path or not ip_cfg_pattern.search(src_path):
+ continue
+
+ block = IpBlock.from_text(
+ chapter["content"],
+ [],
+ "file at {}/{}".format(context["root"], chapter["source_path"])
+ )
+ buffer = io.StringIO()
+ buffer.write("# Hardware Interfaces and Registers\n")
+ buffer.write("## Interfaces\n")
+ gen_cfg_html.gen_cfg_html(block, buffer)
+ buffer.write("\n## Registers\n")
+ gen_html.gen_html(block, buffer)
+ chapter["content"] = buffer.getvalue()
+
+ name2path[block.name] = src_path
+
+ cfg_files = set(Path(p) for p in name2path.values())
+ for chapter in md_utils.chapters(book["sections"]):
+ if not chapter["source_path"]:
+ continue
+ src_dir = Path(chapter["source_path"]).parent
+
+ chapter["content"] = md_utils.change_link_ext(
+ cfg_files,
+ chapter["content"],
+ ".html",
+ book_root,
+ src_dir,
+ )
+
+ def regref_swap(match: re.Match) -> str:
+ """Replaces regref with a link to the register."""
+ reg = match.group(1).split(".")
+ if len(reg) > 3 or len(reg) < 2:
+ sys.exit(
+ f"{match.group(0)} is invalid. "
+ "Should be in the form: 'ip_block.register.field', where 'field' is optional.",
+ )
+ try:
+ # Make the path to the config file absolute (to root of the site),
+ # so we don't have to worry about what page we are in.
+ # Also, do the hjson -> html conversion.
+ path = "/{}.html".format(
+ name2path[reg[0]].removeprefix("./").removesuffix(".hjson")
+ )
+ except KeyError:
+ sys.exit(f"Ip block with name '{reg[0]}' could not be found.")
+
+ name = reg[1] + "." + reg[2] if len(reg) == 3 else reg[1]
+
+ return "[`{}`]({}#Reg_{})".format(name, path, reg[1].lower())
+
+ chapter["content"] = REGREF_PATTERN.sub(regref_swap, chapter["content"])
+
+ # dump the book into stdout
+ print(json.dumps(book))
+
+
+if __name__ == "__main__":
+ main()