remove is the inverse of add: the require line leaves package.ludic; the lock keeps exactly what the
remaining requires still reach, read from the store's copy of each locked package.ludic (no
network), so a package another still requires stays locked and what only the removed one brought in
leaves with it; each leaving package loses the ludic_modules/ symlink add made, never the shared
store entry. Refused (exit 1) when package.ludic does not require the module; source still
importing a removed package is a warning. With no store copy to read, only the named module leaves.
get --json diffs the lock before and after in memory and prints {added, removed, changed,
unchanged} on stdout (an entry as the lock records it: name, version, hash, kind, provides; a change
as name, from, to, from_hash, to_hash), the resolver's lines on stderr. Cases added to test-pkg,
not run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
116 lines
3.6 KiB
Text
116 lines
3.6 KiB
Text
# pkg_lock.ludic — the package manager's answers for editors and tools: `ludic get --json` (what a
|
|
# get changed in package.lock.ludic, as one JSON object on stdout) and `ludic remove <module>`, the
|
|
# inverse of `ludic add`. Neither touches the network: the diff is of the lock before and after, and
|
|
# a removal re-reads the dependency graph from the store's own copies of each package.ludic.
|
|
|
|
# the human lines of add / get / update: on stdout, or on stderr under --json
|
|
var g_pkg_err: bool = false
|
|
function pkg_say(line: pointer) -> void {
|
|
if g_pkg_err { err(line + nl()) } else { print(line) }
|
|
}
|
|
function pkg_has_flag(flag: pointer) -> bool {
|
|
var ai = 2
|
|
while ai < arg_count() {
|
|
if arg(ai) == flag { return true }
|
|
ai += 1
|
|
}
|
|
return false
|
|
}
|
|
|
|
# ---- JSON out --------------------------------------------------------------------
|
|
|
|
function pkg_hex(d: int) -> pointer {
|
|
let b = bytes(2)
|
|
b[0] = '0' + d
|
|
if d > 9 { b[0] = 'a' + d - 10 }
|
|
b[1] = 0
|
|
return b
|
|
}
|
|
function pkg_jesc(c: int) -> pointer {
|
|
if c == '"' { return "\\\"" }
|
|
if c == 92 { return "\\\\" }
|
|
if c == '\n' { return "\\n" }
|
|
if c == '\t' { return "\\t" }
|
|
if c == '\r' { return "\\r" }
|
|
return "\\u00" + pkg_hex(c >> 4) + pkg_hex(c & 15)
|
|
}
|
|
# `s` as a JSON string, quotes included
|
|
function pkg_jq(s: pointer) -> pointer {
|
|
var o = "\""
|
|
let n = slen(s)
|
|
var start = 0
|
|
var i = 0
|
|
while i < n {
|
|
let c = s[i] & 255
|
|
if c == '"' or c == 92 or c < 32 {
|
|
o = o + sslice(s, start, i) + pkg_jesc(c)
|
|
start = i + 1
|
|
}
|
|
i += 1
|
|
}
|
|
return o + sslice(s, start, n) + "\""
|
|
}
|
|
# a lock line as the lock records it
|
|
function lock_entry_json(m: Manifest) -> pointer {
|
|
var o = `{{"name": {pkg_jq(m.module)}, "version": {pkg_jq(m.ver)}, "hash": {pkg_jq(m.hash)}, "kind": {pkg_jq(m.kind)}, "provides": [`
|
|
var p = 0
|
|
while p < len(m.provides) {
|
|
if p > 0 { o = o + ", " }
|
|
o = o + pkg_jq(m.provides[p])
|
|
p += 1
|
|
}
|
|
return o + "]}"
|
|
}
|
|
function lock_index(ms: []Manifest, module: pointer) -> int {
|
|
var i = 0
|
|
while i < len(ms) {
|
|
if ms[i].module == module { return i }
|
|
i += 1
|
|
}
|
|
return -1
|
|
}
|
|
function json_list(items: []pointer) -> pointer {
|
|
if len(items) == 0 { return "[]" }
|
|
var o = "["
|
|
var i = 0
|
|
while i < len(items) {
|
|
if i > 0 { o = o + "," }
|
|
o = o + nl() + " " + items[i]
|
|
i += 1
|
|
}
|
|
return o + nl() + " ]"
|
|
}
|
|
# what changed between two locks: {"added", "removed", "changed", "unchanged"}. A module whose version
|
|
# or content hash moved is changed; `from` / `to` are its versions, the hashes beside them.
|
|
function lock_diff_json(before: []Manifest, after: []Manifest) -> pointer {
|
|
let added = new []pointer
|
|
let removed = new []pointer
|
|
let changed = new []pointer
|
|
var same = 0
|
|
var i = 0
|
|
while i < len(after) {
|
|
let m = after[i]
|
|
let at = lock_index(before, m.module)
|
|
if at < 0 { push(added, lock_entry_json(m)) }
|
|
else {
|
|
let o = before[at]
|
|
if o.ver == m.ver and o.hash == m.hash { same += 1 }
|
|
else { push(changed, `{{"name": {pkg_jq(m.module)}, "from": {pkg_jq(o.ver)}, "to": {pkg_jq(m.ver)}, "from_hash": {pkg_jq(o.hash)}, "to_hash": {pkg_jq(m.hash)}}}`) }
|
|
}
|
|
i += 1
|
|
}
|
|
i = 0
|
|
while i < len(before) {
|
|
if lock_index(after, before[i].module) < 0 { push(removed, lock_entry_json(before[i])) }
|
|
i += 1
|
|
}
|
|
var o = "{" + nl() + ` "added": {json_list(added)},` + nl()
|
|
o = o + ` "removed": {json_list(removed)},` + nl()
|
|
o = o + ` "changed": {json_list(changed)},` + nl()
|
|
return o + ` "unchanged": {string(same)}` + nl() + "}" + nl()
|
|
}
|
|
function read_lock_or_empty() -> []Manifest {
|
|
let t = read_file("package.lock.ludic")
|
|
if t == null { return new []Manifest }
|
|
return parse_lock(t)
|
|
}
|