Skip to content
Merged

448 #453

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions .github/workflows/site.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# SPDX-FileCopyrightText: 2026 RAprogramm <andrey.rozanov.vl@gmail.com>
# SPDX-License-Identifier: MIT

name: Docs Site

on:
push:
branches: [main]
paths: ["wiki/**", "site/**", ".github/workflows/site.yml"]
pull_request:
paths: ["wiki/**", "site/**", ".github/workflows/site.yml"]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: pages-${{ github.ref }}
cancel-in-progress: false

jobs:
build:
name: Build site from wiki
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

- name: Install mdBook
uses: taiki-e/install-action@v2
with:
tool: mdbook@0.5.3

- name: Build site
run: python3 site/build.py --out "$RUNNER_TEMP/site"

- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v5
with:
path: ${{ runner.temp }}/site

deploy:
name: Deploy to GitHub Pages
if: github.event_name != 'pull_request'
needs: build
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy
id: deployment
uses: actions/deploy-pages@v5
37 changes: 37 additions & 0 deletions .github/workflows/wiki.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# SPDX-FileCopyrightText: 2026 RAprogramm <andrey.rozanov.vl@gmail.com>
# SPDX-License-Identifier: MIT

name: Publish Wiki

on:
push:
branches: [main]
paths: ["wiki/**"]
workflow_dispatch:

permissions:
contents: write

jobs:
publish:
name: Sync wiki/ to GitHub wiki
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

- name: Push wiki contents
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git clone "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.wiki.git" /tmp/wiki
rsync -a --delete --exclude .git wiki/ /tmp/wiki/
cd /tmp/wiki
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add -A
if git diff --cached --quiet; then
echo "Wiki already up to date"
exit 0
fi
git commit -m "docs: sync wiki from ${GITHUB_SHA}"
git push
10 changes: 10 additions & 0 deletions REUSE.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version = 1
SPDX-PackageName = "masterror"
SPDX-PackageSupplier = "RAprogramm <andrey.rozanov.vl@gmail.com>"
SPDX-PackageDownloadLocation = "https://github.com/RAprogramm/masterror"

[[annotations]]
path = ["wiki/**"]
precedence = "aggregate"
SPDX-FileCopyrightText = "2026 RAprogramm <andrey.rozanov.vl@gmail.com>"
SPDX-License-Identifier = "MIT"
173 changes: 173 additions & 0 deletions site/build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2026 RAprogramm <andrey.rozanov.vl@gmail.com>
# SPDX-License-Identifier: MIT

"""Build the multilingual documentation site from wiki/ with mdBook.

Parses wiki/_Sidebar.md to discover languages and page order, generates one
mdBook per language, rewrites wiki-style links to book-relative links, and
assembles the final site with a landing page at the output root.

Usage: python3 site/build.py --out <output-directory>
"""

import argparse
import re
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
from urllib.parse import unquote

REPO_URL = "https://github.com/RAprogramm/masterror"
WIKI_URL = REPO_URL + "/wiki/"
LANGUAGES = {
"English": "en",
"Русский": "ru",
"한국어": "ko",
}

HOME_RE = re.compile(r"^\*\*\[(.+?)\]\((\S+?)\)\*\*$")
PART_RE = re.compile(r"^\*\*(.+?)\*\*$")
PAGE_RE = re.compile(r"^- \[(.+?)\]\((\S+?)\)$")
HEADING_RE = re.compile(r"^## (.+)$")
LINK_RE = re.compile(r"\]\(([^)\s]+)\)")


def slug_of(url):
return unquote(url.rsplit("/", 1)[-1])


def parse_sidebar(text):
"""Return {lang: [(kind, title, slug?), ...]} in sidebar order."""
books = {}
current = None
for raw in text.splitlines():
line = raw.strip()
heading = HEADING_RE.match(line)
if heading:
current = None
for name, code in LANGUAGES.items():
if name in heading.group(1):
current = code
books[code] = []
continue
if current is None:
continue
m = HOME_RE.match(line)
if m:
books[current].append(("home", m.group(1), slug_of(m.group(2))))
continue
m = PART_RE.match(line)
if m:
books[current].append(("part", m.group(1), None))
continue
m = PAGE_RE.match(line)
if m:
books[current].append(("page", m.group(1), slug_of(m.group(2))))
return books


def rewrite_links(text, lang, slug_lang):
"""Rewrite wiki links: same book -> page.md, other book -> ../lang/page.html."""

def repl(match):
target = match.group(1)
if target.startswith(WIKI_URL):
target = target[len(WIKI_URL):]
elif "://" in target or target.startswith(("#", "mailto:")):
return match.group(0)
slug, _, anchor = target.partition("#")
slug = unquote(slug)
if slug not in slug_lang:
return match.group(0)
suffix = "#" + anchor if anchor else ""
if slug_lang[slug] == lang:
return "](" + slug + ".md" + suffix + ")"
return "](../" + slug_lang[slug] + "/" + slug + ".html" + suffix + ")"

return LINK_RE.sub(repl, text)


def book_toml(lang):
return (
"[book]\n"
'title = "masterror"\n'
'language = "' + lang + '"\n'
'src = "src"\n'
"\n"
"[output.html]\n"
'site-url = "/masterror/' + lang + '/"\n'
'git-repository-url = "' + REPO_URL + '"\n'
'edit-url-template = "' + REPO_URL + '/edit/main/wiki/{path}"\n'
"\n"
"[output.html.playground]\n"
"runnable = false\n"
)


