1 Commits
0.0.7 ... main

Author SHA1 Message Date
09118115a4 fix(mkdocs): rebase cross-kind wiki links under directory URLs
All checks were successful
continuous-integration/drone/tag Build is passing
Wiki pages linking to a sibling kind (for example ../lib/...) were emitted verbatim by MkDocs and, with use_directory_urls, resolved into a non-existent <kind>/<kind>/ path. Add a docforge-linkfix plugin (doc-forge 0.0.8) that rebases those links to the repository root.
2026-10-17 17:22:23 +05:30
7 changed files with 167 additions and 3 deletions

View File

@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [0.0.8] - 2026-10-17
### Added
- `docforge-linkfix` MkDocs plugin (registered via the `mkdocs.plugins`
entry point) that rebases cross-kind relative links — for example
`../lib/...` written in a wiki page — to the repository root so they resolve
to `/<repo>/<kind>/` under directory URLs.
### Changed
- The wiki template (`mkdocs.wiki.yml`) now enables `docforge-linkfix`;
regenerating a wiki config (or passing `--refresh`) propagates the fix.
### Fixed
- Cross-kind links (`../lib/`, `../api/`, `../wiki/`) in generated wikis no
longer resolve into a non-existent `<kind>/<kind>/` path.
## [0.0.7] - 2026-10-17 ## [0.0.7] - 2026-10-17
### Added ### Added
@@ -43,5 +59,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `import docforge` no longer requires the optional `mcp` dependency; the - `import docforge` no longer requires the optional `mcp` dependency; the
MCP server imports `mcp` lazily, matching the MkDocs backend. MCP server imports `mcp` lazily, matching the MkDocs backend.
[Unreleased]: https://git.aetoskia.com/aetos/doc-forge/compare/0.0.7...HEAD [Unreleased]: https://git.aetoskia.com/aetos/doc-forge/compare/0.0.8...HEAD
[0.0.8]: https://git.aetoskia.com/aetos/doc-forge/compare/0.0.7...0.0.8
[0.0.7]: https://git.aetoskia.com/aetos/doc-forge/compare/0.0.6...0.0.7 [0.0.7]: https://git.aetoskia.com/aetos/doc-forge/compare/0.0.6...0.0.7

72
docforge/mkdocs_links.py Normal file
View File

@@ -0,0 +1,72 @@
"""
# Summary
MkDocs plugin that fixes cross-kind relative links in generated wikis.
A repository served under ``/<repo>/<kind>/`` (for example a wiki at
``/myrepo/wiki/`` and a library reference at ``/myrepo/lib/``) is built as
two independent MkDocs sites. Authors naturally write links between them
using source-relative paths such as ``../lib/01_build_recipe/``.
MkDocs only rewrites links whose target lives inside ``docs_dir``; anything
pointing at a sibling site is emitted verbatim. When ``use_directory_urls``
is enabled (the default) the rendered page sits one or more directory levels
deeper than its source, so ``../lib/`` resolves to ``/<repo>/wiki/lib/`` and
breaks.
`docforge-linkfix` rewrites such links at render time, prefixing them with
enough ``../`` segments to climb back to the repository root so they resolve
to ``/<repo>/<kind>/`` regardless of how deeply the page is nested.
"""
from __future__ import annotations
import re
from typing import TYPE_CHECKING
from mkdocs.plugins import BasePlugin
if TYPE_CHECKING:
from mkdocs.config.defaults import MkDocsConfig
from mkdocs.structure.files import Files
from mkdocs.structure.pages import Page
__all__ = ["LinkFixPlugin"]
_CROSS_KIND_LINK = re.compile(r"\]\((?:\.\./)+(lib|api|wiki)/")
"""Matches a Markdown link whose target climbs out of the current doc kind."""
class LinkFixPlugin(BasePlugin): # type: ignore[no-untyped-call, type-arg]
"""
Rewrite cross-kind relative links so they survive directory URLs.
"""
def on_page_markdown(
self,
markdown: str,
/,
*,
page: Page,
config: MkDocsConfig,
files: Files,
) -> str:
"""
Prefix cross-kind links with the depth required to reach the repo root.
Args:
markdown (str): The raw Markdown source of the current page.
page (Page): The page being rendered; its destination URI is used
to determine how many directory levels the output sits below
the site root.
config (MkDocsConfig): The active MkDocs configuration.
files (Files): The collection of files in the site.
Returns:
str: The Markdown with cross-kind links rebased to the repo root.
"""
depth = page.file.dest_uri.count("/")
prefix = "../" * (depth + 1)
return _CROSS_KIND_LINK.sub(
lambda match: f"]({prefix}{match.group(1)}/", markdown
)

