mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-09 22:25:09 +00:00

## Summary The purpose of this change is mainly to address one of the issues outlined in #10427. Namely, some lists in the docs were not rendering properly when preceded by a text block without a newline character. This PR adds `mdformat` as a final step to the rule documentation script, so that any missing newlines will be added. NB: The default behavior of `mdformat` is to escape markdown special characters found in text such as `<`. This resulted in some misformatted docs. To address this I implemented an ad-hoc mdformat plugin to override the behavior. This may be considered a bit 'hacky', but I think it's a good solution. Nevertheless, if someone has a better idea, let me know. ## Test Plan This change is hard to test systematically, however, I tried my best to look at the before and after diffs to ensure no unwanted changes were made to the docs.
193 lines
6.2 KiB
Python
193 lines
6.2 KiB
Python
"""Generate an MkDocs-compatible `docs` and `mkdocs.yml` from the README.md."""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
from pathlib import Path
|
|
from typing import NamedTuple
|
|
|
|
import mdformat
|
|
import yaml
|
|
|
|
from _mdformat_utils import add_no_escape_text_plugin
|
|
|
|
|
|
class Section(NamedTuple):
|
|
"""A section to include in the MkDocs documentation."""
|
|
|
|
title: str
|
|
filename: str
|
|
generated: bool
|
|
|
|
|
|
SECTIONS: list[Section] = [
|
|
Section("Overview", "index.md", generated=True),
|
|
Section("Tutorial", "tutorial.md", generated=False),
|
|
Section("Installing Ruff", "installation.md", generated=False),
|
|
Section("The Ruff Linter", "linter.md", generated=False),
|
|
Section("The Ruff Formatter", "formatter.md", generated=False),
|
|
Section("Configuring Ruff", "configuration.md", generated=False),
|
|
Section("Preview", "preview.md", generated=False),
|
|
Section("Rules", "rules.md", generated=True),
|
|
Section("Settings", "settings.md", generated=True),
|
|
Section("Versioning", "versioning.md", generated=False),
|
|
Section("Integrations", "integrations.md", generated=False),
|
|
Section("FAQ", "faq.md", generated=False),
|
|
Section("Contributing", "contributing.md", generated=True),
|
|
]
|
|
|
|
|
|
LINK_REWRITES: dict[str, str] = {
|
|
"https://docs.astral.sh/ruff/": "index.md",
|
|
"https://docs.astral.sh/ruff/configuration/": "configuration.md",
|
|
"https://docs.astral.sh/ruff/configuration/#pyprojecttoml-discovery": (
|
|
"configuration.md#pyprojecttoml-discovery"
|
|
),
|
|
"https://docs.astral.sh/ruff/contributing/": "contributing.md",
|
|
"https://docs.astral.sh/ruff/integrations/": "integrations.md",
|
|
"https://docs.astral.sh/ruff/faq/#how-does-ruff-compare-to-flake8": (
|
|
"faq.md#how-does-ruff-compare-to-flake8"
|
|
),
|
|
"https://docs.astral.sh/ruff/installation/": "installation.md",
|
|
"https://docs.astral.sh/ruff/rules/": "rules.md",
|
|
"https://docs.astral.sh/ruff/settings/": "settings.md",
|
|
"#whos-using-ruff": "https://github.com/astral-sh/ruff#whos-using-ruff",
|
|
}
|
|
|
|
|
|
def clean_file_content(content: str, title: str) -> str:
|
|
"""Add missing title, fix the header depth, and remove trailing empty lines."""
|
|
lines = content.splitlines()
|
|
if lines[0].startswith("# "):
|
|
return content
|
|
|
|
in_code_block = False
|
|
for i, line in enumerate(lines):
|
|
if line.startswith("```"):
|
|
in_code_block = not in_code_block
|
|
if not in_code_block and line.startswith("#"):
|
|
lines[i] = line[1:]
|
|
|
|
# Remove trailing empty lines.
|
|
for line in reversed(lines):
|
|
if line == "":
|
|
del lines[-1]
|
|
else:
|
|
break
|
|
|
|
content = "\n".join(lines) + "\n"
|
|
|
|
# Add a missing title.
|
|
return f"# {title}\n\n" + content
|
|
|
|
|
|
def main() -> None:
|
|
"""Generate an MkDocs-compatible `docs` and `mkdocs.yml`."""
|
|
subprocess.run(["cargo", "dev", "generate-docs"], check=True)
|
|
|
|
with Path("README.md").open(encoding="utf8") as fp:
|
|
content = fp.read()
|
|
|
|
# Rewrite links to the documentation.
|
|
for src, dst in LINK_REWRITES.items():
|
|
before = content
|
|
after = content.replace(f"({src})", f"({dst})")
|
|
if before == after:
|
|
msg = f"Unexpected link rewrite in README.md: {src}"
|
|
raise ValueError(msg)
|
|
content = after
|
|
|
|
if m := re.search(r"\(https://docs.astral.sh/ruff/.*\)", content):
|
|
msg = f"Unexpected absolute link to documentation: {m.group(0)}"
|
|
raise ValueError(msg)
|
|
|
|
Path("docs").mkdir(parents=True, exist_ok=True)
|
|
|
|
# Split the README.md into sections.
|
|
for title, filename, generated in SECTIONS:
|
|
if not generated:
|
|
continue
|
|
|
|
with Path(f"docs/{filename}").open("w+", encoding="utf8") as f:
|
|
if filename == "contributing.md":
|
|
# Copy the CONTRIBUTING.md.
|
|
shutil.copy("CONTRIBUTING.md", "docs/contributing.md")
|
|
continue
|
|
|
|
if filename == "settings.md":
|
|
file_content = subprocess.check_output(
|
|
["cargo", "dev", "generate-options"],
|
|
encoding="utf-8",
|
|
)
|
|
else:
|
|
block = content.split(f"<!-- Begin section: {title} -->\n\n")
|
|
if len(block) != 2:
|
|
msg = f"Section {title} not found in README.md"
|
|
raise ValueError(msg)
|
|
|
|
block = block[1].split(f"\n<!-- End section: {title} -->")
|
|
if len(block) != 2:
|
|
msg = f"Section {title} not found in README.md"
|
|
raise ValueError(msg)
|
|
|
|
file_content = block[0]
|
|
|
|
if filename == "rules.md":
|
|
file_content += "\n" + subprocess.check_output(
|
|
["cargo", "dev", "generate-rules-table"],
|
|
encoding="utf-8",
|
|
)
|
|
|
|
f.write(clean_file_content(file_content, title))
|
|
|
|
# Format rules docs
|
|
add_no_escape_text_plugin()
|
|
for rule_doc in Path("docs/rules").glob("*.md"):
|
|
mdformat.file(rule_doc, extensions=["mkdocs", "admonition", "no-escape-text"])
|
|
|
|
with Path("mkdocs.template.yml").open(encoding="utf8") as fp:
|
|
config = yaml.safe_load(fp)
|
|
|
|
# Add the redirect section to mkdocs.yml.
|
|
rules = json.loads(
|
|
subprocess.check_output(
|
|
[
|
|
"cargo",
|
|
"run",
|
|
"-p",
|
|
"ruff",
|
|
"--",
|
|
"rule",
|
|
"--all",
|
|
"--output-format",
|
|
"json",
|
|
],
|
|
),
|
|
)
|
|
config["plugins"].append(
|
|
{
|
|
"redirects": {
|
|
"redirect_maps": {
|
|
f'rules/{rule["code"]}.md': f'rules/{rule["name"]}.md'
|
|
for rule in rules
|
|
},
|
|
},
|
|
},
|
|
)
|
|
|
|
# Add the nav section to mkdocs.yml.
|
|
config["nav"] = [{section.title: section.filename} for section in SECTIONS]
|
|
|
|
with Path("mkdocs.generated.yml").open("w+", encoding="utf8") as fp:
|
|
yaml.safe_dump(config, fp)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(
|
|
description="Generate an MkDocs-compatible `docs` and `mkdocs.yml`.",
|
|
)
|
|
args = parser.parse_args()
|
|
main()
|