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.
|
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
|
## [0.3.9] - 2026-07-29
|
||||||
|
|
||||||
|
|||||||
+167
-25
@@ -249,6 +249,23 @@ def _encode_md_target(target: str) -> str:
|
|||||||
return quote(target, safe="/#._-~")
|
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:
|
def _merge_marker_block(existing: str, block: str) -> str:
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@@ -281,6 +298,53 @@ def _docs_index_rel(folder: str, managed_files: set[str]) -> str | None:
|
|||||||
return 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(
|
def ensure_root_navigation(
|
||||||
*,
|
*,
|
||||||
work_dir: Path,
|
work_dir: Path,
|
||||||
@@ -314,11 +378,22 @@ def ensure_root_navigation(
|
|||||||
f"- [{display}]({_encode_md_target(link_target)})",
|
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(
|
sidebar_block = "\n".join(
|
||||||
[
|
[
|
||||||
"### Документация (docs)",
|
"### Документация (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,
|
map_src_abs_to_dst_rel=map_src_abs_to_dst_rel,
|
||||||
attachments=attachments,
|
attachments=attachments,
|
||||||
)
|
)
|
||||||
write_text(out_dir / dst_rel, rendered)
|
|
||||||
src_folder = item.dst_rel.parent
|
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(
|
folder_docs.setdefault(src_folder, []).append(
|
||||||
(title_by_src[item.src.resolve()], dst_rel)
|
(title_by_src[item.src.resolve()], dst_rel)
|
||||||
)
|
)
|
||||||
@@ -443,21 +533,22 @@ def build_docs_output(
|
|||||||
|
|
||||||
for f in all_folders:
|
for f in all_folders:
|
||||||
lines: list[str] = []
|
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(f"# Индекс: {heading}")
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
docs = sorted(folder_docs.get(f, []), key=lambda x: x[0].casefold())
|
if f != Path("."):
|
||||||
if docs:
|
parent = f.parent if f.parent != Path(".") else Path(".")
|
||||||
lines.append("## Статьи")
|
parent_label = _folder_label(parent, folder_clean)
|
||||||
lines.append("")
|
parent_short = _folder_short_name(parent, folder_clean)
|
||||||
for title, target_rel in docs:
|
parent_target = index_target_by_folder.get(parent)
|
||||||
rel_link = os.path.relpath(
|
if parent_target is not None:
|
||||||
target_rel.as_posix(),
|
lines.append(f"## 📁 {parent_short}")
|
||||||
start=index_target_by_folder[f].parent.as_posix(),
|
lines.append("")
|
||||||
).replace("\\", "/")
|
lines.append(
|
||||||
lines.append(f"- [{title}]({_encode_md_target(rel_link)})")
|
f"[⤴️ Вернуться в {parent_short}]({_rel_link(index_target_by_folder[f], parent_target)})"
|
||||||
lines.append("")
|
)
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
children = sorted(child_folders.get(f, set()), key=lambda p: p.as_posix().casefold())
|
children = sorted(child_folders.get(f, set()), key=lambda p: p.as_posix().casefold())
|
||||||
if children:
|
if children:
|
||||||
@@ -465,11 +556,18 @@ def build_docs_output(
|
|||||||
lines.append("")
|
lines.append("")
|
||||||
for ch in children:
|
for ch in children:
|
||||||
target_rel = index_target_by_folder[ch]
|
target_rel = index_target_by_folder[ch]
|
||||||
rel_link = os.path.relpath(
|
ch_label = _folder_short_name(ch, folder_clean)
|
||||||
target_rel.as_posix(),
|
rel_link = _rel_link(index_target_by_folder[f], target_rel)
|
||||||
start=index_target_by_folder[f].parent.as_posix(),
|
lines.append(f"- [📁 {ch_label}]({rel_link})")
|
||||||
).replace("\\", "/")
|
lines.append("")
|
||||||
lines.append(f"- [{ch.as_posix()}]({_encode_md_target(rel_link)})")
|
|
||||||
|
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("")
|
lines.append("")
|
||||||
|
|
||||||
idx_target = index_target_by_folder[f]
|
idx_target = index_target_by_folder[f]
|
||||||
@@ -511,6 +609,32 @@ def _run_git(cmd: list[str], *, cwd: Path) -> str:
|
|||||||
return proc.stdout
|
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]:
|
def _load_prev_managed(work_dir: Path, manifest_name: str) -> set[str]:
|
||||||
p = work_dir / manifest_name
|
p = work_dir / manifest_name
|
||||||
if not p.is_file():
|
if not p.is_file():
|
||||||
@@ -565,9 +689,18 @@ def push_docs_wiki(
|
|||||||
managed_files=new_files,
|
managed_files=new_files,
|
||||||
)
|
)
|
||||||
|
|
||||||
_run_git(["git", "add", "-A"], cwd=work_dir)
|
if dry_run:
|
||||||
status = _run_git(["git", "status", "--porcelain"], cwd=work_dir)
|
_wiki_clear_index_flags(work_dir, "Home.md", "_Sidebar.md")
|
||||||
if not status.strip() or dry_run:
|
_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(
|
return DocsStats(
|
||||||
files_total=len(new_files),
|
files_total=len(new_files),
|
||||||
files_written=len(new_files),
|
files_written=len(new_files),
|
||||||
@@ -634,9 +767,18 @@ def clean_docs_wiki(
|
|||||||
if cleaned != raw:
|
if cleaned != raw:
|
||||||
write_text(p, cleaned)
|
write_text(p, cleaned)
|
||||||
|
|
||||||
_run_git(["git", "add", "-A"], cwd=work_dir)
|
if dry_run:
|
||||||
status = _run_git(["git", "status", "--porcelain"], cwd=work_dir)
|
_wiki_clear_index_flags(work_dir, "Home.md", "_Sidebar.md")
|
||||||
if not status.strip() or dry_run:
|
_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(
|
return DocsStats(
|
||||||
files_total=deleted,
|
files_total=deleted,
|
||||||
files_written=0,
|
files_written=0,
|
||||||
|
|||||||
Reference in New Issue
Block a user