#!/usr/bin/env python3 """ wiki_push.py — publish a local space to a wiki. wiki_push.py -m "Import the simple-chains discussion" wiki_push.py --prefix "Simple Chains/Ideas" -m "Rework B-04" wiki_push.py -m "Fix the send-timing table" Simple-Chains/Ideas/Send-timing.md Every page in the selection is compared against `pushed` — the hash of what was last published — and only the ones that differ are sent. That is the whole of change detection: no timestamps, no drift model. A page with no `sub_url` is created; a page with one is edited in place. The title comes from the manifest, never re-derived from the file, because sending a different title to the edit endpoint is a RENAME and leaves nothing behind at the old address. Pushing is additive. A page deleted locally is NOT deleted in the wiki — the manifest simply stops mentioning it. Removing a published page is an explicit act; do it in the web UI or with a DELETE through /tea:use. Usage: wiki_push.py -m MESSAGE [--space SPACE] [--repo owner/repo] [--prefix TITLE] [--dry-run] [--out DIR] [PATH-or-TITLE ...] """ import argparse import os import sys _HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, _HERE) sys.path.insert(0, os.path.join(_HERE, "..", "..", "sync", "scripts")) sys.path.insert(0, os.path.join(_HERE, "..", "..", "page", "scripts")) import _gitea # noqa: E402 import page # noqa: E402 import wikimap # noqa: E402 def select(manifest, prefix, targets): """The pages to consider, in tree order. A positional argument matches a manifest path or a title, exactly. Exact because a near-miss that silently selects nothing is indistinguishable from a clean no-op run, and the operator finds out only when the page never appears.""" pages = (page.children_of(manifest, prefix) if prefix else page.sorted_pages(manifest)) if not targets: return pages, [] want, chosen, hit = set(targets), [], set() for p, e in pages: if p in want or e.get("title") in want: chosen.append((p, e)) hit.add(p if p in want else e.get("title")) return chosen, sorted(want - hit) def main(): ap = argparse.ArgumentParser() ap.add_argument("targets", nargs="*", metavar="PATH-or-TITLE") ap.add_argument("-m", "--message", required=True, help="wiki commit message for this push") ap.add_argument("--repo", help="owner/repo (default: the repo in CWD)") ap.add_argument("--space", help="local space (default: the owner/repo slug)") ap.add_argument("--prefix", default="", help="only titles at or under this") ap.add_argument("--dry-run", action="store_true") ap.add_argument("--out", help="wiki cache root (default: /tmp/wiki)") a = ap.parse_args() login = _gitea.require_login() base = _gitea.repo_base(a.repo) slug = _gitea.repo_slug(login, a.repo) space = a.space or slug root = a.out or page.WIKI_ROOT space_dir = page.space_root(space, root) if not os.path.isdir(space_dir): _gitea.die("no such space: %s (looked in %s). Import or pull first." % (space, space_dir)) manifest = page.load_manifest(space, root) if not manifest["pages"]: _gitea.die("space %s has no pages in its manifest" % space) chosen, missing = select(manifest, a.prefix, a.targets) for t in missing: _gitea.warn("not in the manifest: %s" % t) if not chosen: _gitea.die("nothing selected") created = updated = skipped = 0 for relpath, e in chosen: title = e.get("title") full = os.path.join(space_dir, relpath) if not title: _gitea.warn("%s has no title in the manifest; skipped" % relpath) continue if not os.path.isfile(full): _gitea.warn("%s is in the manifest but not on disk; skipped" % relpath) continue with open(full, encoding="utf-8") as f: text = f.read() h = page.body_hash(text) if e.get("sub_url") and h == e.get("pushed"): skipped += 1 continue verb = "create" if not e.get("sub_url") else "update" print("%-7s %-44s %s" % (verb, relpath, title)) if a.dry_run: created += verb == "create" updated += verb == "update" continue if verb == "create": payload = wikimap.new_payload(title, text, a.message) got = _gitea.api(login, "%s/wiki/new" % base, method="POST", payload=payload, payload_name="wiki-new") else: payload = wikimap.edit_payload(title, text, a.message) got = _gitea.api(login, wikimap.page_endpoint(base, e["sub_url"]), method="PATCH", payload=payload, payload_name="wiki-edit") if not isinstance(got, dict) or not got.get("sub_url"): _gitea.warn("%s: no page returned; the manifest is unchanged for it" % title) continue # sub_url comes back from Gitea and is stored as given. It is the only # address this page has, and it is not something we could have computed. e.update(wikimap.from_payload(got)) e["synced"] = _gitea.now_iso() e["pushed"] = h manifest["pages"][relpath] = e created += verb == "create" updated += verb == "update" if a.dry_run: print("\ndry run — %d to create, %d to update, %d unchanged" % (created, updated, skipped)) return 0 page.save_manifest(manifest, root) print("\n%d created, %d updated, %d unchanged -> %s wiki" % (created, updated, skipped, slug)) return 0 if __name__ == "__main__": sys.exit(main())