From be8de118db913711eb72ae5187d26e54a0055727 Mon Sep 17 00:00:00 2001 From: 简律纯 Date: Fri, 15 Dec 2023 09:11:47 +0800 Subject: refactor(docs): optmst `docs` dir & `deps` --- docs/src/overrides/hooks/shortcodes.py | 283 +++++++++++++++++++++++++++++ docs/src/overrides/hooks/translations.html | 54 ++++++ docs/src/overrides/hooks/translations.py | 193 ++++++++++++++++++++ 3 files changed, 530 insertions(+) create mode 100644 docs/src/overrides/hooks/shortcodes.py create mode 100644 docs/src/overrides/hooks/translations.html create mode 100644 docs/src/overrides/hooks/translations.py (limited to 'docs/src/overrides/hooks') diff --git a/docs/src/overrides/hooks/shortcodes.py b/docs/src/overrides/hooks/shortcodes.py new file mode 100644 index 00000000..5b02e3cf --- /dev/null +++ b/docs/src/overrides/hooks/shortcodes.py @@ -0,0 +1,283 @@ +# Copyright (c) 2016-2023 Martin Donath + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +from __future__ import annotations + +import posixpath +import re + +from mkdocs.config.defaults import MkDocsConfig +from mkdocs.structure.files import File, Files +from mkdocs.structure.pages import Page +from re import Match + +# ----------------------------------------------------------------------------- +# Hooks +# ----------------------------------------------------------------------------- + +# @todo +def on_page_markdown( + markdown: str, *, page: Page, config: MkDocsConfig, files: Files +): + + # Replace callback + def replace(match: Match): + type, args = match.groups() + args = args.strip() + if type == "version": + if args.startswith("insiders-"): + return _badge_for_version_insiders(args, page, files) + else: + return _badge_for_version(args, page, files) + elif type == "sponsors": return _badge_for_sponsors(page, files) + elif type == "flag": return flag(args, page, files) + elif type == "option": return option(args) + elif type == "setting": return setting(args) + elif type == "feature": return _badge_for_feature(args, page, files) + elif type == "plugin": return _badge_for_plugin(args, page, files) + elif type == "extension": return _badge_for_extension(args, page, files) + elif type == "utility": return _badge_for_utility(args, page, files) + elif type == "example": return _badge_for_example(args, page, files) + elif type == "default": + if args == "none": return _badge_for_default_none(page, files) + elif args == "computed": return _badge_for_default_computed(page, files) + else: return _badge_for_default(args, page, files) + + # Otherwise, raise an error + raise RuntimeError(f"Unknown shortcode: {type}") + + # Find and replace all external asset URLs in current page + return re.sub( + r"", + replace, markdown, flags = re.I | re.M + ) + +# ----------------------------------------------------------------------------- +# Helper functions +# ----------------------------------------------------------------------------- + +# Create a flag of a specific type +def flag(args: str, page: Page, files: Files): + type, *_ = args.split(" ", 1) + if type == "experimental": return _badge_for_experimental(page, files) + elif type == "required": return _badge_for_required(page, files) + elif type == "customization": return _badge_for_customization(page, files) + elif type == "metadata": return _badge_for_metadata(page, files) + elif type == "multiple": return _badge_for_multiple(page, files) + raise RuntimeError(f"Unknown type: {type}") + +# Create a linkable option +def option(type: str): + _, *_, name = re.split(r"[.:]", type) + return f"[`{name}`](#+{type}){{ #+{type} }}\n\n" + +# Create a linkable setting - @todo append them to the bottom of the page +def setting(type: str): + _, *_, name = re.split(r"[.*]", type) + return f"`{name}` {{ #{type} }}\n\n[{type}]: #{type}\n\n" + +# ----------------------------------------------------------------------------- + +# Resolve path of file relative to given page - the posixpath always includes +# one additional level of `..` which we need to remove +def _resolve_path(path: str, page: Page, files: Files): + path, anchor, *_ = f"{path}#".split("#") + path = _resolve(files.get_file_from_path(path), page) + return "#".join([path, anchor]) if anchor else path + +# Resolve path of file relative to given page - the posixpath always includes +# one additional level of `..` which we need to remove +def _resolve(file: File, page: Page): + path = posixpath.relpath(file.src_uri, page.file.src_uri) + return posixpath.sep.join(path.split(posixpath.sep)[1:]) + +# ----------------------------------------------------------------------------- + +# Create badge +def _badge(icon: str, text: str = "", type: str = ""): + classes = f"mdx-badge mdx-badge--{type}" if type else "mdx-badge" + return "".join([ + f"", + *([f"{icon}"] if icon else []), + *([f"{text}"] if text else []), + f"", + ]) + +# Create sponsors badge +def _badge_for_sponsors(page: Page, files: Files): + icon = "material-heart" + href = _resolve_path("insiders/index.md", page, files) + return _badge( + icon = f"[:{icon}:]({href} 'Sponsors only')", + type = "heart" + ) + +# Create badge for version +def _badge_for_version(text: str, page: Page, files: Files): + spec = text + path = f"changelog/index.md#{spec}" + + # Return badge + icon = "material-tag-outline" + href = _resolve_path("conventions.md#version", page, files) + return _badge( + icon = f"[:{icon}:]({href} 'Minimum version')", + text = f"[{text}]({_resolve_path(path, page, files)})" if spec else "" + ) + +# Create badge for version of Insiders +def _badge_for_version_insiders(text: str, page: Page, files: Files): + spec = text.replace("insiders-", "") + path = f"insiders/changelog/index.md#{spec}" + + # Return badge + icon = "material-tag-heart-outline" + href = _resolve_path("conventions.md#version-insiders", page, files) + return _badge( + icon = f"[:{icon}:]({href} 'Minimum version')", + text = f"[{text}]({_resolve_path(path, page, files)})" if spec else "" + ) + +# Create badge for feature +def _badge_for_feature(text: str, page: Page, files: Files): + icon = "material-toggle-switch" + href = _resolve_path("conventions.md#feature", page, files) + return _badge( + icon = f"[:{icon}:]({href} 'Optional feature')", + text = text + ) + +# Create badge for plugin +def _badge_for_plugin(text: str, page: Page, files: Files): + icon = "material-floppy" + href = _resolve_path("conventions.md#plugin", page, files) + return _badge( + icon = f"[:{icon}:]({href} 'Plugin')", + text = text + ) + +# Create badge for extension +def _badge_for_extension(text: str, page: Page, files: Files): + icon = "material-language-markdown" + href = _resolve_path("conventions.md#extension", page, files) + return _badge( + icon = f"[:{icon}:]({href} 'Markdown extension')", + text = text + ) + +# Create badge for utility +def _badge_for_utility(text: str, page: Page, files: Files): + icon = "material-package-variant" + href = _resolve_path("conventions.md#utility", page, files) + return _badge( + icon = f"[:{icon}:]({href} 'Third-party utility')", + text = text + ) + +# Create badge for example +def _badge_for_example(text: str, page: Page, files: Files): + return "\n".join([ + _badge_for_example_download(text, page, files), + _badge_for_example_view(text, page, files) + ]) + +# Create badge for example view +def _badge_for_example_view(text: str, page: Page, files: Files): + icon = "material-folder-eye" + href = f"https://mkdocs-material.github.io/examples/{text}/" + return _badge( + icon = f"[:{icon}:]({href} 'View example')", + type = "right" + ) + +# Create badge for example download +def _badge_for_example_download(text: str, page: Page, files: Files): + icon = "material-folder-download" + href = f"https://mkdocs-material.github.io/examples/{text}.zip" + return _badge( + icon = f"[:{icon}:]({href} 'Download example')", + text = f"[`.zip`]({href})", + type = "right" + ) + +# Create badge for default value +def _badge_for_default(text: str, page: Page, files: Files): + icon = "material-water" + href = _resolve_path("conventions.md#default", page, files) + return _badge( + icon = f"[:{icon}:]({href} 'Default value')", + text = text + ) + +# Create badge for empty default value +def _badge_for_default_none(page: Page, files: Files): + icon = "material-water-outline" + href = _resolve_path("conventions.md#default", page, files) + return _badge( + icon = f"[:{icon}:]({href} 'Default value is empty')" + ) + +# Create badge for computed default value +def _badge_for_default_computed(page: Page, files: Files): + icon = "material-water-check" + href = _resolve_path("conventions.md#default", page, files) + return _badge( + icon = f"[:{icon}:]({href} 'Default value is computed')" + ) + +# Create badge for metadata property flag +def _badge_for_metadata(page: Page, files: Files): + icon = "material-list-box-outline" + href = _resolve_path("conventions.md#metadata", page, files) + return _badge( + icon = f"[:{icon}:]({href} 'Metadata property')" + ) + +# Create badge for required value flag +def _badge_for_required(page: Page, files: Files): + icon = "material-alert" + href = _resolve_path("conventions.md#required", page, files) + return _badge( + icon = f"[:{icon}:]({href} 'Required value')" + ) + +# Create badge for customization flag +def _badge_for_customization(page: Page, files: Files): + icon = "material-brush-variant" + href = _resolve_path("conventions.md#customization", page, files) + return _badge( + icon = f"[:{icon}:]({href} 'Customization')" + ) + +# Create badge for multiple instance flag +def _badge_for_multiple(page: Page, files: Files): + icon = "material-inbox-multiple" + href = _resolve_path("conventions.md#multiple-instances", page, files) + return _badge( + icon = f"[:{icon}:]({href} 'Multiple instances')" + ) + +# Create badge for experimental flag +def _badge_for_experimental(page: Page, files: Files): + icon = "material-flask-outline" + href = _resolve_path("conventions.md#experimental", page, files) + return _badge( + icon = f"[:{icon}:]({href} 'Experimental')" + ) diff --git a/docs/src/overrides/hooks/translations.html b/docs/src/overrides/hooks/translations.html new file mode 100644 index 00000000..ab41c77d --- /dev/null +++ b/docs/src/overrides/hooks/translations.html @@ -0,0 +1,54 @@ + + + +{% macro render_language(language) %} +
+ :flag_{{ language.flag }}:{ .lg .middle } + + + {{ language.name }} + {{ language.code }} + + {% if language.miss %} + + + {{ language.miss | length }} translations missing + + + {% else %} + Complete + {% endif %} + +
+{% endmacro %} + + +{% macro render(translations, start = 1) %} +
+
    + {% for language in translations %} +
  1. {{ render_language(language) }}
  2. + {% endfor %} +
