From 60d8931743588289095709f34c34fc753ae2ed27 Mon Sep 17 00:00:00 2001 From: Makiras <22570332+Makiras@users.noreply.github.com> Date: Sat, 24 Oct 2026 09:18:52 +0800 Subject: [PATCH 1/2] Fix README publication links and publish GitHub Wiki documentation --- .github/workflows/docs.yml | 4 ++ README.cn.md | 18 ++++--- README.en.md | 18 ++++--- docs/en/development/documentation.md | 24 ++++++--- docs/en/guides/installation.md | 2 +- docs/en/index.md | 2 +- docs/zh/development/documentation.md | 24 ++++++--- docs/zh/guides/installation.md | 2 +- docs/zh/index.md | 2 +- pyproject.toml | 1 + scripts/docs.py | 29 +++++++--- tests/docs/test_publication_links.py | 81 ++++++++++++++++++++++++++++ 12 files changed, 167 insertions(+), 40 deletions(-) create mode 100644 tests/docs/test_publication_links.py diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 7adc817..4da0585 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -10,6 +10,7 @@ on: - "mkdocs.yml" - "requirements-docs.txt" - "scripts/docs.py" + - "tests/docs/**" - ".github/workflows/docs.yml" push: branches: [master, "docs/**"] @@ -20,6 +21,7 @@ on: - "mkdocs.yml" - "requirements-docs.txt" - "scripts/docs.py" + - "tests/docs/**" - ".github/workflows/docs.yml" workflow_dispatch: @@ -43,6 +45,8 @@ jobs: cache-dependency-path: requirements-docs.txt - name: Install documentation tools run: python -m pip install -r requirements-docs.txt + - name: Test publication link checks + run: python -m unittest discover -s tests/docs -v - name: Check translations, links and example syntax run: python scripts/docs.py check - name: Build documentation site diff --git a/README.cn.md b/README.cn.md index ed626fa..e1fb2a8 100644 --- a/README.cn.md +++ b/README.cn.md @@ -1,12 +1,14 @@ # xcomm -中文 | [English](README.en.md) +[![PyPI](https://img.shields.io/pypi/v/uc-xcomm)](https://pypi.org/project/uc-xcomm/) + +中文 | [English](https://github.com/XS-MLVP/xcomm/blob/master/README.en.md) xcomm 是硬件仿真的 C++20 组件库,提供四态信号、端口集合、时钟、表达式、时序事件和功能覆盖率。Python 发行包名为 `uc-xcomm`,导入名为 `xspcomm`,也支持 Java、Scala、Go、Lua 和 JavaScript。 ## 开始使用 -完成[安装](docs/zh/guides/installation.md)后,可以直接创建和读写宽信号: +完成[安装](https://github.com/XS-MLVP/xcomm/wiki/zh-guides-installation)后,可以直接创建和读写宽信号: ```python import xspcomm as xs @@ -16,14 +18,14 @@ signal.value = (1 << 96) + 3 print(hex(signal.value)) ``` -输出为 `0x1000000000000000000000003`。[入门教程](docs/zh/tutorials/first-simulation.md)继续演示时钟推进和事件等待。 +输出为 `0x1000000000000000000000003`。[入门教程](https://github.com/XS-MLVP/xcomm/wiki/zh-tutorials-first-simulation)继续演示时钟推进和事件等待。 ## 文档 -[完整中文文档](docs/zh/index.md) · [English documentation](docs/en/index.md) +[完整中文文档](https://github.com/XS-MLVP/xcomm/wiki/zh-Home) · [English documentation](https://github.com/XS-MLVP/xcomm/wiki/en-Home) -- 使用:[事件与覆盖率](docs/zh/guides/events-coverage.md)、[宽位表达式](docs/zh/guides/typed-expressions.md)。 -- 查询:[API 索引](docs/zh/reference/api-index.md)、[语言绑定](docs/zh/reference/bindings.md)、[接口迁移](docs/zh/reference/compatibility.md)、[故障排查](docs/zh/reference/troubleshooting.md)。 -- 开发:[架构](docs/zh/explanation/architecture.md)、[性能](docs/zh/explanation/performance.md)、[测试](docs/zh/development/contributing.md)、[文档与 Wiki](docs/zh/development/documentation.md)。 +- 使用:[事件与覆盖率](https://github.com/XS-MLVP/xcomm/wiki/zh-guides-events-coverage)、[宽位表达式](https://github.com/XS-MLVP/xcomm/wiki/zh-guides-typed-expressions)。 +- 查询:[API 索引](https://github.com/XS-MLVP/xcomm/wiki/zh-reference-api-index)、[语言绑定](https://github.com/XS-MLVP/xcomm/wiki/zh-reference-bindings)、[接口迁移](https://github.com/XS-MLVP/xcomm/wiki/zh-reference-compatibility)、[故障排查](https://github.com/XS-MLVP/xcomm/wiki/zh-reference-troubleshooting)。 +- 开发:[架构](https://github.com/XS-MLVP/xcomm/wiki/zh-explanation-architecture)、[性能](https://github.com/XS-MLVP/xcomm/wiki/zh-explanation-performance)、[测试](https://github.com/XS-MLVP/xcomm/wiki/zh-development-contributing)、[文档与 Wiki](https://github.com/XS-MLVP/xcomm/wiki/zh-development-documentation)。 -问题和建议请提交到 [GitHub Issues](https://github.com/XS-MLVP/xcomm/issues)。许可见 [LICENSE](LICENSE)。 +问题和建议请提交到 [GitHub Issues](https://github.com/XS-MLVP/xcomm/issues)。许可见 [LICENSE](https://github.com/XS-MLVP/xcomm/blob/master/LICENSE)。 diff --git a/README.en.md b/README.en.md index e6ddf9c..e22d09d 100644 --- a/README.en.md +++ b/README.en.md @@ -1,12 +1,14 @@ # xcomm -English | [中文](README.cn.md) +[![PyPI](https://img.shields.io/pypi/v/uc-xcomm)](https://pypi.org/project/uc-xcomm/) + +English | [中文](https://github.com/XS-MLVP/xcomm/blob/master/README.cn.md) xcomm is a C++20 library for hardware simulation, providing four-state signals, port collections, clocks, expressions, temporal events, and functional coverage. Install the Python distribution as `uc-xcomm` and import it as `xspcomm`; bindings are also available for Java, Scala, Go, Lua, and JavaScript. ## Get started -After [installation](docs/en/guides/installation.md), create and read a wide signal: +After [installation](https://github.com/XS-MLVP/xcomm/wiki/en-guides-installation), create and read a wide signal: ```python import xspcomm as xs @@ -16,14 +18,14 @@ signal.value = (1 << 96) + 3 print(hex(signal.value)) ``` -The output is `0x1000000000000000000000003`. Continue with the [tutorial](docs/en/tutorials/first-simulation.md) to advance a clock and wait for an event. +The output is `0x1000000000000000000000003`. Continue with the [tutorial](https://github.com/XS-MLVP/xcomm/wiki/en-tutorials-first-simulation) to advance a clock and wait for an event. ## Documentation -[English documentation](docs/en/index.md) · [完整中文文档](docs/zh/index.md) +[English documentation](https://github.com/XS-MLVP/xcomm/wiki/en-Home) · [完整中文文档](https://github.com/XS-MLVP/xcomm/wiki/zh-Home) -- Use the library: [events and coverage](docs/en/guides/events-coverage.md), [typed expressions](docs/en/guides/typed-expressions.md). -- Look up an interface: [API index](docs/en/reference/api-index.md), [language bindings](docs/en/reference/bindings.md), [migration](docs/en/reference/compatibility.md), [troubleshooting](docs/en/reference/troubleshooting.md). -- Develop the library: [architecture](docs/en/explanation/architecture.md), [performance](docs/en/explanation/performance.md), [tests](docs/en/development/contributing.md), [documentation and Wiki](docs/en/development/documentation.md). +- Use the library: [events and coverage](https://github.com/XS-MLVP/xcomm/wiki/en-guides-events-coverage), [typed expressions](https://github.com/XS-MLVP/xcomm/wiki/en-guides-typed-expressions). +- Look up an interface: [API index](https://github.com/XS-MLVP/xcomm/wiki/en-reference-api-index), [language bindings](https://github.com/XS-MLVP/xcomm/wiki/en-reference-bindings), [migration](https://github.com/XS-MLVP/xcomm/wiki/en-reference-compatibility), [troubleshooting](https://github.com/XS-MLVP/xcomm/wiki/en-reference-troubleshooting). +- Develop the library: [architecture](https://github.com/XS-MLVP/xcomm/wiki/en-explanation-architecture), [performance](https://github.com/XS-MLVP/xcomm/wiki/en-explanation-performance), [tests](https://github.com/XS-MLVP/xcomm/wiki/en-development-contributing), [documentation and Wiki](https://github.com/XS-MLVP/xcomm/wiki/en-development-documentation). -Report issues and suggestions through [GitHub Issues](https://github.com/XS-MLVP/xcomm/issues). See [LICENSE](LICENSE) for licensing. +Report issues and suggestions through [GitHub Issues](https://github.com/XS-MLVP/xcomm/issues). See [LICENSE](https://github.com/XS-MLVP/xcomm/blob/master/LICENSE) for licensing. diff --git a/docs/en/development/documentation.md b/docs/en/development/documentation.md index 43d878e..49481da 100644 --- a/docs/en/development/documentation.md +++ b/docs/en/development/documentation.md @@ -35,7 +35,7 @@ python3 -m venv /tmp/docs-venv Open `http://127.0.0.1:8000` to check language switching, navigation, full-text search, and code rendering. The static site is written to `build/docs-site`. -`check` validates translated page sets, navigation, links, anchors, and Python syntax. Mark complete executable blocks with `` or ``, then run: +`check` validates translated page sets, navigation, links, anchors, and Python syntax. README links must use absolute URLs so they also work on PyPI; links into this repository's Wiki must name an exported page. Mark complete executable blocks with `` or ``, then run: ```sh /tmp/docs-venv/bin/python scripts/docs.py test --python /path/to/runtime/bin/python @@ -43,14 +43,26 @@ Open `http://127.0.0.1:8000` to check language switching, navigation, full-text Run examples with a Python environment containing xspcomm. For C++ examples, pass SDK header and library directories with `--include` and `--lib`, and optionally choose a compiler with `--cxx`. -## Export a GitHub Wiki +## Publish the GitHub Wiki -The site and Wiki are generated from the same Markdown. Export into an empty directory: +The [GitHub Wiki](https://github.com/XS-MLVP/xcomm/wiki/Home) and the site are generated from the same Markdown. Export into an empty directory: ```sh -/tmp/docs-venv/bin/python scripts/docs.py wiki --output /tmp/xcomm-wiki +/tmp/docs-venv/bin/python scripts/docs.py wiki --output /tmp/xcomm-wiki-export ``` -The output includes bilingual pages, a home page, sidebar, footer, and file manifest. Links are converted to Wiki page names. After review, maintainers can sync these files to `xcomm.wiki.git`. See [GitHub's Wiki documentation](https://docs.github.com/en/communities/documenting-your-project-with-wikis/adding-or-editing-wiki-pages) for editing the separate repository. +The output includes bilingual pages, a home page, sidebar, footer, and file manifest. Page links become absolute GitHub Wiki URLs; source and image links point to the main repository. After reviewing the export, maintainers publish it to GitHub's separate Wiki repository: -Documentation CI runs for PRs targeting master, documentation changes pushed to master or `docs/**` branches, and manual dispatches. It checks translated pages, links, and example syntax, then builds the site and exports the Wiki as review artifacts. +```sh +git clone https://github.com/XS-MLVP/xcomm.wiki.git /tmp/xcomm-wiki-checkout +cp /tmp/xcomm-wiki-export/*.md /tmp/xcomm-wiki-export/manifest.json /tmp/xcomm-wiki-checkout/ +git -C /tmp/xcomm-wiki-checkout diff +git -C /tmp/xcomm-wiki-checkout status --short +git -C /tmp/xcomm-wiki-checkout add -- '*.md' manifest.json +git -C /tmp/xcomm-wiki-checkout commit -m "Sync documentation from xcomm" +git -C /tmp/xcomm-wiki-checkout push +``` + +Compare manifests when pages are renamed or removed, and remove only obsolete generated files from the Wiki checkout. Keep unrelated Wiki content. See [GitHub's Wiki documentation](https://docs.github.com/en/communities/documenting-your-project-with-wikis/adding-or-editing-wiki-pages) for editing the separate repository. + +Documentation CI runs for PRs targeting master, documentation changes pushed to master or `docs/**` branches, and manual dispatches. It checks translated pages, links, and example syntax, then builds the site and exports the Wiki as review artifacts. CI does not push to the Wiki; publishing requires the separate Git push above. diff --git a/docs/en/guides/installation.md b/docs/en/guides/installation.md index c85b656..19ee025 100644 --- a/docs/en/guides/installation.md +++ b/docs/en/guides/installation.md @@ -4,7 +4,7 @@ The Python distribution is `uc-xcomm`; import it as `xspcomm`. The release wheel ## Python wheel -After the first [PyPI release is configured](../development/releases.md#pypi-setup), install into the environment that will run the simulation: +Install from [PyPI](https://pypi.org/project/uc-xcomm/) into the environment that will run the simulation: ```sh python3.12 -m venv .venv diff --git a/docs/en/index.md b/docs/en/index.md index 1f6d275..7f8500c 100644 --- a/docs/en/index.md +++ b/docs/en/index.md @@ -1,6 +1,6 @@ # xcomm documentation -xcomm provides C++ components for hardware simulation: signal access, port collections, clock advancement, expression evaluation, temporal event matching, and functional coverage. The Python package is named `xspcomm`; bindings are also available for Java, Scala, Go, Lua, and JavaScript. +xcomm provides C++ components for hardware simulation: signal access, port collections, clock advancement, expression evaluation, temporal event matching, and functional coverage. The Python distribution is `uc-xcomm`, imported as `xspcomm`; bindings are also available for Java, Scala, Go, Lua, and JavaScript. Start with [installation](guides/installation.md) and [your first signal and event](tutorials/first-simulation.md). The tutorial uses a small script to introduce wide signals, clocks, and event waits. diff --git a/docs/zh/development/documentation.md b/docs/zh/development/documentation.md index 6e306cb..60ac3bc 100644 --- a/docs/zh/development/documentation.md +++ b/docs/zh/development/documentation.md @@ -35,7 +35,7 @@ python3 -m venv /tmp/docs-venv 浏览器打开 `http://127.0.0.1:8000`,检查语言切换、导航、全文搜索和代码显示。静态站点输出到 `build/docs-site`。 -`check` 校验中英文页面对应关系、导航、链接、锚点和 Python 语法。完整可执行代码块前添加 `` 或 ``,可以通过以下命令运行: +`check` 校验中英文页面对应关系、导航、链接、锚点和 Python 语法。README 链接必须使用绝对 URL,以便在 PyPI 上正常访问;指向本仓库 Wiki 的链接必须对应导出页面。完整可执行代码块前添加 `` 或 ``,可以通过以下命令运行: ```sh /tmp/docs-venv/bin/python scripts/docs.py test --python /path/to/runtime/bin/python @@ -43,14 +43,26 @@ python3 -m venv /tmp/docs-venv 使用已安装 xspcomm 的 Python 运行示例。C++ 示例通过 `--include` 和 `--lib` 指定 SDK 头文件与库目录,可用 `--cxx` 选择编译器。 -## 导出 GitHub Wiki +## 发布 GitHub Wiki -网站和 Wiki 都由同一份 Markdown 生成。在空目录导出: +[GitHub 自带的 Wiki](https://github.com/XS-MLVP/xcomm/wiki/Home) 和网站都由同一份 Markdown 生成。在空目录导出: ```sh -/tmp/docs-venv/bin/python scripts/docs.py wiki --output /tmp/xcomm-wiki +/tmp/docs-venv/bin/python scripts/docs.py wiki --output /tmp/xcomm-wiki-export ``` -输出包含双语页面、首页、侧栏、页脚和文件清单,页面链接已转换为 Wiki 名称。维护者审阅后,将这些文件同步到 `xcomm.wiki.git` 即可。[GitHub Wiki 文档](https://docs.github.com/en/communities/documenting-your-project-with-wikis/adding-or-editing-wiki-pages)介绍独立仓库的编辑方式。 +输出包含双语页面、首页、侧边栏、页脚和文件清单。页面链接转换为 GitHub Wiki 的绝对 URL,源码和图片链接指向主仓库。审阅导出内容后,维护者将其推送到 GitHub 独立的 Wiki 仓库: -文档 CI 在面向 master 的 PR、master 或 `docs/**` 分支上的文档改动,以及手动运行时执行。它检查双语页面、链接和示例语法,构建网站并导出 Wiki,结果作为 artifacts 提供评审。 +```sh +git clone https://github.com/XS-MLVP/xcomm.wiki.git /tmp/xcomm-wiki-checkout +cp /tmp/xcomm-wiki-export/*.md /tmp/xcomm-wiki-export/manifest.json /tmp/xcomm-wiki-checkout/ +git -C /tmp/xcomm-wiki-checkout diff +git -C /tmp/xcomm-wiki-checkout status --short +git -C /tmp/xcomm-wiki-checkout add -- '*.md' manifest.json +git -C /tmp/xcomm-wiki-checkout commit -m "Sync documentation from xcomm" +git -C /tmp/xcomm-wiki-checkout push +``` + +重命名或删除页面时,对比新旧清单,仅从 Wiki 工作区移除已失效的生成文件,保留无关内容。[GitHub Wiki 文档](https://docs.github.com/en/communities/documenting-your-project-with-wikis/adding-or-editing-wiki-pages)介绍独立仓库的编辑方式。 + +文档 CI 在面向 master 的 PR、master 或 `docs/**` 分支上的文档改动,以及手动运行时执行。它检查双语页面、链接和示例语法,构建网站并导出 Wiki,结果作为 artifacts 提供评审。CI 不会推送 Wiki,实际发布需单独执行上述 Git 推送。 diff --git a/docs/zh/guides/installation.md b/docs/zh/guides/installation.md index c6a0337..8180bee 100644 --- a/docs/zh/guides/installation.md +++ b/docs/zh/guides/installation.md @@ -4,7 +4,7 @@ Python 发行包名是 `uc-xcomm`,导入名是 `xspcomm`。预编译包覆盖 ## Python wheel -完成首次 [PyPI 发布配置](../development/releases.md#pypi) 后,在实际运行仿真的 Python 环境中安装: +在实际运行仿真的 Python 环境中,从 [PyPI](https://pypi.org/project/uc-xcomm/) 安装: ```sh python3.12 -m venv .venv diff --git a/docs/zh/index.md b/docs/zh/index.md index 7fa9c81..c73a41b 100644 --- a/docs/zh/index.md +++ b/docs/zh/index.md @@ -1,6 +1,6 @@ # xcomm 文档 -xcomm 提供硬件仿真中常用的 C++ 组件:信号读写、端口分组、时钟推进、表达式求值、时序事件匹配和功能覆盖率。Python 包名为 `xspcomm`,也提供 Java、Scala、Go、Lua 和 JavaScript 绑定。 +xcomm 提供硬件仿真中常用的 C++ 组件:信号读写、端口分组、时钟推进、表达式求值、时序事件匹配和功能覆盖率。Python 发行包名为 `uc-xcomm`,导入名为 `xspcomm`,也提供 Java、Scala、Go、Lua 和 JavaScript 绑定。 第一次使用,从[安装](guides/installation.md)和[第一个信号与事件](tutorials/first-simulation.md)开始。教程用一个小脚本演示宽信号、时钟和事件等待。 diff --git a/pyproject.toml b/pyproject.toml index c3c69f7..071992c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ classifiers = [ [project.urls] Homepage = "https://github.com/XS-MLVP/xcomm" +Documentation = "https://github.com/XS-MLVP/xcomm/wiki" Source = "https://github.com/XS-MLVP/xcomm" Issues = "https://github.com/XS-MLVP/xcomm/issues" diff --git a/scripts/docs.py b/scripts/docs.py index 620864c..d69f3c0 100644 --- a/scripts/docs.py +++ b/scripts/docs.py @@ -10,7 +10,7 @@ import subprocess import sys import tempfile -from urllib.parse import unquote, urlsplit +from urllib.parse import quote, unquote, urlsplit import markdown import yaml @@ -65,10 +65,16 @@ def check(): if (ROOT / 'test/README.md').exists(): entrypoints.append(ROOT / 'test/README.md') parsed = {p.resolve(): Links(p.read_text()) for p in [*DOCS.rglob('*.md'), *entrypoints]} + wiki_pages = {'Home', *(wiki_slug(p) for p in all_pages)} + wiki_prefix = CONFIG['repo_url'].rstrip('/') + '/wiki/' link_count = 0 for path, data in parsed.items(): for target in data.targets: url = urlsplit(target) + if path.parent == ROOT and path.name.startswith('README') and url.path and not (url.scheme or url.netloc): + errors.append(f'{path.name}: use an absolute URL for PyPI: {target}') + if target.startswith(wiki_prefix) and unquote(url.path.rsplit('/', 1)[-1]) not in wiki_pages: + errors.append(f'{path.relative_to(ROOT)}: unknown Wiki page {target}') if url.scheme or url.netloc: continue destination = (path.parent / unquote(url.path)).resolve() if url.path else path @@ -132,6 +138,10 @@ def wiki_slug(page): return language + '-' + ('Home' if relative == Path('index.md') else relative.with_suffix('').as_posix().replace('/', '-')) +def wiki_url(slug): + return CONFIG['repo_url'].rstrip('/') + '/wiki/' + quote(slug) + + def export_wiki(args): checked = check() output = Path(args.output).resolve() @@ -148,9 +158,10 @@ def replace_link(match): return match[0] target = (page.parent / unquote(url.path)).resolve() if target in slugs: - destination = slugs[target] + destination = wiki_url(slugs[target]) else: - destination = CONFIG['repo_url'].rstrip('/') + '/blob/master/' + target.relative_to(ROOT).as_posix() + route = '/raw/refs/heads/master/' if match[1].startswith('!') else '/blob/master/' + destination = CONFIG['repo_url'].rstrip('/') + route + quote(target.relative_to(ROOT).as_posix()) if url.fragment: destination += '#' + url.fragment return match[1] + destination + match[3] @@ -161,10 +172,10 @@ def replace_link(match): counterpart = DOCS / ('en' if relative.parts[0] == 'zh' else 'zh') / Path(*relative.parts[1:]) label = 'English' if relative.parts[0] == 'zh' else '中文' source = CONFIG['repo_url'].rstrip('/') + '/blob/master/' + page.relative_to(ROOT).as_posix() - content = f'[{label}]({slugs[counterpart.resolve()]}) · [源文档 / Source]({source})\n\n' + ''.join(chunks) + content = f'[{label}]({wiki_url(slugs[counterpart.resolve()])}) · [源文档 / Source]({source})\n\n' + ''.join(chunks) (output / (slugs[page.resolve()] + '.md')).write_text(content) - (output / 'Home.md').write_text('# ' + CONFIG['site_name'] + '\n\n[中文](zh-Home) | [English](en-Home)\n') - sidebar = ['[Home](Home)', ''] + (output / 'Home.md').write_text('# ' + CONFIG['site_name'] + f'\n\n[中文]({wiki_url("zh-Home")}) | [English]({wiki_url("en-Home")})\n') + sidebar = [f'[Home]({wiki_url("Home")})', ''] translations = CONFIG['plugins'][0]['i18n']['languages'][1].get('nav_translations', {}) for language, name in [('zh', '中文'), ('en', 'English')]: sidebar += ['## ' + name, ''] @@ -176,7 +187,7 @@ def add_navigation(entries, indent=''): sidebar.append(f'{indent}- **{label}**') add_navigation(value, indent + ' ') else: - sidebar.append(f'{indent}- [{label}]({slugs[(DOCS / language / value).resolve()]})') + sidebar.append(f'{indent}- [{label}]({wiki_url(slugs[(DOCS / language / value).resolve()])})') add_navigation(CONFIG['nav']) sidebar.append('') (output / '_Sidebar.md').write_text('\n'.join(sidebar)) @@ -185,7 +196,9 @@ def add_navigation(entries, indent=''): for page in output.glob('*.md'): for target in Links(page.read_text()).targets: url = urlsplit(target) - if not url.scheme and url.path and not (output / (url.path + '.md')).exists(): + if not (url.scheme or url.netloc) and url.path: + raise SystemExit(f'Relative exported Wiki link: {page.name}: {target}') + if target.startswith(wiki_url('')) and not (output / (unquote(url.path.rsplit('/', 1)[-1]) + '.md')).exists(): raise SystemExit(f'Broken exported Wiki link: {page.name}: {target}') manifest = {p.name: hashlib.sha256(p.read_bytes()).hexdigest() for p in sorted(output.glob('*.md'))} (output / 'manifest.json').write_text(json.dumps(manifest, indent=2) + '\n') diff --git a/tests/docs/test_publication_links.py b/tests/docs/test_publication_links.py new file mode 100644 index 0000000..66e0cb4 --- /dev/null +++ b/tests/docs/test_publication_links.py @@ -0,0 +1,81 @@ +"""Catch links that work in a checkout but break on PyPI or GitHub Wiki.""" +import contextlib +import importlib.util +import io +import json +from pathlib import Path +import tempfile +from types import SimpleNamespace +import unittest +from unittest.mock import patch + + +SPEC = importlib.util.spec_from_file_location( + "docs", Path(__file__).resolve().parents[2] / "scripts/docs.py" +) +docs = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(docs) + + +class PublicationLinks(unittest.TestCase): + def setUp(self): + directory = tempfile.TemporaryDirectory() + self.addCleanup(directory.cleanup) + self.root = Path(directory.name).resolve() + self.repo = "https://github.com/XS-MLVP/xcomm" + config = { + "site_name": "xcomm", "repo_url": self.repo, + "nav": [{"Home": "index.md"}, {"Guide": "guide.md"}], + "plugins": [{"i18n": {"languages": [{}, {}]}}], + } + self.enterContext(patch.multiple(docs, ROOT=self.root, DOCS=self.root / "docs", CONFIG=config)) + self.enterContext(contextlib.redirect_stdout(io.StringIO())) + (self.root / "LICENSE").write_text("License\n") + (self.root / "example image.png").write_bytes(b"image") + for language in ("en", "zh"): + folder = self.root / "docs" / language + folder.mkdir(parents=True) + (folder / "index.md").write_text( + "# Home\n\n[Guide](guide.md#install)\n\n" + "[License](../../LICENSE)\n\n" + "![Image](../../example%20image.png)\n\n" + "```text\n[Literal example](missing.md)\n```\n" + ) + (folder / "guide.md").write_text( + "# Guide\n\n## Install\n\nSteps.\n\n[Home](index.md)\n\nDone.\n" + ) + + def test_readme_rejects_existing_relative_file(self): + (self.root / "README.en.md").write_text("[License][license]\n\n[license]: LICENSE\n") + with self.assertRaisesRegex(SystemExit, "use an absolute URL for PyPI: LICENSE"): + docs.check() + + def test_readme_accepts_absolute_links_and_local_anchors(self): + (self.root / "README.en.md").write_text( + f"# xcomm\n\n[Docs]({self.repo}/wiki/en-Home)\n\n[Top](#xcomm)\n" + ) + docs.check() + + def test_readme_rejects_unknown_wiki_page(self): + (self.root / "README.en.md").write_text(f"[Docs]({self.repo}/wiki/missing-page)\n") + with self.assertRaisesRegex(SystemExit, "unknown Wiki page"): + docs.check() + + def test_wiki_exports_links_for_github_and_preserves_code(self): + output = self.root / "export" + docs.export_wiki(SimpleNamespace(output=output)) + home = (output / "en-Home.md").read_text() + self.assertIn(f"[Guide]({self.repo}/wiki/en-guide#install)", home) + self.assertIn(f"[中文]({self.repo}/wiki/zh-Home)", home) + self.assertIn(f"[License]({self.repo}/blob/master/LICENSE)", home) + self.assertIn(f"![Image]({self.repo}/raw/refs/heads/master/example%20image.png)", home) + self.assertIn("```text\n[Literal example](missing.md)\n```", home) + self.assertIn(f"[English]({self.repo}/wiki/en-Home)", (output / "Home.md").read_text()) + sidebar = (output / "_Sidebar.md").read_text() + self.assertIn(f"[Guide]({self.repo}/wiki/en-guide)", sidebar) + self.assertIn(f"[Guide]({self.repo}/wiki/zh-guide)", sidebar) + self.assertEqual(len(json.loads((output / "manifest.json").read_text())), 7) + + +if __name__ == "__main__": + unittest.main() From e36ed6b00e09dc68dc7e58f7cc2279ba5d34b0e2 Mon Sep 17 00:00:00 2001 From: Makiras <22570332+Makiras@users.noreply.github.com> Date: Sat, 24 Oct 2026 09:42:00 +0800 Subject: [PATCH 2/2] Unify README badges and validate documentation links --- README.cn.md | 7 ++++++- README.en.md | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/README.cn.md b/README.cn.md index e1fb2a8..17317e2 100644 --- a/README.cn.md +++ b/README.cn.md @@ -1,6 +1,11 @@ # xcomm -[![PyPI](https://img.shields.io/pypi/v/uc-xcomm)](https://pypi.org/project/uc-xcomm/) +[![PyPI](https://img.shields.io/pypi/v/uc-xcomm?style=flat-square&logo=pypi&logoColor=white)](https://pypi.org/project/uc-xcomm/) +[![Python](https://img.shields.io/pypi/pyversions/uc-xcomm?style=flat-square&logo=python&logoColor=white)](https://github.com/XS-MLVP/xcomm/wiki/zh-guides-installation) +[![CI](https://img.shields.io/github/actions/workflow/status/XS-MLVP/xcomm/python-wheel.yml?branch=master&style=flat-square&label=CI&logo=github)](https://github.com/XS-MLVP/xcomm/actions/workflows/python-wheel.yml?query=branch%3Amaster) +[![License](https://img.shields.io/badge/license-MulanPSL--2.0-blue?style=flat-square)](https://github.com/XS-MLVP/xcomm/blob/master/LICENSE) +[![Wiki](https://img.shields.io/badge/docs-Wiki-0A7EA4?style=flat-square)](https://github.com/XS-MLVP/xcomm/wiki/zh-Home) +[![GitHub Stars](https://img.shields.io/github/stars/XS-MLVP/xcomm?style=social)](https://github.com/XS-MLVP/xcomm) 中文 | [English](https://github.com/XS-MLVP/xcomm/blob/master/README.en.md) diff --git a/README.en.md b/README.en.md index e22d09d..558d074 100644 --- a/README.en.md +++ b/README.en.md @@ -1,6 +1,11 @@ # xcomm -[![PyPI](https://img.shields.io/pypi/v/uc-xcomm)](https://pypi.org/project/uc-xcomm/) +[![PyPI](https://img.shields.io/pypi/v/uc-xcomm?style=flat-square&logo=pypi&logoColor=white)](https://pypi.org/project/uc-xcomm/) +[![Python](https://img.shields.io/pypi/pyversions/uc-xcomm?style=flat-square&logo=python&logoColor=white)](https://github.com/XS-MLVP/xcomm/wiki/en-guides-installation) +[![CI](https://img.shields.io/github/actions/workflow/status/XS-MLVP/xcomm/python-wheel.yml?branch=master&style=flat-square&label=CI&logo=github)](https://github.com/XS-MLVP/xcomm/actions/workflows/python-wheel.yml?query=branch%3Amaster) +[![License](https://img.shields.io/badge/license-MulanPSL--2.0-blue?style=flat-square)](https://github.com/XS-MLVP/xcomm/blob/master/LICENSE) +[![Wiki](https://img.shields.io/badge/docs-Wiki-0A7EA4?style=flat-square)](https://github.com/XS-MLVP/xcomm/wiki/en-Home) +[![GitHub Stars](https://img.shields.io/github/stars/XS-MLVP/xcomm?style=social)](https://github.com/XS-MLVP/xcomm) English | [中文](https://github.com/XS-MLVP/xcomm/blob/master/README.cn.md)