View File

@@ -22,4 +22,5 @@ theme:
logo: material/database logo: material/database
repo: fontawesome/brands/github repo: fontawesome/brands/github
plugins: plugins:
- search - search
- docforge-linkfix

View File

@@ -56,6 +56,7 @@ extra_css:
- https://unpkg.com/dracula-prism/dist/css/dracula-prism.css - https://unpkg.com/dracula-prism/dist/css/dracula-prism.css
plugins: plugins:
- search - search
- docforge-linkfix
site_name: doc-forge site_name: doc-forge
docs_dir: wiki docs_dir: wiki
site_dir: ../site/wiki site_dir: ../site/wiki

View File

@@ -55,5 +55,20 @@ Commit `docs/mkdocs.{kind}.yml` so served docs are reproducible from source.
--- ---
## 🔗 Cross-kind links
Kinds are separate MkDocs sites served side by side under `/<repo>/<kind>/`,
so a wiki page may link to the library reference with a source-relative path
such as `../lib/13_library_registry/`. MkDocs only rewrites links that point
inside `docs_dir`; anything reaching a sibling kind is emitted verbatim and,
under `use_directory_urls`, resolves into `<kind>/<kind>/` and breaks.
The `docforge-linkfix` plugin (part of doc-forge, enabled in the wiki
template) fixes this at render time: it rewrites `../lib/`, `../api/`, and
`../wiki/` links to climb to the repository root, so they resolve correctly
however deeply the page is nested.
---
## Related ## Related
- [04 – MCP Guide](04_mcp_guide.md) - [04 – MCP Guide](04_mcp_guide.md)

View File

@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "doc-forge" name = "doc-forge"
version = "0.0.7" version = "0.0.8"
description = "A renderer-agnostic Python documentation compiler" description = "A renderer-agnostic Python documentation compiler"
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"
@@ -56,6 +56,9 @@ dependencies = [
[project.scripts] [project.scripts]
doc-forge = "docforge.cli.main:main" doc-forge = "docforge.cli.main:main"
[project.entry-points."mkdocs.plugins"]
docforge-linkfix = "docforge.mkdocs_links:LinkFixPlugin"
[project.optional-dependencies] [project.optional-dependencies]
mkdocs = [ mkdocs = [
"mkdocs==1.6.1", "mkdocs==1.6.1",

View File

@@ -0,0 +1,55 @@
from types import SimpleNamespace
from docforge.mkdocs_links import LinkFixPlugin
def _page(dest_uri: str):
return SimpleNamespace(file=SimpleNamespace(dest_uri=dest_uri))
def _fix(markdown: str, dest_uri: str) -> str:
return LinkFixPlugin().on_page_markdown(
markdown,
page=_page(dest_uri),
config=SimpleNamespace(),
files=SimpleNamespace(),
)
def test_root_page_keeps_single_parent():
src = "[lib](../lib/pkg/index.md)"
assert _fix(src, "index.html") == src
def test_nested_page_adds_directory_depth():
assert _fix("[lib](../lib/pkg/index.md)", "01_overview/index.html") == (
"[lib](../../lib/pkg/index.md)"
)
def test_deeply_nested_page_adds_two_levels():
src = "[lib](../lib/pkg/index.md)"
got = _fix(src, "04_development/02_releasing/index.html")
assert got == "[lib](../../../lib/pkg/index.md)"
def test_multiple_cross_kind_links_are_rebased():
src = "See [a](../lib/a) and [b](../api/b)."
got = _fix(src, "01_overview/index.html")
assert got == "See [a](../../lib/a) and [b](../../api/b)."
def test_wiki_kind_is_rebased():
assert _fix("[w](../wiki/index.md)", "01_overview/index.html") == (
"[w](../../wiki/index.md)"
)
def test_relative_within_kind_untouched():
src = "[next](02_install.md) and [prev](../index.md)"
assert _fix(src, "01_overview/index.html") == src
def test_absolute_and_url_links_untouched():
src = "[site](https://example.com/lib/x) and [root](/lib/x) and [r](lib/x)"
assert _fix(src, "a/index.html") == src