src/lock/lock.bend
ezx@1.11.0.0 · 780 lines · 104 definitions
1
2
3
4
5
6
7
8
9
10
11
12
13
14import Base
15import ezx@1.11.0.0/src/toml/toml.bend as T
16import ezx@1.11.0.0/src/ledger/manifest.bend as M
17import ezx@1.11.0.0/src/pkg/pkg.bend as K
18
19
20
21
22
23
24type Src is Data:
25 Hub{}
26 Git{url: String, rev: String, entry: String, root: String, nar: String, tag: String}
27 Member{path: String, entry: String}
28
29
30type Pack is Data:
31 Pack{hash: String, src: Src, files: List<&2, K.Item>}
32
33
34type Origin is Data:
35 Origin{hash: String, src: Src}
36
37
38
39
40
41type Name is Data:
42 Name{nv: String, hash: String}
43
44
45def spec.hash(spec: String) -> String:
46 K.word(String.split(spec, '/'), 0n)
47
48
49def spec.put(+spec: String) -> List<&2, String>:
50 Bool.pick(List<&2, String>, K.is_hub(spec), [spec.hash(spec)], [])
51
52
53def specs(ms: List<&2, String>) -> List<&2, String>:
54 match ms:
55 case []:
56 []
57 case h <> t:
58 List.append(&2, String, spec.put(h), specs(t))
59
60
61def kids(srcs: List<&2, String>) -> List<&2, String>:
62 match srcs:
63 case []:
64 []
65 case h <> t:
66 List.append(&2, String, specs(K.scan.mods(K.scan(h))), kids(t))
67
68
69
70
71def named.put(+spec: String) -> List<&2, String>:
72 +nv = K.named.of(spec)
73 Bool.pick(List<&2, String>, String.is_empty(nv), [], [nv])
74
75
76def named(ms: List<&2, String>) -> List<&2, String>:
77 match ms:
78 case []:
79 []
80 case h <> t:
81 List.append(&2, String, named.put(h), named(t))
82
83
84def kids.named(srcs: List<&2, String>) -> List<&2, String>:
85 match srcs:
86 case []:
87 []
88 case h <> t:
89 List.append(&2, String, named(K.scan.mods(K.scan(h))), kids.named(t))
90
91
92
93def name.at(hit: Bool, hash: String, rest: Unit -> String) -> String:
94 match hit:
95 case True{}:
96 hash
97 case False{}:
98 rest(Unit{})
99
100
101
102def name.find(ns: List<&2, Name>, +nv: String) -> String:
103 match ns:
104 case []:
105 ""
106 case Name{+n, h} <> t:
107 name.at(String.eq(n, nv), h, _u => name.find(t, nv))
108
109
110def name.put(+hash: String) -> List<&2, String>:
111 Bool.pick(List<&2, String>, String.is_empty(hash), [], [hash])
112
113
114
115def name.hashes(+ns: List<&2, Name>, nvs: List<&2, String>) -> List<&2, String>:
116 match nvs:
117 case []:
118 []
119 case +h <> t:
120 List.append(&2, String, name.put(name.find(ns, h)), name.hashes(ns, t))
121
122
123
124def kids.in(+ns: List<&2, Name>, +srcs: List<&2, String>) -> List<&2, String>:
125 List.append(&2, String, kids(srcs), name.hashes(ns, kids.named(srcs)))
126
127
128def name.one(+nv: String, +hash: String) -> List<&2, Name>:
129 Bool.pick(List<&2, Name>, String.is_empty(hash), [], [Name{nv, hash}])
130
131
132def name.pairs(+ns: List<&2, Name>, nvs: List<&2, String>) -> List<&2, Name>:
133 match nvs:
134 case []:
135 []
136 case +h <> t:
137 List.append(&2, Name, name.one(h, name.find(ns, h)), name.pairs(ns, t))
138
139
140
141def hex.char(+ch: Char) -> Bool:
142 +x = Char.to_u32(ch)
143 Bool.or(Char.is_digit(ch), Bool.and(U32.is_ge(x, 97), U32.is_le(x, 102)))
144
145def hex.all(text: String) -> Bool:
146 match text:
147 case SNil{}:
148 True{}
149 case SCon{+h, t}:
150 +rest = hex.all(t)
151 Bool.and(hex.char(h), rest)
152
153def hash.ok(+hash: String) -> Bool:
154 Bool.and(Nat.is_eq(String.length(hash), 34n),
155 Bool.and(String.starts_with(hash, "0x"), hex.all(String.drop(hash, 2n))))
156
157
158def name.text(+hash: String) -> String:
159 hash ++ "\n"
160
161
162
163def name.valid(name: Name) -> Bool:
164 Name{+nv, +hash} = name
165 Bool.and(K.name.ok(nv), hash.ok(hash))
166
167def names.valid.pick(ok: Bool, +name: Name, rest: List<&2, Name>) -> List<&2, Name>:
168 match ok:
169 case True{}:
170 name <> rest
171 case False{}:
172 rest
173
174
175def names.valid(ns: List<&2, Name>) -> List<&2, Name>:
176 match ns:
177 case []:
178 []
179 case +h <> t:
180 names.valid.pick(name.valid(h), h, names.valid(t))
181
182
183def segs(sect: T.Sect) -> List<&2, String>:
184 T.Sect{n, _ps} = sect
185 T.segments(n)
186
187
188def is3(+ss: List<&2, String>, first: String, second: String, third: String) -> Bool:
189 Bool.and(Nat.is_eq(List.length(&2, String, ss), 3n),
190 Bool.and(String.eq(K.word(ss, 0n), first),
191 Bool.and(String.eq(K.word(ss, 1n), second), String.eq(K.word(ss, 2n), third))))
192
193
194def named1(+ss: List<&2, String>, first: String) -> Bool:
195 Bool.and(Nat.is_eq(List.length(&2, String, ss), 1n), String.eq(K.word(ss, 0n), first))
196
197
198
199def named3(+ss: List<&2, String>, first: String, third: String) -> Bool:
200 Bool.and(Nat.is_eq(List.length(&2, String, ss), 3n),
201 Bool.and(String.eq(K.word(ss, 0n), first), String.eq(K.word(ss, 2n), third)))
202
203
204def at.pick(hit: Bool, sect: T.Sect, rest: List<&2, T.Kv>) -> List<&2, T.Kv>:
205 match hit:
206 case True{}:
207 T.Sect{_n, ps} = sect
208 ps
209 case False{}:
210 rest
211
212
213def at3(ss: List<&2, T.Sect>, +first: String, +second: String, +third: String) -> List<&2, T.Kv>:
214 match ss:
215 case []:
216 []
217 case +h <> t:
218 at.pick(is3(segs(h), first, second, third), h, at3(t, first, second, third))
219
220
221def src.member(member: Bool, +ps: List<&2, T.Kv>) -> Src:
222 match member:
223 case True{}:
224 Member{T.value(ps, "path"), T.value(ps, "entry")}
225 case False{}:
226 Hub{}
227
228
229def src.go(git: Bool, +member: Bool, +ps: List<&2, T.Kv>) -> Src:
230 match git:
231 case True{}:
232 Git{T.value(ps, "url"), T.value(ps, "rev"), T.value(ps, "entry"),
233 T.value(ps, "root"), T.value(ps, "narHash"), T.value(ps, "tag")}
234 case False{}:
235 src.member(member, ps)
236
237
238def src_of(+ps: List<&2, T.Kv>) -> Src:
239 +kind = T.value(ps, "kind")
240 src.go(String.eq(kind, "git"), String.eq(kind, "member"), ps)
241
242
243
244def ledger.other(src: M.Source, +entry: String) -> Src:
245 match src:
246 case M.Hub{_named}:
247 Hub{}
248 case M.Git{_url, _rev, _tag, _root, _nar, _vend}:
249 Hub{}
250 case M.Member{path, _named}:
251 Member{path, entry}
252
253
254
255
256
257def ledger.put(git: Bool, +dep: M.Dep) -> List<&2, Origin>:
258 match git:
259 case True{}:
260 [Origin{M.dep.hash(dep), Git{M.source.url(M.source.dep(dep)),
261 M.source.rev(M.source.dep(dep)), M.dep.entry(dep),
262 M.source.root(M.source.dep(dep)), M.source.nar(M.source.dep(dep)),
263 M.source.tag(M.source.dep(dep))}}]
264 case False{}:
265 [Origin{M.dep.hash(dep), ledger.other(M.source.dep(dep), M.dep.entry(dep))}]
266
267
268def ledger.origins(ds: List<&2, M.Dep>) -> List<&2, Origin>:
269 match ds:
270 case []:
271 []
272 case +h <> t:
273 List.append(&2, Origin,
274 ledger.put(M.source.is_git(M.source.dep(h)), h), ledger.origins(t))
275
276
277
278def ledger.read(parsed: M.Read) -> List<&2, Origin>:
279 match parsed:
280 case M.Bad{_why}:
281 []
282 case M.Good{m}:
283 M.Manifest{_n, _e, _b, _h, _pa, _pv, ds, _ts} = m
284 ledger.origins(ds)
285
286
287def ledger.name(+dep: M.Dep) -> List<&2, Name>:
288 +nv = M.source.nv(M.source.dep(dep))
289 Bool.pick(List<&2, Name>, String.is_empty(nv), [], [Name{nv, M.dep.hash(dep)}])
290
291
292
293def ledger.names.of(ds: List<&2, M.Dep>) -> List<&2, Name>:
294 match ds:
295 case []:
296 []
297 case +h <> t:
298 List.append(&2, Name, ledger.name(h), ledger.names.of(t))
299
300
301def ledger.names(parsed: M.Read) -> List<&2, Name>:
302 match parsed:
303 case M.Bad{_why}:
304 []
305 case M.Good{m}:
306 M.Manifest{_n, _e, _b, _h, _pa, _pv, ds, _ts} = m
307 ledger.names.of(ds)
308
309
310def origin.at(known: Origin, rest: Src, wanted: String) -> Src:
311 Origin{n, s} = known
312 Bool.pick(Src, String.eq(n, wanted), s, rest)
313
314
315def origin(ds: List<&2, Origin>, +wanted: String) -> Src:
316 match ds:
317 case []:
318 Hub{}
319 case x <> t:
320 origin.at(x, origin(t, wanted), wanted)
321
322
323def origin.hash(known: Origin) -> String:
324 Origin{n, _s} = known
325 n
326
327
328def line.put(+ws: List<&2, String>) -> List<&2, K.Item>:
329 Bool.pick(List<&2, K.Item>, Nat.is_eq(List.length(&2, String, ws), 2n),
330 [K.Item{K.word(ws, 1n), K.word(ws, 0n)}], [])
331
332
333def manifest.files(ls: List<&2, String>) -> List<&2, K.Item>:
334 match ls:
335 case []:
336 []
337 case h <> t:
338 List.append(&2, K.Item, line.put(K.words(h)), manifest.files(t))
339
340
341def url_of(+hub: String, +hash: String, at: String) -> String:
342 hub ++ "/" ++ hash ++ "/" ++ at
343
344
345
346def want(+hash: String) -> String:
347 String.drop(hash, 2n)
348
349
350
351
352
353
354def nar.why.go(none: Bool, same: Bool, +want: String, +got: String, +url: String, +rev: String) -> String:
355 match none:
356 case True{}:
357 "ez: ez.toml records no narHash for " ++ url ++ " at " ++ rev ++
358 ", so its tree cannot be checked; run `ez lock --upgrade` to pin it"
359 case False{}:
360 match same:
361 case True{}:
362 ""
363 case False{}:
364 "ez: " ++ url ++ " at " ++ rev ++ " has narHash " ++ got ++
365 ", and ez.toml records " ++ want
366
367def nar.why(+want: String, +got: String, +url: String, +rev: String) -> String:
368 nar.why.go(String.is_empty(want), String.eq(want, got), want, got, url, rev)
369
370
371def pack.hash(package: Pack) -> String:
372 Pack{h, _s, _f} = package
373 h
374
375
376
377
378
379
380
381
382
383def any.step(here: Bool, rest: Unit -> Bool) -> Bool:
384 match here:
385 case True{}:
386 True{}
387 case False{}:
388 rest(Unit{})
389
390
391
392
393
394def known(ds: List<&2, Origin>, +wanted: String) -> Bool:
395 match ds:
396 case []:
397 False{}
398 case x <> t:
399 any.step(String.eq(origin.hash(x), wanted), _u => known(t, wanted))
400
401
402
403
404def has(ds: List<&2, Pack>, +wanted: String) -> Bool:
405 match ds:
406 case []:
407 False{}
408 case x <> t:
409 any.step(String.eq(wanted, pack.hash(x)), _u => has(t, wanted))
410
411
412
413
414
415def seen.holds(xs: List<&2, String>, +needle: String) -> Bool:
416 match xs:
417 case []:
418 False{}
419 case +h <> t:
420 any.step(String.eq(h, needle), _u => seen.holds(t, needle))
421
422
423
424def kv(key: String, +val: String) -> List<&2, T.Kv>:
425 Bool.pick(List<&2, T.Kv>, String.is_empty(val), [], [T.Kv{key, val}])
426
427
428def src.pairs(source: Src) -> List<&2, T.Kv>:
429 match source:
430 case Hub{}:
431 [T.Kv{"kind", "hub"}]
432 case Git{url, rev, entry, root, nar, tag}:
433 List.concat(&2, T.Kv,
434 [[T.Kv{"kind", "git"}], kv("url", url), kv("rev", rev), kv("entry", entry),
435 kv("root", root), kv("narHash", nar), kv("tag", tag)])
436 case Member{path, entry}:
437 List.concat(&2, T.Kv, [[T.Kv{"kind", "member"}], kv("path", path), kv("entry", entry)])
438
439
440def file.pair(item: K.Item) -> T.Kv:
441 K.Item{at, sum} = item
442 T.Kv{at, sum}
443
444
445def file.pairs(fs: List<&2, K.Item>) -> List<&2, T.Kv>:
446 match fs:
447 case []:
448 []
449 case h <> t:
450 file.pair(h) <> file.pairs(t)
451
452
453
454def pack.sects.of(hash: String, src: Src, fs: List<&2, K.Item>) -> List<&2, T.Sect>:
455 +at = "packages." ++ T.quote(hash)
456 [T.Sect{at ++ ".source", src.pairs(src)},
457 T.Sect{at ++ ".files", file.pairs(fs)}]
458
459
460def pack.sects(package: Pack) -> List<&2, T.Sect>:
461 Pack{hash, src, files} = package
462 pack.sects.of(hash, src, K.files_of(files))
463
464
465def pack.all(ps: List<&2, Pack>) -> List<&2, T.Sect>:
466 match ps:
467 case []:
468 []
469 case h <> t:
470 List.append(&2, T.Sect, pack.sects(h), pack.all(t))
471
472
473
474def pack.le(left: Pack, right: Pack) -> Bool:
475 String.is_le(pack.hash(left), pack.hash(right))
476
477
478
479
480def pack.ins.put(le: Bool, item: Pack, head: Pack, tail: List<&2, Pack>, rest: List<&2, Pack>) -> List<&2, Pack>:
481 match le:
482 case True{}:
483 item <> (head <> tail)
484 case False{}:
485 head <> rest
486
487
488def pack.ins(ps: List<&2, Pack>, +item: Pack) -> List<&2, Pack>:
489 match ps:
490 case []:
491 [item]
492 case Con{+h, +t}:
493 pack.ins.put(pack.le(item, h), item, h, t, pack.ins(t, item))
494
495
496
497
498
499
500def pack.sort(ps: List<&2, Pack>) -> List<&2, Pack>:
501 match ps:
502 case []:
503 []
504 case Con{+h, t}:
505 pack.ins(pack.sort(t), h)
506
507
508
509
510
511def lock.sect(hub: String) -> T.Sect:
512 T.Sect{"lock", [T.Kv{"version", "2"}, T.Kv{"hub", hub}]}
513
514
515
516
517def doc(hub: String, ps: List<&2, Pack>) -> List<&2, T.Sect>:
518 List.append(&2, T.Sect, [lock.sect(hub)], pack.all(pack.sort(ps)))
519
520
521
522
523
524
525
526def pack.block(package: Pack) -> K.Item:
527 Pack{+hash, src, files} = package
528 K.Item{hash, T.render(pack.sects.of(hash, src, K.files_of(files)))}
529
530
531def pack.blocks(ps: List<&2, Pack>) -> List<&2, K.Item>:
532 match ps:
533 case []:
534 []
535 case h <> t:
536 pack.block(h) <> pack.blocks(t)
537
538
539def block.texts(bs: List<&2, K.Item>) -> List<&2, String>:
540 match bs:
541 case []:
542 []
543 case K.Item{_hash, text} <> t:
544 text <> block.texts(t)
545
546
547
548
549
550def text.of(hub: String, bs: List<&2, K.Item>, rest: List<&2, String>) -> String:
551 T.normal(String.join(T.render.sect(lock.sect(hub)) <>
552 List.append(&2, String, block.texts(bs), rest), "\n"))
553
554
555def render(hub: String, ps: List<&2, Pack>) -> String:
556 text.of(hub, K.file.sort(pack.blocks(ps)), [])
557
558
559
560def tool.pairs(+entry: String, +binary: String, source: M.Source) -> List<&2, T.Kv>:
561 match source:
562 case M.Hub{_named}:
563 List.concat(&2, T.Kv, [kv("entry", entry), kv("bin", binary)])
564 case M.Git{url, rev, tag, root, nar, _vend}:
565 List.concat(&2, T.Kv,
566 [kv("git", url), kv("rev", rev), kv("tag", tag), kv("root", root),
567 kv("narHash", nar), kv("entry", entry), kv("bin", binary)])
568 case M.Member{_path, _named}:
569 List.concat(&2, T.Kv, [kv("entry", entry), kv("bin", binary)])
570
571
572def tool.sect(pin: M.Tool) -> T.Sect:
573 M.Tool{n, e, b, s} = pin
574 T.Sect{"tools." ++ n, tool.pairs(e, b, s)}
575
576
577def tool.sects(ts: List<&2, M.Tool>) -> List<&2, T.Sect>:
578 match ts:
579 case []:
580 []
581 case h <> t:
582 tool.sect(h) <> tool.sects(t)
583
584
585
586def render.tools(ts: List<&2, M.Tool>, hub: String, ps: List<&2, Pack>) -> String:
587 text.of(hub, K.file.sort(pack.blocks(ps)), T.render.all(tool.sects(ts)))
588
589
590
591
592def name.file(name: Name) -> K.Item:
593 Name{nv, hash} = name
594 K.Item{nv, hash}
595
596
597def name.files(ns: List<&2, Name>) -> List<&2, K.Item>:
598 match ns:
599 case []:
600 []
601 case h <> t:
602 name.file(h) <> name.files(t)
603
604
605
606
607
608def names.sects(ns: List<&2, Name>) -> List<&2, T.Sect>:
609 match ns:
610 case []:
611 []
612 case +h <> +t:
613 [T.Sect{"names", file.pairs(K.files_of(name.files(h <> t)))}]
614
615
616def render.names(ns: List<&2, Name>, ts: List<&2, M.Tool>, hub: String, ps: List<&2, Pack>) -> String:
617 text.of(hub, K.file.sort(pack.blocks(ps)),
618 T.render.all(List.append(&2, T.Sect, names.sects(ns), tool.sects(ts))))
619
620
621def render.pick(empty: Bool, hub: String, ps: List<&2, Pack>, ts: List<&2, M.Tool>) -> String:
622 match empty:
623 case True{}:
624 render(hub, ps)
625 case False{}:
626 render.tools(ts, hub, ps)
627
628
629def lock.pairs(ss: List<&2, T.Sect>) -> List<&2, T.Kv>:
630 match ss:
631 case []:
632 []
633 case +h <> t:
634 at.pick(named1(segs(h), "lock"), h, lock.pairs(t))
635
636
637def lock.hub(ss: List<&2, T.Sect>) -> String:
638 T.value(lock.pairs(ss), "hub")
639
640
641def names.pairs(ss: List<&2, T.Sect>) -> List<&2, T.Kv>:
642 match ss:
643 case []:
644 []
645 case +h <> t:
646 at.pick(named1(segs(h), "names"), h, names.pairs(t))
647
648
649def pair.name(kv: T.Kv) -> Name:
650 T.Kv{k, v} = kv
651 Name{k, v}
652
653
654def pair.names(ps: List<&2, T.Kv>) -> List<&2, Name>:
655 match ps:
656 case []:
657 []
658 case h <> t:
659 pair.name(h) <> pair.names(t)
660
661
662def names(+ss: List<&2, T.Sect>) -> List<&2, Name>:
663 pair.names(names.pairs(ss))
664
665
666
667def names.read(text: String) -> List<&2, Name>:
668 names.valid(names(T.sects(T.parse(text))))
669
670
671def hash.put(hit: Bool, sect: T.Sect, rest: List<&2, String>) -> List<&2, String>:
672 match hit:
673 case True{}:
674 T.Sect{n, _ps} = sect
675 K.word(T.segments(n), 1n) <> rest
676 case False{}:
677 rest
678
679
680def hashes(ss: List<&2, T.Sect>) -> List<&2, String>:
681 match ss:
682 case []:
683 []
684 case +h <> t:
685 hash.put(named3(segs(h), "packages", "files"), h, hashes(t))
686
687
688def pair.file(kv: T.Kv) -> K.Item:
689 T.Kv{k, v} = kv
690 K.Item{k, v}
691
692
693def pair.files(ps: List<&2, T.Kv>) -> List<&2, K.Item>:
694 match ps:
695 case []:
696 []
697 case h <> t:
698 pair.file(h) <> pair.files(t)
699
700
701def pack_of(+ss: List<&2, T.Sect>, +key: String) -> Pack:
702 Pack{key, src_of(at3(ss, "packages", key, "source")),
703 pair.files(at3(ss, "packages", key, "files"))}
704
705
706def packs.at(+ss: List<&2, T.Sect>, hs: List<&2, String>) -> List<&2, Pack>:
707 match hs:
708 case []:
709 []
710 case h <> t:
711 pack_of(ss, h) <> packs.at(ss, t)
712
713
714def packs(+ss: List<&2, T.Sect>) -> List<&2, Pack>:
715 packs.at(ss, hashes(ss))
716
717
718def pack.src(package: Pack) -> Src:
719 Pack{_h, s, _f} = package
720 s
721
722
723def pack.files(package: Pack) -> List<&2, K.Item>:
724 Pack{_h, _s, f} = package
725 f
726
727
728def pack.manifest(package: Pack) -> String:
729 K.manifest_of(pack.files(package))
730
731
732def src.kind(source: Src) -> String:
733 match source:
734 case Hub{}:
735 "hub"
736 case Git{_u, _r, _e, _ro, _n, _t}:
737 "git"
738 case Member{_p, _e}:
739 "member"
740
741
742def src.rev(source: Src) -> String:
743 match source:
744 case Hub{}:
745 ""
746 case Git{_u, rev, _e, _ro, _n, _t}:
747 rev
748 case Member{_p, _e}:
749 ""
750
751
752def src.url(source: Src) -> String:
753 match source:
754 case Hub{}:
755 ""
756 case Git{url, _r, _e, _ro, _n, _t}:
757 url
758 case Member{_p, _e}:
759 ""
760
761
762def src.root(source: Src) -> String:
763 match source:
764 case Hub{}:
765 ""
766 case Git{_u, _r, _e, root, _n, _t}:
767 root
768 case Member{_p, _e}:
769 ""
770
771
772
773def src.nar(source: Src) -> String:
774 match source:
775 case Hub{}:
776 ""
777 case Git{_u, _r, _e, _ro, nar, _t}:
778 nar
779 case Member{_p, _e}:
780 ""