|
| 1 | +import logging |
| 2 | +import os |
| 3 | +import re |
| 4 | +import subprocess |
| 5 | +from functools import lru_cache |
| 6 | +from http.server import HTTPServer, SimpleHTTPRequestHandler |
| 7 | +from importlib import metadata |
| 8 | +from pathlib import Path |
| 9 | + |
| 10 | +import mkdocs.commands.build |
| 11 | +import mkdocs.commands.serve |
| 12 | +import mkdocs.config |
| 13 | +import mkdocs.utils |
| 14 | +import typer |
| 15 | +from jinja2 import Template |
| 16 | + |
| 17 | +logging.basicConfig(level=logging.INFO) |
| 18 | + |
| 19 | +mkdocs_name = "mkdocs.yml" |
| 20 | +en_docs_path = Path("") |
| 21 | + |
| 22 | +app = typer.Typer() |
| 23 | + |
| 24 | + |
| 25 | +@lru_cache |
| 26 | +def is_mkdocs_insiders() -> bool: |
| 27 | + version = metadata.version("mkdocs-material") |
| 28 | + return "insiders" in version |
| 29 | + |
| 30 | + |
| 31 | +@app.callback() |
| 32 | +def callback() -> None: |
| 33 | + if is_mkdocs_insiders(): |
| 34 | + os.environ["INSIDERS_FILE"] = "./mkdocs.insiders.yml" |
| 35 | + # For MacOS with insiders and Cairo |
| 36 | + os.environ["DYLD_FALLBACK_LIBRARY_PATH"] = "/opt/homebrew/lib" |
| 37 | + |
| 38 | + |
| 39 | +index_sponsors_template = """ |
| 40 | +{% if sponsors %} |
| 41 | +{% for sponsor in sponsors.gold -%} |
| 42 | +<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}"></a> |
| 43 | +{% endfor -%} |
| 44 | +{%- for sponsor in sponsors.silver -%} |
| 45 | +<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}"></a> |
| 46 | +{% endfor %} |
| 47 | +{% endif %} |
| 48 | +""" |
| 49 | + |
| 50 | + |
| 51 | +def generate_readme_content() -> str: |
| 52 | + en_index = en_docs_path / "docs" / "index.md" |
| 53 | + content = en_index.read_text("utf-8") |
| 54 | + match_pre = re.search(r"</style>\n\n", content) |
| 55 | + match_start = re.search(r"<!-- sponsors -->", content) |
| 56 | + match_end = re.search(r"<!-- /sponsors -->", content) |
| 57 | + sponsors_data_path = en_docs_path / "data" / "sponsors.yml" |
| 58 | + sponsors = mkdocs.utils.yaml_load(sponsors_data_path.read_text(encoding="utf-8")) |
| 59 | + if not (match_start and match_end): |
| 60 | + raise RuntimeError("Couldn't auto-generate sponsors section") |
| 61 | + if not match_pre: |
| 62 | + raise RuntimeError("Couldn't find pre section (<style>) in index.md") |
| 63 | + frontmatter_end = match_pre.end() |
| 64 | + pre_end = match_start.end() |
| 65 | + post_start = match_end.start() |
| 66 | + template = Template(index_sponsors_template) |
| 67 | + message = template.render(sponsors=sponsors) |
| 68 | + pre_content = content[frontmatter_end:pre_end] |
| 69 | + post_content = content[post_start:] |
| 70 | + new_content = pre_content + message + post_content |
| 71 | + return new_content |
| 72 | + |
| 73 | + |
| 74 | +@app.command() |
| 75 | +def generate_readme() -> None: |
| 76 | + """ |
| 77 | + Generate README.md content from main index.md |
| 78 | + """ |
| 79 | + typer.echo("Generating README") |
| 80 | + readme_path = Path("README.md") |
| 81 | + new_content = generate_readme_content() |
| 82 | + readme_path.write_text(new_content, encoding="utf-8") |
| 83 | + |
| 84 | + |
| 85 | +@app.command() |
| 86 | +def verify_readme() -> None: |
| 87 | + """ |
| 88 | + Verify README.md content from main index.md |
| 89 | + """ |
| 90 | + typer.echo("Verifying README") |
| 91 | + readme_path = Path("README.md") |
| 92 | + generated_content = generate_readme_content() |
| 93 | + readme_content = readme_path.read_text("utf-8") |
| 94 | + if generated_content != readme_content: |
| 95 | + typer.secho( |
| 96 | + "README.md outdated from the latest index.md", color=typer.colors.RED |
| 97 | + ) |
| 98 | + raise typer.Abort() |
| 99 | + typer.echo("Valid README ✅") |
| 100 | + |
| 101 | + |
| 102 | +@app.command() |
| 103 | +def live() -> None: |
| 104 | + """ |
| 105 | + Serve with livereload a docs site for a specific language. |
| 106 | +
|
| 107 | + This only shows the actual translated files, not the placeholders created with |
| 108 | + build-all. |
| 109 | +
|
| 110 | + Takes an optional LANG argument with the name of the language to serve, by default |
| 111 | + en. |
| 112 | + """ |
| 113 | + # Enable line numbers during local development to make it easier to highlight |
| 114 | + os.environ["LINENUMS"] = "true" |
| 115 | + mkdocs.commands.serve.serve(dev_addr="127.0.0.1:8008") |
| 116 | + |
| 117 | + |
| 118 | +@app.command() |
| 119 | +def build() -> None: |
| 120 | + """ |
| 121 | + Build the docs. |
| 122 | + """ |
| 123 | + insiders_env_file = os.environ.get("INSIDERS_FILE") |
| 124 | + print(f"Insiders file {insiders_env_file}") |
| 125 | + if is_mkdocs_insiders(): |
| 126 | + print("Using insiders") |
| 127 | + print("Building docs") |
| 128 | + subprocess.run(["mkdocs", "build"], check=True) |
| 129 | + typer.secho("Successfully built docs", color=typer.colors.GREEN) |
| 130 | + |
| 131 | + |
| 132 | +@app.command() |
| 133 | +def serve() -> None: |
| 134 | + """ |
| 135 | + A quick server to preview a built site. |
| 136 | +
|
| 137 | + For development, prefer the command live (or just mkdocs serve). |
| 138 | +
|
| 139 | + This is here only to preview the documentation site. |
| 140 | +
|
| 141 | + Make sure you run the build command first. |
| 142 | + """ |
| 143 | + typer.echo("Warning: this is a very simple server.") |
| 144 | + typer.echo("For development, use the command live instead.") |
| 145 | + typer.echo("This is here only to preview the documentation site.") |
| 146 | + typer.echo("Make sure you run the build command first.") |
| 147 | + os.chdir("site") |
| 148 | + server_address = ("", 8008) |
| 149 | + server = HTTPServer(server_address, SimpleHTTPRequestHandler) |
| 150 | + typer.echo("Serving at: http://127.0.0.1:8008") |
| 151 | + server.serve_forever() |
| 152 | + |
| 153 | + |
| 154 | +if __name__ == "__main__": |
| 155 | + app() |
0 commit comments