def build_book(lang, items, wiki_dir, work_dir, out_dir, slug_lang):
book_dir = work_dir / lang
src_dir = book_dir / "src"
src_dir.mkdir(parents=True)
summary = ["# Summary", ""]
for kind, title, slug in items:
if kind == "home":
summary += ["[" + title + "](" + slug + ".md)", ""]
elif kind == "part":
summary += ["# " + title, ""]
else:
summary.append("- [" + title + "](" + slug + ".md)")
(src_dir / "SUMMARY.md").write_text("\n".join(summary) + "\n", encoding="utf-8")
for kind, _title, slug in items:
if kind == "part":
continue
text = (wiki_dir / (slug + ".md")).read_text(encoding="utf-8")
(src_dir / (slug + ".md")).write_text(
rewrite_links(text, lang, slug_lang), encoding="utf-8"
)
(book_dir / "book.toml").write_text(book_toml(lang), encoding="utf-8")
subprocess.run(
["mdbook", "build", "--dest-dir", str(out_dir / lang)],
cwd=book_dir,
check=True,
)


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--out", required=True, type=Path)
args = parser.parse_args()

repo_root = Path(__file__).resolve().parent.parent
wiki_dir = repo_root / "wiki"
out_dir = args.out.resolve()

books = parse_sidebar((wiki_dir / "_Sidebar.md").read_text(encoding="utf-8"))
missing = sorted(set(LANGUAGES.values()) - set(books))
if missing:
sys.exit("languages missing from wiki/_Sidebar.md: " + ", ".join(missing))

slug_lang = {
slug: lang
for lang, items in books.items()
for kind, _title, slug in items
if kind != "part"
}

if out_dir.exists():
shutil.rmtree(out_dir)
out_dir.mkdir(parents=True)

with tempfile.TemporaryDirectory() as work:
for lang, items in books.items():
build_book(lang, items, wiki_dir, Path(work), out_dir, slug_lang)

shutil.copy(repo_root / "site" / "landing.html", out_dir / "index.html")
shutil.copy(repo_root / "images" / "materror.png", out_dir / "logo.png")
print("site built at", out_dir)


if __name__ == "__main__":
main()
95 changes: 95 additions & 0 deletions site/landing.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<!-- SPDX-FileCopyrightText: 2026 RAprogramm <andrey.rozanov.vl@gmail.com> -->
<!-- SPDX-License-Identifier: MIT -->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="masterror documentation — framework-agnostic application error types with typed codes, context, and transport mappings">
<title>masterror — Documentation</title>
<link rel="icon" type="image/png" href="logo.png">
<style>
:root {
--bg: #fdfdfd;
--fg: #1f2328;
--muted: #57606a;
--card-bg: #ffffff;
--card-border: #d0d7de;
--card-hover: #0969da;
--accent: #0969da;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #0d1117;
--fg: #e6edf3;
--muted: #8d96a0;
--card-bg: #161b22;
--card-border: #30363d;
--card-hover: #58a6ff;
--accent: #58a6ff;
}
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: var(--bg);
color: var(--fg);
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2rem 1rem;
text-align: center;
}
img.logo { width: 140px; height: 140px; }
h1 { margin-top: 1rem; font-size: 2.2rem; }
p.tagline { margin-top: .5rem; color: var(--muted); font-size: 1.1rem; font-style: italic; }
nav.languages {
margin-top: 2.5rem;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 1rem;
width: 100%;
max-width: 640px;
}
nav.languages a {
background: var(--card-bg);
border: 1px solid var(--card-border);
border-radius: 12px;
padding: 1.4rem 1rem;
text-decoration: none;
color: var(--fg);
transition: border-color .15s, transform .15s;
display: flex;
flex-direction: column;
gap: .4rem;
}
nav.languages a:hover {
border-color: var(--card-hover);
transform: translateY(-2px);
}
nav.languages .flag { font-size: 2rem; }
nav.languages .name { font-weight: 600; font-size: 1.05rem; }
nav.languages .label { color: var(--muted); font-size: .9rem; }
footer { margin-top: 3rem; color: var(--muted); font-size: .95rem; }
footer a { color: var(--accent); text-decoration: none; }
footer a:hover { text-decoration: underline; }
</style>
</head>
<body>
<img class="logo" src="logo.png" alt="masterror logo">
<h1>masterror</h1>
<p class="tagline">Framework-agnostic application error types</p>
<nav class="languages" aria-label="Documentation language">
<a href="en/"><span class="flag">🇬🇧</span><span class="name">English</span><span class="label">Documentation</span></a>
<a href="ru/" lang="ru"><span class="flag">🇷🇺</span><span class="name">Русский</span><span class="label">Документация</span></a>
<a href="ko/" lang="ko"><span class="flag">🇰🇷</span><span class="name">한국어</span><span class="label">문서</span></a>
</nav>
<footer>
<a href="https://docs.rs/masterror">API&nbsp;Docs</a> ·
<a href="https://crates.io/crates/masterror">crates.io</a> ·
<a href="https://github.com/RAprogramm/masterror">GitHub</a>
</footer>
</body>
</html>
Loading
Loading