"""Resolve configuration paths and Gitea/GitLab wiki clone URLs.""" from __future__ import annotations import re import subprocess from pathlib import Path class ResolveError(ValueError): pass def resolve_src_root(config_or_src: Path) -> Path: """ Accept configuration root (…/crm3-26) or its src/ dump. Returns absolute path to the directory that contains Catalogs/, Documents/, … """ path = Path(config_or_src).expanduser().resolve() if not path.exists(): raise ResolveError(f"path does not exist: {path}") if path.is_file(): raise ResolveError(f"expected directory, got file: {path}") if _looks_like_src(path): return path src = path / "src" if src.is_dir() and _looks_like_src(src): return src.resolve() raise ResolveError( f"not a 1C configuration dump: {path} " "(need …/src with Catalogs|Documents|Configuration.xml, or the src itself)" ) def resolve_config_root(config_or_src: Path) -> Path: """Configuration root (parent of src when applicable).""" src = resolve_src_root(config_or_src) if src.name == "src": return src.parent return src def _looks_like_src(path: Path) -> bool: if (path / "Configuration.xml").is_file(): return True markers = ("Catalogs", "Documents", "DataProcessors", "Reports", "CommonModules") return any((path / name).is_dir() for name in markers) def git_toplevel(path: Path) -> Path | None: """Nearest git work tree containing path (submodule or root project).""" path = Path(path).resolve() try: proc = subprocess.run( ["git", "-C", str(path), "rev-parse", "--show-toplevel"], capture_output=True, text=True, check=False, ) except OSError: return None if proc.returncode != 0: return None top = proc.stdout.strip() return Path(top) if top else None def git_remote_url(path: Path, *, remote: str = "origin") -> str | None: """Clone URL of `remote` for the git repo that owns path.""" top = git_toplevel(path) if top is None: return None try: proc = subprocess.run( ["git", "-C", str(top), "remote", "get-url", remote], capture_output=True, text=True, check=False, ) except OSError: return None if proc.returncode != 0: return None url = proc.stdout.strip() return url or None def to_wiki_clone_url(repo_url: str) -> str: """ Map configuration repository URL → wiki git URL. Gitea / GitLab: https://host/org/repo.git → https://host/org/repo.wiki.git git@host:org/repo.git → git@host:org/repo.wiki.git """ url = repo_url.strip().rstrip("/") if not url: raise ResolveError("empty repository URL") if url.endswith(".wiki.git"): return url if url.endswith(".git"): return url[: -len(".git")] + ".wiki.git" return url + ".wiki.git" def resolve_wiki_url( config_or_src: Path, *, wiki_url: str | None = None, remote: str = "origin", ) -> str: """Explicit --wiki-url, or derive from git remote of the configuration repo.""" if wiki_url: return to_wiki_clone_url(wiki_url) root = resolve_config_root(config_or_src) repo = git_remote_url(root, remote=remote) if not repo: raise ResolveError( f"cannot detect git remote '{remote}' for {root}; " "pass --wiki-url explicitly (…/repo.wiki.git)" ) return to_wiki_clone_url(repo) def slug_for_path(path: Path) -> str: """Short filesystem-safe slug from config root name.""" name = resolve_config_root(path).name slug = re.sub(r"[^\w.\-]+", "-", name, flags=re.UNICODE).strip("-") return slug or "config"