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:
mihailkudravcev
2026-07-29 13:31:10 +03:00
parent 3d272b46c7
commit a455cd8085
3 changed files with 174 additions and 27 deletions
+6 -1
View File
@@ -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
+1 -1
View File
@@ -1 +1 @@
0.3.9
0.3.10
+167 -25
View File
@@ -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,