fix(export_docs): stage Home/Sidebar and shorten sidebar folder labels
Gitea wiki clones may mark Home.md assume-unchanged; clear index flags before add and commit only staged changes. Sidebar shows last path segment without «Папка». Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+6
-1
@@ -2,7 +2,12 @@
|
||||
|
||||
All notable changes to **export_1c_help** are documented in this file.
|
||||
|
||||
## [0.3.8] - 2026-07-29
|
||||
## [0.3.10] - 2026-07-29
|
||||
|
||||
### Fixed
|
||||
|
||||
- `export_docs` wiki push: clear Gitea `assume-unchanged` on `Home.md` / `_Sidebar.md` before `git add`, and commit only when the index has staged changes (fixes «no changes added to commit» when nav pages were modified).
|
||||
- `_Sidebar` folder links: short folder name only (`📁 meta`), without «Папка» and without full `docs/...` path.
|
||||
|
||||
## [0.3.9] - 2026-07-29
|
||||
|
||||
|
||||
+167
-25
@@ -249,6 +249,23 @@ def _encode_md_target(target: str) -> str:
|
||||
return quote(target, safe="/#._-~")
|
||||
|
||||
|
||||
def _folder_label(folder_rel: Path, folder_clean: str) -> str:
|
||||
if folder_rel == Path("."):
|
||||
return folder_clean or "root"
|
||||
return folder_rel.as_posix()
|
||||
|
||||
|
||||
def _folder_short_name(folder_rel: Path, folder_clean: str) -> str:
|
||||
if folder_rel == Path("."):
|
||||
return folder_clean or "root"
|
||||
return folder_rel.name or folder_rel.as_posix()
|
||||
|
||||
|
||||
def _rel_link(from_rel: Path, to_rel: Path) -> str:
|
||||
rel = os.path.relpath(to_rel.as_posix(), start=from_rel.parent.as_posix())
|
||||
return _encode_md_target(rel.replace("\\", "/"))
|
||||
|
||||
|
||||
def _merge_marker_block(existing: str, block: str) -> str:
|
||||
import re
|
||||
|
||||
@@ -281,6 +298,53 @@ def _docs_index_rel(folder: str, managed_files: set[str]) -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
def _sidebar_folder_links(folder: str, managed_files: set[str]) -> list[tuple[int, str, str]]:
|
||||
"""
|
||||
Return (depth, label, target) for folder index pages in wiki.
|
||||
|
||||
Supports both:
|
||||
- nested mode: docs/sub/INDEX.md
|
||||
- flat mode: docs__sub__INDEX.md
|
||||
"""
|
||||
folder_clean = folder.strip().strip("/").strip("\\")
|
||||
rows: list[tuple[int, str, str]] = []
|
||||
|
||||
if not folder_clean:
|
||||
return rows
|
||||
|
||||
# nested mode
|
||||
for rel in managed_files:
|
||||
if not rel.endswith("/INDEX.md"):
|
||||
continue
|
||||
if not rel.startswith(folder_clean + "/"):
|
||||
continue
|
||||
folder_rel = rel[: -len("/INDEX.md")]
|
||||
depth = max(folder_rel.count("/") - folder_clean.count("/"), 0)
|
||||
rows.append((depth, folder_rel, rel))
|
||||
|
||||
# flat mode
|
||||
root_flat = f"{folder_clean}__INDEX.md"
|
||||
if root_flat in managed_files:
|
||||
rows.append((0, folder_clean, root_flat))
|
||||
prefix = f"{folder_clean}__"
|
||||
suffix = "__INDEX.md"
|
||||
for rel in managed_files:
|
||||
if not rel.startswith(prefix) or not rel.endswith(suffix) or rel == root_flat:
|
||||
continue
|
||||
mid = rel[len(prefix) : -len(suffix)]
|
||||
if not mid:
|
||||
continue
|
||||
folder_rel = folder_clean + "/" + mid.replace("__", "/")
|
||||
depth = max(folder_rel.count("/") - folder_clean.count("/"), 0)
|
||||
rows.append((depth, folder_rel, rel))
|
||||
|
||||
# dedupe by target
|
||||
uniq: dict[str, tuple[int, str, str]] = {}
|
||||
for row in rows:
|
||||
uniq[row[2]] = row
|
||||
return sorted(uniq.values(), key=lambda x: (x[0], x[1].casefold()))
|
||||
|
||||
|
||||
def ensure_root_navigation(
|
||||
*,
|
||||
work_dir: Path,
|
||||
@@ -314,11 +378,22 @@ def ensure_root_navigation(
|
||||
f"- [{display}]({_encode_md_target(link_target)})",
|
||||
]
|
||||
)
|
||||
tree = _sidebar_folder_links(folder, managed_files)
|
||||
tree_lines: list[str] = []
|
||||
for depth, label, target in tree:
|
||||
indent = " " * depth
|
||||
short = Path(label).name or label
|
||||
tree_lines.append(
|
||||
f"{indent}- [📁 {short}]({_encode_md_target(target)})"
|
||||
)
|
||||
if not tree_lines:
|
||||
root_short = Path(link_target).name or display
|
||||
tree_lines = [f"- [📁 {root_short}]({_encode_md_target(link_target)})"]
|
||||
sidebar_block = "\n".join(
|
||||
[
|
||||
"### Документация (docs)",
|
||||
"",
|
||||
f"- [{display}]({_encode_md_target(link_target)})",
|
||||
*tree_lines,
|
||||
"",
|
||||
]
|
||||
)
|
||||
@@ -408,8 +483,23 @@ def build_docs_output(
|
||||
map_src_abs_to_dst_rel=map_src_abs_to_dst_rel,
|
||||
attachments=attachments,
|
||||
)
|
||||
write_text(out_dir / dst_rel, rendered)
|
||||
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(
|
||||
[
|
||||
f"## 📁 {short}",
|
||||
"",
|
||||
f"[⤴️ Вернуться в {short}]({_rel_link(dst_rel, folder_index_rel)})",
|
||||
"",
|
||||
]
|
||||
)
|
||||
rendered = nav + rendered.lstrip()
|
||||
write_text(out_dir / dst_rel, rendered)
|
||||
folder_docs.setdefault(src_folder, []).append(
|
||||
(title_by_src[item.src.resolve()], dst_rel)
|
||||
)
|
||||
@@ -443,21 +533,22 @@ def build_docs_output(
|
||||
|
||||
for f in all_folders:
|
||||
lines: list[str] = []
|
||||
heading = f.as_posix() if f != Path(".") else (folder_clean or "docs")
|
||||
heading = _folder_label(f, folder_clean)
|
||||
lines.append(f"# Индекс: {heading}")
|
||||
lines.append("")
|
||||
|
||||
docs = sorted(folder_docs.get(f, []), key=lambda x: x[0].casefold())
|
||||
if docs:
|
||||
lines.append("## Статьи")
|
||||
lines.append("")
|
||||
for title, target_rel in docs:
|
||||
rel_link = os.path.relpath(
|
||||
target_rel.as_posix(),
|
||||
start=index_target_by_folder[f].parent.as_posix(),
|
||||
).replace("\\", "/")
|
||||
lines.append(f"- [{title}]({_encode_md_target(rel_link)})")
|
||||
lines.append("")
|
||||
if f != Path("."):
|
||||
parent = f.parent if f.parent != Path(".") else Path(".")
|
||||
parent_label = _folder_label(parent, folder_clean)
|
||||
parent_short = _folder_short_name(parent, folder_clean)
|
||||
parent_target = index_target_by_folder.get(parent)
|
||||
if parent_target is not None:
|
||||
lines.append(f"## 📁 {parent_short}")
|
||||
lines.append("")
|
||||
lines.append(
|
||||
f"[⤴️ Вернуться в {parent_short}]({_rel_link(index_target_by_folder[f], parent_target)})"
|
||||
)
|
||||
lines.append("")
|
||||
|
||||
children = sorted(child_folders.get(f, set()), key=lambda p: p.as_posix().casefold())
|
||||
if children:
|
||||
@@ -465,11 +556,18 @@ def build_docs_output(
|
||||
lines.append("")
|
||||
for ch in children:
|
||||
target_rel = index_target_by_folder[ch]
|
||||
rel_link = os.path.relpath(
|
||||
target_rel.as_posix(),
|
||||
start=index_target_by_folder[f].parent.as_posix(),
|
||||
).replace("\\", "/")
|
||||
lines.append(f"- [{ch.as_posix()}]({_encode_md_target(rel_link)})")
|
||||
ch_label = _folder_short_name(ch, folder_clean)
|
||||
rel_link = _rel_link(index_target_by_folder[f], target_rel)
|
||||
lines.append(f"- [📁 {ch_label}]({rel_link})")
|
||||
lines.append("")
|
||||
|
||||
docs = sorted(folder_docs.get(f, []), key=lambda x: x[0].casefold())
|
||||
if docs:
|
||||
lines.append("## Статьи")
|
||||
lines.append("")
|
||||
for title, target_rel in docs:
|
||||
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]
|
||||
@@ -511,6 +609,32 @@ def _run_git(cmd: list[str], *, cwd: Path) -> str:
|
||||
return proc.stdout
|
||||
|
||||
|
||||
def _wiki_clear_index_flags(work_dir: Path, *names: str) -> None:
|
||||
"""Gitea may mark Home/_Sidebar assume-unchanged; git add then skips them."""
|
||||
for name in names:
|
||||
if not (work_dir / name).is_file():
|
||||
continue
|
||||
_run_git(["git", "update-index", "--no-assume-unchanged", name], cwd=work_dir)
|
||||
_run_git(["git", "update-index", "--no-skip-worktree", name], cwd=work_dir)
|
||||
|
||||
|
||||
def _git_has_staged_changes(work_dir: Path) -> bool:
|
||||
proc = subprocess.run(
|
||||
["git", "diff", "--cached", "--quiet"],
|
||||
cwd=str(work_dir),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
return proc.returncode != 0
|
||||
|
||||
|
||||
def _git_stage_and_ready(work_dir: Path) -> bool:
|
||||
_wiki_clear_index_flags(work_dir, "Home.md", "_Sidebar.md")
|
||||
_run_git(["git", "add", "-A"], cwd=work_dir)
|
||||
return _git_has_staged_changes(work_dir)
|
||||
|
||||
|
||||
def _load_prev_managed(work_dir: Path, manifest_name: str) -> set[str]:
|
||||
p = work_dir / manifest_name
|
||||
if not p.is_file():
|
||||
@@ -565,9 +689,18 @@ def push_docs_wiki(
|
||||
managed_files=new_files,
|
||||
)
|
||||
|
||||
_run_git(["git", "add", "-A"], cwd=work_dir)
|
||||
status = _run_git(["git", "status", "--porcelain"], cwd=work_dir)
|
||||
if not status.strip() or dry_run:
|
||||
if dry_run:
|
||||
_wiki_clear_index_flags(work_dir, "Home.md", "_Sidebar.md")
|
||||
_run_git(["git", "add", "-A"], cwd=work_dir)
|
||||
return DocsStats(
|
||||
files_total=len(new_files),
|
||||
files_written=len(new_files),
|
||||
files_deleted=deleted,
|
||||
markdown_files=0,
|
||||
attachment_files=0,
|
||||
folders={},
|
||||
)
|
||||
if not _git_stage_and_ready(work_dir):
|
||||
return DocsStats(
|
||||
files_total=len(new_files),
|
||||
files_written=len(new_files),
|
||||
@@ -634,9 +767,18 @@ def clean_docs_wiki(
|
||||
if cleaned != raw:
|
||||
write_text(p, cleaned)
|
||||
|
||||
_run_git(["git", "add", "-A"], cwd=work_dir)
|
||||
status = _run_git(["git", "status", "--porcelain"], cwd=work_dir)
|
||||
if not status.strip() or dry_run:
|
||||
if dry_run:
|
||||
_wiki_clear_index_flags(work_dir, "Home.md", "_Sidebar.md")
|
||||
_run_git(["git", "add", "-A"], cwd=work_dir)
|
||||
return DocsStats(
|
||||
files_total=deleted,
|
||||
files_written=0,
|
||||
files_deleted=deleted,
|
||||
markdown_files=0,
|
||||
attachment_files=0,
|
||||
folders={},
|
||||
)
|
||||
if not _git_stage_and_ready(work_dir):
|
||||
return DocsStats(
|
||||
files_total=deleted,
|
||||
files_written=0,
|
||||
|
||||
Reference in New Issue
Block a user