+
+{% endmacro %} diff --git a/docs/src/overrides/hooks/translations.py b/docs/src/overrides/hooks/translations.py new file mode 100644 index 00000000..661fd18e --- /dev/null +++ b/docs/src/overrides/hooks/translations.py @@ -0,0 +1,193 @@ +# Copyright (c) 2016-2023 Martin Donath + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +import os +import re + +from glob import iglob +from mkdocs.config.defaults import MkDocsConfig +from mkdocs.structure.pages import Page +from urllib.parse import urlencode, urlparse + +# ----------------------------------------------------------------------------- +# Hooks +# ----------------------------------------------------------------------------- + +# Determine missing translations and render language overview in the setup +# guide, including links to provide missing translations. +def on_page_markdown(markdown: str, *, page: Page, config: MkDocsConfig, files): + issue_url = "https://github.com/squidfunk/mkdocs-material/issues/new" + if page.file.src_uri != "setup/changing-the-language.md": + return + + # Collect all existing languages + names: dict[str, str] = {} + known: dict[str, dict[str, str]] = {} + for path in iglob("src/templates/partials/languages/*.html"): + with open(path, "r", encoding = "utf-8") as f: + data = f.read() + + # Extract language code and name + name, = re.findall(r"", data) + code, _ = os.path.splitext(os.path.basename(path)) + + # Map names and available translations + names[code] = name + known[code] = dict(re.findall( + r"^ \"([^\"]+)\": \"([^\"]*)\"(?:,|$)?", data, + re.MULTILINE + )) + + # Remove technical stuff + for key in [ + "direction", + "search.config.pipeline", + "search.config.lang", + "search.config.separator" + ]: + if key in known[code]: + del known[code][key] + + # Traverse all languages and compute missing translations + languages = [] + reference = set(known["en"]) + for code, name in names.items(): + miss = reference - set(known[code]) + + # Check each translations + translations: list[str] = [] + for key, value in known["en"].items(): + if key in known[code]: + translations.append( + f" \"{key}\": \"{known[code][key]}\"" + ) + else: + translations.append( + f" \"{key}\": \"{value} ⬅️\"" + ) + + # Assemble GitHub issue URL + link = urlparse(issue_url) + link = link._replace(query = urlencode({ + "template": "04-add-translations.yml", + "title": f"Update {name} translations", + "translations": "\n".join([ + "{% macro t(key) %}{{ {", + ",\n".join(translations), + "}[key] }}{% endmacro %}" + ]), + "country-flag": f":flag_{countries[code]}:" + })) + + # Add translation + languages.append({ + "flag": countries[code], + "code": code, + "name": name, + "link": link.geturl(), + "miss": miss + }) + + # Load template and render translations + env = config.theme.get_env() + template = env.get_template( "hooks/translations.html") + translations = template.module.render( + sorted(languages, key = lambda language: language["name"]) + ) + + # Replace translation marker + return markdown.replace( + "", "\n".join( + [line.lstrip() for line in translations.split("\n") + ] + )) + +# ----------------------------------------------------------------------------- +# Data +# ----------------------------------------------------------------------------- + +# Map ISO 639-1 (languages) to ISO 3166 (countries) +countries = dict({ + "af": "za", + "ar": "ae", + "be": "by", + "bg": "bg", + "bn": "bd", + "ca": "es", + "cs": "cz", + "da": "dk", + "de": "de", + "el": "gr", + "en": "us", + "eo": "eu", + "es": "es", + "et": "ee", + "eu": "es", + "fa": "ir", + "fi": "fi", + "fr": "fr", + "gl": "es", + "he": "il", + "hi": "in", + "hr": "hr", + "hu": "hu", + "hy": "am", + "id": "id", + "is": "is", + "it": "it", + "ja": "jp", + "ka": "ge", + "kn": "in", + "ko": "kr", + "ku-IQ": "iq", + "lb": "lu", + "lt": "lt", + "lv": "lv", + "mk": "mk", + "mn": "mn", + "ms": "my", + "my": "mm", + "nb": "no", + "nl": "nl", + "nn": "no", + "pl": "pl", + "pt-BR": "br", + "pt": "pt", + "ro": "ro", + "ru": "ru", + "sa": "in", + "sh": "rs", + "si": "lk", + "sk": "sk", + "sl": "si", + "sr": "rs", + "sv": "se", + "te": "in", + "th": "th", + "tl": "ph", + "tr": "tr", + "uk": "ua", + "ur": "pk", + "uz": "uz", + "vi": "vn", + "zh": "cn", + "zh-Hant": "cn", + "zh-TW": "tw" +}) -- cgit v1.2.3-70-g09d2