fix(export_docs): Gitea wiki link targets and INDEX.md pages
Strip .md from internal links, merge folder INDEX bodies, rewrite out-of-wiki repo paths to Gitea src URLs. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,6 +2,14 @@
|
||||
|
||||
All notable changes to **export_1c_help** are documented in this file.
|
||||
|
||||
## [0.3.11] - 2026-07-29
|
||||
|
||||
### Fixed
|
||||
|
||||
- Internal wiki links omit the `.md` suffix in Markdown targets (Gitea resolves `/wiki/page`, not `/wiki/page.md`).
|
||||
- Folder `INDEX.md`: merge source body into auto-generated index; do not list INDEX as a self-link under «Статьи».
|
||||
- Relative links to files in the git repo but outside the wiki export (e.g. `ws-rhana/...`) rewrite to Gitea `src/branch/...` URLs.
|
||||
|
||||
## [0.3.10] - 2026-07-29
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -29,6 +29,30 @@ _EXPORT_DOCS_MARKER_END = "<!-- export_docs:end -->"
|
||||
_RESERVED_ROOT_PAGES = {"Home.md", "History.md", "_Sidebar.md"}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RepoLinkContext:
|
||||
"""Rewrite links to repo files (outside wiki export) as Gitea src/ URLs."""
|
||||
|
||||
root: Path
|
||||
web_base: str
|
||||
branch: str = "main"
|
||||
|
||||
|
||||
def _git_web_base(remote_url: str) -> str:
|
||||
u = remote_url.strip().rstrip("/")
|
||||
if u.lower().endswith(".git"):
|
||||
return u[:-4]
|
||||
return u
|
||||
|
||||
|
||||
def _repo_src_url(ctx: RepoLinkContext, abs_path: Path) -> str | None:
|
||||
try:
|
||||
rel = abs_path.resolve().relative_to(ctx.root.resolve())
|
||||
except ValueError:
|
||||
return None
|
||||
return f"{ctx.web_base.rstrip('/')}/src/branch/{ctx.branch}/{rel.as_posix()}"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DocItem:
|
||||
src: Path
|
||||
@@ -135,6 +159,7 @@ def _rewrite_links(
|
||||
src_to_md_dst: dict[Path, Path],
|
||||
map_src_abs_to_dst_rel,
|
||||
attachments: set[Path],
|
||||
repo: RepoLinkContext | None = None,
|
||||
) -> str:
|
||||
def repl(m: re.Match[str]) -> str:
|
||||
left, raw_target, right = m.group(1), m.group(2), m.group(3)
|
||||
@@ -162,6 +187,10 @@ def _rewrite_links(
|
||||
else:
|
||||
if not cand.is_file():
|
||||
return m.group(0)
|
||||
if repo is not None:
|
||||
src_url = _repo_src_url(repo, cand)
|
||||
if src_url:
|
||||
return f"{left}{src_url}{anchor}{tail}{right}"
|
||||
dst_target_rel = map_src_abs_to_dst_rel(cand)
|
||||
if dst_target_rel is None:
|
||||
return m.group(0)
|
||||
@@ -206,6 +235,10 @@ def _rewrite_links(
|
||||
else:
|
||||
if not cand.is_file():
|
||||
return m.group(0)
|
||||
if repo is not None:
|
||||
src_url = _repo_src_url(repo, cand)
|
||||
if src_url:
|
||||
return f"{prefix}{src_url}{anchor}{suffix}"
|
||||
dst_target_rel = map_src_abs_to_dst_rel(cand)
|
||||
if dst_target_rel is None:
|
||||
return m.group(0)
|
||||
@@ -245,8 +278,19 @@ def _target_rel_for(rel: Path, *, flat_root: bool) -> Path:
|
||||
|
||||
|
||||
def _encode_md_target(target: str) -> str:
|
||||
# Keep slash + fragment markers, encode spaces/parentheses/unicode safely.
|
||||
return quote(target, safe="/#._-~")
|
||||
# Gitea wiki page URLs omit the `.md` suffix (file `foo.md` → page `/wiki/foo`).
|
||||
t = target.replace("\\", "/")
|
||||
if t.lower().endswith(".md"):
|
||||
t = t[:-3]
|
||||
return quote(t, safe="/#._-~")
|
||||
|
||||
|
||||
def _is_folder_index_src(src: Path) -> bool:
|
||||
return src.name.casefold() == "index.md"
|
||||
|
||||
|
||||
def _strip_leading_h1(text: str) -> str:
|
||||
return re.sub(r"^\s*#\s+.+?\n+", "", text, count=1, flags=re.MULTILINE).lstrip()
|
||||
|
||||
|
||||
def _folder_label(folder_rel: Path, folder_clean: str) -> str:
|
||||
@@ -415,6 +459,7 @@ def build_docs_output(
|
||||
folder: str,
|
||||
flat_root: bool = True,
|
||||
clean: bool = True,
|
||||
repo: RepoLinkContext | None = None,
|
||||
) -> tuple[DocsStats, str]:
|
||||
out_dir = out_dir.resolve()
|
||||
if clean and out_dir.exists():
|
||||
@@ -471,10 +516,17 @@ def build_docs_output(
|
||||
attachments: set[Path] = set()
|
||||
managed_rels: set[Path] = set(src_to_md_dst.values())
|
||||
folder_docs: dict[Path, list[tuple[str, Path]]] = {}
|
||||
index_body_by_folder: dict[Path, str] = {}
|
||||
index_src_by_folder: dict[Path, Path] = {}
|
||||
for item in items_ok:
|
||||
raw = item.src.read_text(encoding="utf-8-sig", errors="ignore")
|
||||
title_by_src[item.src.resolve()] = _extract_doc_title(raw, item.src.stem)
|
||||
dst_rel = src_to_md_dst[item.src.resolve()]
|
||||
src_folder = item.dst_rel.parent
|
||||
if _is_folder_index_src(item.src):
|
||||
index_body_by_folder[src_folder] = raw
|
||||
index_src_by_folder[src_folder] = item.src
|
||||
continue
|
||||
rendered = _rewrite_links(
|
||||
raw,
|
||||
src=item.src,
|
||||
@@ -482,13 +534,12 @@ def build_docs_output(
|
||||
src_to_md_dst=src_to_md_dst,
|
||||
map_src_abs_to_dst_rel=map_src_abs_to_dst_rel,
|
||||
attachments=attachments,
|
||||
repo=repo,
|
||||
)
|
||||
src_folder = item.dst_rel.parent
|
||||
folder_index_rel = _target_rel_for(
|
||||
(src_folder / "INDEX.md") if src_folder != Path(".") else Path("INDEX.md"),
|
||||
flat_root=flat_root,
|
||||
)
|
||||
folder_title = _folder_label(src_folder, folder_clean)
|
||||
short = _folder_short_name(src_folder, folder_clean)
|
||||
nav = "\n".join(
|
||||
[
|
||||
@@ -514,7 +565,10 @@ def build_docs_output(
|
||||
copy_file(abs_src, out_dir / dst_rel)
|
||||
|
||||
# Build per-folder index pages with links to articles and child folders.
|
||||
all_folders = sorted(folder_docs.keys(), key=lambda p: p.as_posix())
|
||||
all_folders = sorted(
|
||||
set(folder_docs.keys()) | set(index_body_by_folder.keys()),
|
||||
key=lambda p: p.as_posix(),
|
||||
)
|
||||
child_folders: dict[Path, set[Path]] = {}
|
||||
folder_set = set(all_folders)
|
||||
for f in all_folders:
|
||||
@@ -562,15 +616,34 @@ def build_docs_output(
|
||||
lines.append("")
|
||||
|
||||
docs = sorted(folder_docs.get(f, []), key=lambda x: x[0].casefold())
|
||||
idx_target = index_target_by_folder[f]
|
||||
if docs:
|
||||
lines.append("## Статьи")
|
||||
lines.append("")
|
||||
for title, target_rel in docs:
|
||||
if target_rel == idx_target:
|
||||
continue
|
||||
rel_link = _rel_link(index_target_by_folder[f], target_rel)
|
||||
lines.append(f"- [{title}]({rel_link})")
|
||||
lines.append("")
|
||||
|
||||
idx_target = index_target_by_folder[f]
|
||||
index_raw = index_body_by_folder.get(f)
|
||||
if index_raw:
|
||||
idx_dst = index_target_by_folder[f]
|
||||
index_src = index_src_by_folder.get(f, Path("INDEX.md"))
|
||||
index_rendered = _rewrite_links(
|
||||
_strip_leading_h1(index_raw),
|
||||
src=index_src,
|
||||
dst_rel=idx_dst,
|
||||
src_to_md_dst=src_to_md_dst,
|
||||
map_src_abs_to_dst_rel=map_src_abs_to_dst_rel,
|
||||
attachments=attachments,
|
||||
repo=repo,
|
||||
)
|
||||
if index_rendered.strip():
|
||||
lines.append(index_rendered.rstrip())
|
||||
lines.append("")
|
||||
|
||||
write_text(out_dir / idx_target, "\n".join(lines).rstrip() + "\n")
|
||||
managed_rels.add(idx_target)
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ if str(TOOL_ROOT) not in sys.path:
|
||||
|
||||
from export1c_help import __status__, __version__ # noqa: E402
|
||||
from export1c_help.docs_export import ( # noqa: E402
|
||||
RepoLinkContext,
|
||||
_git_web_base,
|
||||
build_docs_output,
|
||||
clean_docs_wiki,
|
||||
collect_docs,
|
||||
@@ -119,6 +121,15 @@ def cmd_push(args: argparse.Namespace) -> int:
|
||||
src = top
|
||||
slug = re.sub(r"[^\w.\-]+", "-", top.name, flags=re.UNICODE).strip("-") or "repo"
|
||||
folder = validate_folder(args.folder)
|
||||
repo_remote = git_remote_url(src if src_input is None else git_toplevel(src) or src, remote=args.remote)
|
||||
repo_root = git_toplevel(src) or src
|
||||
repo_ctx: RepoLinkContext | None = None
|
||||
if repo_remote:
|
||||
repo_ctx = RepoLinkContext(
|
||||
root=repo_root.resolve(),
|
||||
web_base=_git_web_base(repo_remote),
|
||||
branch=args.branch,
|
||||
)
|
||||
except (ResolveError, ValueError) as exc:
|
||||
print(f"error: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
@@ -150,6 +161,7 @@ def cmd_push(args: argparse.Namespace) -> int:
|
||||
folder=folder,
|
||||
flat_root=not args.no_flat_root,
|
||||
clean=True,
|
||||
repo=repo_ctx,
|
||||
)
|
||||
print(
|
||||
f"collect: md={build_stats.markdown_files}, attachments={build_stats.attachment_files}, "
|
||||
|
||||
Reference in New Issue
Block a user