This commit is contained in:
2026-05-14 11:20:33 +02:00
parent 355831c76d
commit 2b3c823743
41 changed files with 2101 additions and 554 deletions

View File

@@ -10,18 +10,13 @@ from __future__ import annotations
import argparse
import html
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
BUSINESS = ROOT / "business"
SOURCE = ROOT.parent / "docs_source" / "business"
TARGETS = (
"lots-and-quantities.md",
"lots-and-quantities.en.md",
)
MATERIAL_ICON = re.compile(r":material-[a-z0-9-]+:\s*")
@@ -190,6 +185,56 @@ def render_callout(lines: list[str]) -> str:
)
def is_bullet_line(line: str) -> bool:
return bool(re.match(r"^\s*-\s+", line))
def bullet_level(line: str) -> int:
return len(re.match(r"^\s*", line).group(0)) // 2
def render_list(lines: list[str]) -> str:
root: list[dict[str, object]] = []
stack: list[tuple[int, list[dict[str, object]]]] = [(-1, root)]
last_item: dict[str, object] | None = None
for line in lines:
if is_bullet_line(line):
level = bullet_level(line)
content = re.sub(r"^\s*-\s+", "", line).strip()
while stack[-1][0] >= level:
stack.pop()
item: dict[str, object] = {
"content": content,
"children": [],
}
stack[-1][1].append(item)
stack.append((level, item["children"]))
last_item = item
elif line.strip() and last_item is not None:
last_item["content"] = str(last_item["content"]) + " " + line.strip()
def render_items(items: list[dict[str, object]], level: int = 0) -> str:
margin = "1.1rem" if level == 0 else "1.35rem"
bullet = "disc" if level == 0 else "circle"
parts = [
f'<ul style="margin:0.65rem 0 1rem {margin}; padding-left:1rem; list-style-type:{bullet};">'
]
for item in items:
children = item["children"]
parts.append(
'<li style="margin:0.38rem 0;">'
f"{render_inline(str(item['content']))}"
)
if children:
parts.append(render_items(children, level + 1))
parts.append("</li>")
parts.append("</ul>")
return "\n".join(parts)
return render_items(root)
def render_source(text: str) -> str:
lines = text.splitlines()
out = [
@@ -244,20 +289,47 @@ def render_source(text: str) -> str:
out.extend(table)
continue
if is_bullet_line(line):
list_lines = []
while i < len(lines) and (
is_bullet_line(lines[i])
or (
lines[i].startswith(" ")
and lines[i].strip()
and not lines[i].lstrip().startswith("|")
)
):
list_lines.append(lines[i])
i += 1
out.append(render_list(list_lines))
continue
out.append(line)
i += 1
return "\n".join(out).strip() + "\n"
def render_all(normalize: bool = False) -> None:
def source_files() -> list[Path]:
return sorted(path for path in SOURCE.rglob("*.md") if path.is_file())
def render_all(normalize: bool = False, check: bool = False) -> bool:
SOURCE.mkdir(parents=True, exist_ok=True)
for name in TARGETS:
source = SOURCE / name
target = BUSINESS / name
ok = True
for source in source_files():
target = BUSINESS / source.relative_to(SOURCE)
target.parent.mkdir(parents=True, exist_ok=True)
if normalize:
source.write_text(normalize_source(source.read_text(encoding="utf-8")), encoding="utf-8")
target.write_text(render_source(source.read_text(encoding="utf-8")), encoding="utf-8")
rendered = render_source(source.read_text(encoding="utf-8"))
if check:
if not target.exists() or target.read_text(encoding="utf-8") != rendered:
print(f"Outdated generated doc: {target.relative_to(ROOT.parent)}")
ok = False
else:
target.write_text(rendered, encoding="utf-8")
return ok
def main() -> None:
@@ -267,8 +339,16 @@ def main() -> None:
action="store_true",
help="Clean existing source files from wiki-only syntax before rendering.",
)
parser.add_argument(
"--check",
action="store_true",
help="Fail if generated wiki files are not up to date.",
)
args = parser.parse_args()
render_all(normalize=args.normalize_source)
if args.normalize_source and args.check:
parser.error("--normalize-source cannot be used with --check")
if not render_all(normalize=args.normalize_source, check=args.check):
sys.exit(1)
if __name__ == "__main__":