diff --git a/helpers/fill-oc-go-providers.py b/helpers/fill-oc-go-providers.py new file mode 100755 index 0000000..3182a6e --- /dev/null +++ b/helpers/fill-oc-go-providers.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python3 +"""ocd-fill.py -- create ocd account stubs from oc-go account names. + +Reads the oc-go provider account map (a YAML file), takes the account names, +and appends [[account]] stubs to the ocd config for any name not already +present. cookie and default_workspace are left empty for you to fill in. + +Inputs + source defaults to $OC_GO_YAML or + ~/.pi/agent/oc-providers/accounts/oc-go.yaml + (fallback: ~/.pi/agent/oc-providers/oc-go.yaml) + config defaults to $OCD_CONFIG or + $XDG_CONFIG_HOME/ocd/config.toml (~/.config/ocd/config.toml) + +Flags + --source PATH override the oc-go YAML path + --config PATH override the ocd config path + --dry-run print the plan, do not write + -h, --help print this help + +Report: added N / present N / to fill N + added new stubs written this run + present source names already in the config + to fill config entries whose cookie or default_workspace is empty or + still a placeholder +""" + +from __future__ import annotations + +import argparse +import os +import sys +from pathlib import Path + +import yaml + +try: + import tomllib # Python 3.11+ +except ModuleNotFoundError: # pragma: no cover + sys.stderr.write("ocd-fill: python 3.11+ required for tomllib\n") + sys.exit(1) + + +HEADER = ( + "# ocd config. Generated by ocd-fill; fill in cookie and\n" + "# default_workspace for each account. ${VAR} expands from env.\n" +) + + +def default_source() -> Path: + if env := os.environ.get("OC_GO_YAML"): + return Path(env) + home = Path.home() + primary = home / ".pi/agent/oc-providers/accounts/oc-go.yaml" + if primary.is_file(): + return primary + return home / ".pi/agent/oc-providers/oc-go.yaml" + + +def default_config() -> Path: + if env := os.environ.get("OCD_CONFIG"): + return Path(env) + xdg = os.environ.get("XDG_CONFIG_HOME") or str(Path.home() / ".config") + return Path(xdg) / "ocd/config.toml" + + +def toml_basic_string(s: str) -> str: + """Encode a TOML basic string (double-quoted, minimal escapes).""" + s = s.replace("\\", "\\\\").replace('"', '\\"') + return f'"{s}"' + + +def read_source_names(path: Path) -> list[str]: + with path.open(encoding="utf-8") as f: + data = yaml.safe_load(f) or {} + accs = data.get("accounts") or {} + if not isinstance(accs, dict): + sys.stderr.write(f"ocd-fill: `accounts:` is not a map in {path}\n") + sys.exit(1) + return list(accs.keys()) + + +def read_config(path: Path) -> tuple[list[str], int]: + """Return (existing account names, count of entries still to fill).""" + if not path.is_file(): + return [], 0 + try: + with path.open("rb") as f: + data = tomllib.load(f) + except tomllib.TOMLDecodeError as e: + sys.stderr.write(f"ocd-fill: config is not valid TOML: {e}\n") + sys.exit(1) + names: list[str] = [] + to_fill = 0 + for a in data.get("account", []): + n = a.get("name") + if n: + names.append(str(n)) + cookie = str(a.get("cookie", "") or "") + ws = str(a.get("default_workspace", "") or "") + needs = ( + not cookie.strip() + or not ws.strip() + or "REPLACE_ME" in cookie + or "REPLACE_ME" in ws + ) + if needs: + to_fill += 1 + return names, to_fill + + +def append_stubs(path: Path, names: list[str]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + existing = path.read_text(encoding="utf-8") if path.is_file() else "" + stub_text = "\n\n".join(stub_block(n) for n in names) + "\n" + with path.open("a", encoding="utf-8") as f: + if existing.strip(): + # Keep one blank line before the first new stub. + f.write(existing.rstrip("\n") + "\n\n") + else: + f.write(HEADER + "\n") + f.write(stub_text) + + +def stub_block(name: str) -> str: + return ( + f"[[account]]\n" + f"name = {toml_basic_string(name)}\n" + f'cookie = ""\n' + f'default_workspace = ""' + ) + + +def main(argv: list[str]) -> int: + p = argparse.ArgumentParser( + prog="ocd-fill", + description="Create ocd account stubs from oc-go account names.", + add_help=False, + ) + p.add_argument("-h", "--help", action="help", help="print this help and exit") + p.add_argument("--source", type=Path, default=default_source(), + help="oc-go YAML path") + p.add_argument("--config", type=Path, default=default_config(), + help="ocd config path to write") + p.add_argument("--dry-run", action="store_true", help="print plan, do not write") + args = p.parse_args(argv) + + if not args.source.is_file(): + sys.stderr.write(f"ocd-fill: source file not found: {args.source}\n") + return 1 + + src_names = read_source_names(args.source) + existing_names, to_fill = read_config(args.config) + existing_set = set(existing_names) + to_add = [n for n in src_names if n not in existing_set] + present = [n for n in src_names if n in existing_set] + + n_added = len(to_add) + n_present = len(present) + + print(f"ocd-fill: source={args.source}") + print(f"ocd-fill: config={args.config}") + if n_added == 0: + print("ocd-fill: added 0") + else: + print(f"ocd-fill: added {n_added}: {' '.join(to_add)}") + print(f"ocd-fill: present {n_present}") + print(f"ocd-fill: to fill {to_fill}") + + if args.dry_run: + print("ocd-fill: dry run, no write") + return 0 + if n_added == 0: + return 0 + + append_stubs(args.config, to_add) + print(f"ocd-fill: wrote {n_added} stub(s) to {args.config}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv[1:])) \ No newline at end of file