summaryrefslogtreecommitdiff
path: root/link/pi-models.py
diff options
context:
space:
mode:
authorIgor Soarez <igor@soarez.org>2026-09-25 12:20:13 +0300
committerIgor Soarez <igor@soarez.org>2026-09-25 12:20:13 +0300
commited21d1270007a8f9cbaa2ca7da0fee1eefeb405f (patch)
tree9f71c392b5c087b55304d1bb7123d0e436c0f343 /link/pi-models.py
parent220553f1f016707a883632edb38b5e767728df97 (diff)
link/pi.sh: the generators move into files, and parse under bash 3.2 again
57e49dd made link/pi.sh unparseable on macOS. bash 3.2 does not skip heredoc content when it scans for the end of a command substitution, so the three apostrophes in a comment inside the new heredoc read as an unterminated quote and the script stopped parsing at end of file. ./link.sh on a Mac aborted partway: settings still generated, models.json stayed the symlink the commit meant to replace, and everything after that point never ran. Linux was fine, and so was every check I ran -- `bash` on a Mac with Homebrew is 5.x, which parses it happily. Reported from the machine it broke. Rewording the comment would fix it and leave the trap armed for the next edit, so the two python programs move into link/pi-settings.py and link/pi-models.py instead, called by path. The text is unchanged; there is no longer a heredoc inside $( ) to trip over, and both files now compile-check on their own. Verified under /bin/bash 3.2: first apply replaces both symlinks, a package removed by hand still survives a relink, fragments merge for settings and for models, and repeat runs report unchanged.
Diffstat (limited to 'link/pi-models.py')
-rw-r--r--link/pi-models.py66
1 files changed, 66 insertions, 0 deletions
diff --git a/link/pi-models.py b/link/pi-models.py
new file mode 100644
index 0000000..6188c94
--- /dev/null
+++ b/link/pi-models.py
@@ -0,0 +1,66 @@
+"""Generate ~/.pi/agent/models.json from the repo and its fragments.
+
+Run by link/pi.sh. It lives in a file rather than a heredoc inside $( ):
+macOS ships bash 3.2, which does not skip heredoc content when it scans
+for the end of a command substitution, so an odd number of apostrophes in
+here would stop the whole script parsing -- which it did, on 2026-09-25.
+"""
+
+import glob, json, os, sys
+
+base_path, frag_dir, target = sys.argv[1:4]
+
+
+def load(path):
+ with open(path) as f:
+ return json.load(f)
+
+
+# The repo's providers, then each fragment's. A fragment may add a provider or
+# replace one of the same name; any other top-level key takes the last file's
+# value. Which provider came from where is reported: a fragment quietly
+# shadowing a provider the repo ships would otherwise be invisible.
+desired = {}
+notes = []
+for path in [base_path] + sorted(glob.glob(os.path.join(frag_dir, "*.json"))):
+ try:
+ layer = load(path)
+ except (OSError, ValueError) as e:
+ print("error:cannot read %s: %s" % (path, e))
+ sys.exit(0)
+ for key, value in layer.items():
+ if key == "providers":
+ providers = desired.setdefault("providers", {})
+ if path != base_path:
+ for name in value:
+ notes.append("%s %s by %s" % (
+ name,
+ "replaced" if name in providers else "added",
+ os.path.basename(path),
+ ))
+ providers.update(value)
+ else:
+ desired[key] = value
+
+# A symlink is the pre-generation state: replace it, even with no fragments, so
+# the live file is never the tracked one.
+was_link = os.path.islink(target)
+try:
+ current = None if was_link else load(target)
+except (OSError, ValueError):
+ current = None
+
+for note in notes:
+ print("note:" + note)
+
+if current == desired:
+ print("unchanged:")
+elif os.environ.get("DRY_RUN"):
+ print("would-write:" + ("replacing the link" if was_link else "merged changes"))
+else:
+ tmp = target + ".dotfiles-tmp"
+ with open(tmp, "w") as f:
+ json.dump(desired, f, indent=2)
+ f.write("\n")
+ os.replace(tmp, target)
+ print("wrote:" + ("replaced the link" if was_link else "merged changes"))