From 3c798b04d500377de62d8538e8f28845e9827f6a Mon Sep 17 00:00:00 2001 From: Louis Velez Date: Fri, 13 Dec 2024 21:12:28 -0500 Subject: [PATCH 1/5] feat: glossary infra --- docs/css/tooltip.css | 44 +++++++++++++++++++++++ docs/hooks/glossary.py | 79 ++++++++++++++++++++++++++++++++++++++++++ mkdocs.yml | 4 +++ 3 files changed, 127 insertions(+) create mode 100644 docs/css/tooltip.css create mode 100644 docs/hooks/glossary.py diff --git a/docs/css/tooltip.css b/docs/css/tooltip.css new file mode 100644 index 00000000000000..b9a54f793f9fe5 --- /dev/null +++ b/docs/css/tooltip.css @@ -0,0 +1,44 @@ +[data-tooltip] { + position: relative; + display: inline-block; + border-bottom: 1px dotted black; +} + +[data-tooltip] .tooltip-content { + width: max-content; + max-width: 25em; + position: absolute; + top: 100%; + left: 50%; + transform: translateX(-50%); + background-color: white; + color: #404040; + box-shadow: 0 4px 14px 0 rgba(0,0,0,.2), 0 0 0 1px rgba(0,0,0,.05); + padding: 10px; + font: 14px/1.5 Lato, proxima-nova, Helvetica Neue, Arial, sans-serif; + text-decoration: none; + opacity: 0; + visibility: hidden; + transition: opacity 0.1s, visibility 0s; + z-index: 1000; + pointer-events: none; /* Prevent accidental interaction */ +} + +[data-tooltip]:hover .tooltip-content { + opacity: 1; + visibility: visible; + pointer-events: auto; /* Allow interaction when visible */ +} + +.tooltip-content .tooltip-glossary-link { + display: inline-block; + margin-top: 8px; + font-size: 12px; + color: #007bff; + text-decoration: none; +} + +.tooltip-content .tooltip-glossary-link:hover { + color: #0056b3; + text-decoration: underline; +} diff --git a/docs/hooks/glossary.py b/docs/hooks/glossary.py new file mode 100644 index 00000000000000..bf2eb375494ad0 --- /dev/null +++ b/docs/hooks/glossary.py @@ -0,0 +1,79 @@ +import os +import re +import tomllib + +def load_glossary(file_path="docs/glossary.toml"): + if not os.path.exists(file_path): + return {} + with open(file_path, "rb") as f: + glossary_data = tomllib.load(f) + return glossary_data.get("glossary", {}) + +glossary = None + +def get_glossary(): + global glossary + if glossary is None: + glossary = load_glossary() + return glossary + +def generate_anchor_id(name): + return name.replace(" ", "-").replace("_", "-").lower() + +def format_markdown_term(name, definition): + anchor_id = generate_anchor_id(name) + markdown = f"* [**{name.replace('_', ' ').title()}**](#{anchor_id})" + if definition.get("abbreviation"): + markdown += f" *({definition['abbreviation']})*" + if definition.get("description"): + markdown += f": {definition['description']}\n" + return markdown + +def glossary_markdown(vocabulary): + markdown = "" + for category, terms in vocabulary.items(): + markdown += f"## {category.replace('_', ' ').title()}\n\n" + for name, definition in terms.items(): + markdown += format_markdown_term(name, definition) + return markdown + +def format_tooltip_html(term_key, definition, html): + display_term = term_key.replace("_", " ").title() + clean_description = re.sub(r"\[(.+)]\(.+\)", r"\1", definition["description"]) + glossary_link = ( + f"Glossaryđź”—" + ) + return re.sub( + re.escape(display_term), + lambda + match: f"{match.group(0)}{clean_description} {glossary_link}", + html, + flags=re.IGNORECASE, + ) + +def apply_tooltip(_term_key, _definition, pattern, html): + return re.sub( + pattern, + lambda match: format_tooltip_html(_term_key, _definition, match.group(0)), + html, + flags=re.IGNORECASE, + ) + +def tooltip_html(vocabulary, html): + for _category, terms in vocabulary.items(): + for term_key, definition in terms.items(): + if definition.get("description"): + pattern = rf"(?)(?!\([^)]*\))" + html = apply_tooltip(term_key, definition, pattern, html) + return html + +# Page Hooks +def on_page_markdown(markdown, **kwargs): + vocabulary = get_glossary() + return markdown.replace("{{GLOSSARY_DEFINITIONS}}", glossary_markdown(vocabulary)) + +def on_page_content(html, **kwargs): + if kwargs.get("page").title == "Glossary": + return html + vocabulary = get_glossary() + return tooltip_html(vocabulary, html) diff --git a/mkdocs.yml b/mkdocs.yml index 58527ea7ee021d..a66d1c76d45bba 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -8,6 +8,10 @@ strict: true docs_dir: docs site_dir: docs_site/ +hooks: + - docs/hooks/glossary.py +extra_css: + - css/tooltip.css theme: name: readthedocs navigation_depth: 3 From 780226414c72fe1a73eca4bbc72df3bb325c5268 Mon Sep 17 00:00:00 2001 From: Louis Velez Date: Fri, 13 Dec 2024 22:24:19 -0500 Subject: [PATCH 2/5] fix static analysis error --- docs/hooks/glossary.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/hooks/glossary.py b/docs/hooks/glossary.py index bf2eb375494ad0..17824593a1ff9b 100644 --- a/docs/hooks/glossary.py +++ b/docs/hooks/glossary.py @@ -1,6 +1,7 @@ import os import re import tomllib +from typing import Dict, Optional, Any def load_glossary(file_path="docs/glossary.toml"): if not os.path.exists(file_path): @@ -9,7 +10,7 @@ def load_glossary(file_path="docs/glossary.toml"): glossary_data = tomllib.load(f) return glossary_data.get("glossary", {}) -glossary = None +glossary: Optional[Dict[str, Any]] = None def get_glossary(): global glossary From e540effdc8ef3b78753950731129388fcfd92043 Mon Sep 17 00:00:00 2001 From: Louis Velez Date: Fri, 13 Dec 2024 22:29:23 -0500 Subject: [PATCH 3/5] fix ruff linter error. --- docs/hooks/glossary.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/hooks/glossary.py b/docs/hooks/glossary.py index 17824593a1ff9b..fa32c967d5bbe6 100644 --- a/docs/hooks/glossary.py +++ b/docs/hooks/glossary.py @@ -1,7 +1,7 @@ import os import re import tomllib -from typing import Dict, Optional, Any +from typing import Any def load_glossary(file_path="docs/glossary.toml"): if not os.path.exists(file_path): @@ -10,7 +10,7 @@ def load_glossary(file_path="docs/glossary.toml"): glossary_data = tomllib.load(f) return glossary_data.get("glossary", {}) -glossary: Optional[Dict[str, Any]] = None +glossary: dict[str, Any] | None = None def get_glossary(): global glossary From c6e9df0d3c211fa9c52134c30d405f25fdd89e14 Mon Sep 17 00:00:00 2001 From: Louis Velez Date: Fri, 13 Dec 2024 22:56:32 -0500 Subject: [PATCH 4/5] updates docs.yaml to use ubuntu-24.04 --- .github/workflows/docs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 6ede255a00a20c..92c311829c0654 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -18,7 +18,7 @@ concurrency: jobs: docs: name: build docs - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: commaai/timeout@v1 From 36cc7c720a6990fcd5d792fe9a748c67866cb9f8 Mon Sep 17 00:00:00 2001 From: Louis Velez Date: Fri, 13 Dec 2024 23:16:43 -0500 Subject: [PATCH 5/5] code review fixes --- docs/glossary.toml | 0 docs/hooks/glossary.py | 22 +++++----------------- 2 files changed, 5 insertions(+), 17 deletions(-) create mode 100644 docs/glossary.toml diff --git a/docs/glossary.toml b/docs/glossary.toml new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/docs/hooks/glossary.py b/docs/hooks/glossary.py index fa32c967d5bbe6..e2fa3d51e04cb9 100644 --- a/docs/hooks/glossary.py +++ b/docs/hooks/glossary.py @@ -1,23 +1,11 @@ -import os import re import tomllib -from typing import Any def load_glossary(file_path="docs/glossary.toml"): - if not os.path.exists(file_path): - return {} with open(file_path, "rb") as f: glossary_data = tomllib.load(f) return glossary_data.get("glossary", {}) -glossary: dict[str, Any] | None = None - -def get_glossary(): - global glossary - if glossary is None: - glossary = load_glossary() - return glossary - def generate_anchor_id(name): return name.replace(" ", "-").replace("_", "-").lower() @@ -47,7 +35,7 @@ def format_tooltip_html(term_key, definition, html): return re.sub( re.escape(display_term), lambda - match: f"{match.group(0)}{clean_description} {glossary_link}", + match: f"{match.group(0)}{clean_description} {glossary_link}", html, flags=re.IGNORECASE, ) @@ -70,11 +58,11 @@ def tooltip_html(vocabulary, html): # Page Hooks def on_page_markdown(markdown, **kwargs): - vocabulary = get_glossary() - return markdown.replace("{{GLOSSARY_DEFINITIONS}}", glossary_markdown(vocabulary)) + glossary = load_glossary() + return markdown.replace("{{GLOSSARY_DEFINITIONS}}", glossary_markdown(glossary)) def on_page_content(html, **kwargs): if kwargs.get("page").title == "Glossary": return html - vocabulary = get_glossary() - return tooltip_html(vocabulary, html) + glossary = load_glossary() + return tooltip_html(glossary, html)