src/fetch/plan.bend
ezx@1.11.0.0 · 994 lines · 101 definitions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43import Base
44import ezx@1.11.0.0/src/toml/toml.bend as T
45import ezx@1.11.0.0/src/fetch/world.bend as F
46import ezx@1.11.0.0/src/lock/plan.bend as P
47import ezx@1.11.0.0/src/lock/world.bend as W
48import ezx@1.11.0.0/src/lock/up.bend as Up
49import ezx@1.11.0.0/src/lock/lock.bend as L
50import ezx@1.11.0.0/src/pkg/pkg.bend as K
51import ezx@1.11.0.0/src/pkg/path.bend as Path
52import ezx@1.11.0.0/src/share/sha.bend as Sha
53
54
55
56
57
58
59
60def fetch.ask(src: L.Src, +here: String, +hub: String, +hash: String) -> F.Ask:
61 match src:
62 case L.Hub{}:
63 F.Hub{W.Pkg{hash, L.Hub{}, hub}}
64 case L.Git{url, rev, _entry, _root, _nar, _tag}:
65 F.Git{hash, Up.Clone{Path.anchor(here, url), rev}}
66 case L.Member{_path, entry}:
67 F.Tree{hash, entry}
68
69
70
71
72
73
74type Tree is Data:
75 Tree{root: String, files: List<&2, K.Source>}
76 Gone{why: String}
77
78
79def zip(fs: List<&2, K.Item>, ss: List<&2, String>) -> List<&2, K.Source>:
80 match fs ss:
81 case Nil{} Nil{}:
82 []
83 case Nil{} Con{_s, _t}:
84 []
85 case Con{_f, _g} Nil{}:
86 []
87 case Con{f, g} Con{s, t}:
88 K.Source{K.file.at(f), s} <> zip(g, t)
89
90
91
92def tree.read(answer: W.Answer) -> Tree:
93 match answer:
94 case W.Got{_how, manifest, ss}:
95 Tree{"", zip(L.manifest.files(String.lines(manifest)), ss)}
96 case W.Miss{why}:
97 Gone{why}
98 case _:
99 Gone{"ez: a tree was not answered with one"}
100
101
102def tree.said(answer: Up.Answer, +root: String) -> Tree:
103 match answer:
104 case Up.Cloned{_nar, fs}:
105 Tree{root, fs}
106 case Up.Miss{why}:
107 Gone{why}
108 case _:
109 Gone{"ez: a checkout was not answered with one"}
110
111
112def tree.of(answer: F.Answer, +root: String) -> Tree:
113 match answer:
114 case F.Read{a}:
115 tree.read(a)
116 case F.Said{a}:
117 tree.said(a, root)
118 case F.Held{_got}:
119 Gone{"ez: a tree was not answered with one"}
120 case F.Seen{_listing}:
121 Gone{"ez: a tree was not answered with one"}
122
123
124def text.or(got: Maybe<&2, String>) -> String:
125 match got:
126 case None{}:
127 ""
128 case Some{text}:
129 text
130
131
132
133def key(+root: String, +at: String) -> String:
134 Bool.pick(String, String.is_empty(root), at, Path.join(root, at))
135
136
137
138
139def cand(fs: List<&2, K.Item>, +root: String, +tree: List<&2, K.Source>) -> List<&2, W.Source>:
140 match fs:
141 case []:
142 []
143 case K.Item{+at, _sum} <> t:
144 W.Source{at, text.or(K.look(tree, key(root, at)))} <> cand(t, root, tree)
145
146
147
148
149
150def weigh(src: W.Source) -> K.Item:
151 W.Source{at, +text} = src
152 K.Item{at, Sha.hex(text)}
153
154
155def weighs(ls: List<&2, W.Source>) -> List<&2, K.Item>:
156 match ls:
157 case []:
158 []
159 case h <> t:
160 weigh(h) <> weighs(t)
161
162
163
164def fit(want: K.Item, got: W.Source) -> Bool:
165 K.Item{+at, sum} = want
166 W.Source{lat, +text} = got
167 Bool.and(String.eq(lat, at), String.eq(Sha.hex(text), sum))
168
169
170
171def fits(fs: List<&2, K.Item>, ls: List<&2, W.Source>) -> Bool:
172 match fs ls:
173 case Nil{} Nil{}:
174 True{}
175 case Nil{} Con{_l, _m}:
176 False{}
177 case Con{_f, _g} Nil{}:
178 False{}
179 case Con{f, g} Con{l, m}:
180 +rest = fits(g, m)
181 Bool.and(fit(f, l), rest)
182
183
184
185
186def path.safe(+at: String) -> Bool:
187 Bool.and(String.eq(Path.norm(at), at),
188 Bool.not(Bool.or(K.escapes(at), String.eq(at, "."))))
189
190
191def paths.safe(ls: List<&2, W.Source>) -> Bool:
192 match ls:
193 case []:
194 True{}
195 case W.Source{+at, _text} <> t:
196 +rest = paths.safe(t)
197 Bool.and(path.safe(at), rest)
198
199
200def manifest(+ls: List<&2, W.Source>) -> String:
201 K.manifest_of(K.files_of(weighs(ls)))
202
203
204def named(+ls: List<&2, W.Source>) -> String:
205 K.hash_of(K.files_of(weighs(ls)))
206
207
208
209
210def good(fs: List<&2, K.Item>, +hash: String, +ls: List<&2, W.Source>) -> Bool:
211 +fit = fits(fs, ls)
212 +safe = paths.safe(ls)
213 Bool.and(fit, Bool.and(safe, String.eq(hash, named(ls))))
214
215
216
217def unfit.pick(ok: Bool, +at: String, rest: String) -> String:
218 match ok:
219 case True{}:
220 rest
221 case False{}:
222 at
223
224
225def unfit(fs: List<&2, K.Item>, ls: List<&2, W.Source>) -> String:
226 match fs ls:
227 case Nil{} Nil{}:
228 ""
229 case Nil{} Con{l, _m}:
230 W.Source{at, _text} = l
231 at
232 case Con{f, _g} Nil{}:
233 K.file.at(f)
234 case Con{+f, g} Con{l, m}:
235 unfit.pick(fit(f, l), K.file.at(f), unfit(g, m))
236
237
238def bad.why(fs: List<&2, K.Item>, +hash: String, +ls: List<&2, W.Source>) -> String:
239 +at = unfit(fs, ls)
240 Bool.pick(String, String.is_empty(at),
241 Bool.pick(String, paths.safe(ls),
242 "ez: " ++ hash ++ ": its files do not hash to its name",
243 "ez: " ++ hash ++ ": a path in the lock leaves the package"),
244 "ez: " ++ hash ++ ": " ++ at ++ " does not match the lock")
245
246
247
248
249
250
251
252type Part is Data:
253 AskLaid{}
254 AskFetch{}
255 Have{}
256 Got{files: List<&2, W.Source>}
257 No{why: String}
258
259
260def part.tree(tree: Tree, fs: List<&2, K.Item>) -> Part:
261 match tree:
262 case Tree{r, files}:
263 Got{cand(fs, r, files)}
264 case Gone{why}:
265 No{why}
266
267
268def part.fetch.heard(got: F.Heard, fs: List<&2, K.Item>, +root: String) -> Part:
269 match got:
270 case F.Open{}:
271 AskFetch{}
272 case F.Heard{answer}:
273 part.tree(tree.of(answer, root), fs)
274
275
276def files.of(ls: List<&2, W.Source>) -> List<&2, K.Source>:
277 match ls:
278 case []:
279 []
280 case W.Source{at, text} <> tl:
281 K.Source{at, text} <> files.of(tl)
282
283
284
285def member.same(
286 same: Bool,
287 +now: String,
288 wfs: List<&2, K.Source>,
289 fs: List<&2, K.Item>,
290 +path: String,
291 +hash: String
292) -> Part:
293 match same:
294 case True{}:
295 Got{cand(fs, "", wfs)}
296 case False{}:
297 No{"ez: workspace member " ++ path ++ " is " ++ now ++ " in the committed files, and the lock records " ++
298 hash ++ "; run ez lock"}
299
300
301def member.walked(got: K.Walked, fs: List<&2, K.Item>, +path: String, +hash: String) -> Part:
302 match got:
303 case K.Walked{+now, _root, wfs, _sums}:
304 member.same(String.eq(now, hash), now, wfs, fs, path, hash)
305 case K.Asks{at}:
306 No{"ez: workspace member " ++ path ++ " imports " ++ at ++ ", which git does not track"}
307 case K.Refused{why}:
308 No{why}
309
310
311def member.listed(listing: W.Listing, fs: List<&2, K.Item>, +path: String, +entry: String, +hash: String) -> Part:
312 match listing:
313 case W.Listed{ls}:
314 member.walked(K.of(entry, files.of(ls)), fs, path, hash)
315 case W.Unlisted{why}:
316 No{why}
317
318
319def member.answer(answer: F.Answer, fs: List<&2, K.Item>, +path: String, +entry: String, +hash: String) -> Part:
320 match answer:
321 case F.Seen{listing}:
322 member.listed(listing, fs, path, entry, hash)
323 case _:
324 No{"ez: workspace member " ++ path ++ " was not read from the workspace"}
325
326
327
328def part.member(got: F.Heard, fs: List<&2, K.Item>, +path: String, +entry: String, +hash: String) -> Part:
329 match got:
330 case F.Open{}:
331 AskFetch{}
332 case F.Heard{answer}:
333 member.answer(answer, fs, path, entry, hash)
334
335
336def part.fetch(
337 +rs: List<&2, F.Reply>,
338 +src: L.Src,
339 fs: List<&2, K.Item>,
340 +here: String,
341 +hub: String,
342 +hash: String
343) -> Part:
344 match src:
345 case L.Member{+path, +entry}:
346 part.member(F.heard(rs, F.Tree{hash, entry}), fs, path, entry, hash)
347 case L.Hub{}:
348 part.fetch.heard(F.heard(rs, fetch.ask(L.Hub{}, here, hub, hash)), fs, L.src.root(L.Hub{}))
349 case L.Git{url, rev, entry, root, nar, tag}:
350 part.fetch.heard(F.heard(rs, fetch.ask(L.Git{url, rev, entry, root, nar, tag}, here, hub, hash)), fs,
351 L.src.root(L.Git{url, rev, entry, root, nar, tag}))
352
353
354def part.kept(
355 ok: Bool,
356 +rs: List<&2, F.Reply>,
357 +src: L.Src,
358 fs: List<&2, K.Item>,
359 +here: String,
360 +hub: String,
361 +hash: String
362) -> Part:
363 match ok:
364 case True{}:
365 Have{}
366 case False{}:
367 part.fetch(rs, src, fs, here, hub, hash)
368
369
370
371def part.found(
372 tree: Tree,
373 +rs: List<&2, F.Reply>,
374 +src: L.Src,
375 +fs: List<&2, K.Item>,
376 +here: String,
377 +hub: String,
378 +hash: String
379) -> Part:
380 match tree:
381 case Tree{_r, files}:
382 part.kept(good(fs, hash, cand(fs, "", files)), rs, src, fs, here, hub, hash)
383 case Gone{_why}:
384 part.fetch(rs, src, fs, here, hub, hash)
385
386
387def part.laid(
388 got: F.Heard,
389 +rs: List<&2, F.Reply>,
390 +src: L.Src,
391 +fs: List<&2, K.Item>,
392 +here: String,
393 +hub: String,
394 +hash: String
395) -> Part:
396 match got:
397 case F.Open{}:
398 AskLaid{}
399 case F.Heard{answer}:
400 part.found(tree.of(answer, ""), rs, src, fs, here, hub, hash)
401
402
403def part(
404 +rs: List<&2, F.Reply>,
405 +src: L.Src,
406 +fs: List<&2, K.Item>,
407 +here: String,
408 +hub: String,
409 +hash: String
410) -> Part:
411 part.laid(F.heard(rs, F.Laid{hash}), rs, src, fs, here, hub, hash)
412
413
414
415
416
417
418def nar.why(
419 none: Bool,
420 same: Bool,
421 +hash: String,
422 +want: String,
423 +got: String,
424 +url: String,
425 +rev: String
426) -> String:
427 match none:
428 case True{}:
429 "ez: " ++ hash ++ ": the lock records no narHash for " ++ url ++ " at " ++ rev ++
430 ", so its checkout cannot be checked; run `ez lock --upgrade` to pin it"
431 case False{}:
432 match same:
433 case True{}:
434 ""
435 case False{}:
436 "ez: " ++ hash ++ ": " ++ url ++ " at " ++ rev ++ " has narHash " ++ got ++
437 ", and the lock records " ++ want
438
439
440def unweighed(+hash: String) -> String:
441 "ez: " ++ hash ++ ": its checkout was not weighed"
442
443
444def weighed.up(answer: Up.Answer, +hash: String, +nar: String, +url: String, +rev: String) -> String:
445 match answer:
446 case Up.Cloned{+got, _fs}:
447 nar.why(String.is_empty(nar), String.eq(got, nar), hash, nar, got, url, rev)
448 case _:
449 unweighed(hash)
450
451def weighed.answer(answer: F.Answer, +hash: String, +nar: String, +url: String, +rev: String) -> String:
452 match answer:
453 case F.Said{a}:
454 weighed.up(a, hash, nar, url, rev)
455 case F.Read{_a}:
456 unweighed(hash)
457 case F.Held{_got}:
458 unweighed(hash)
459 case F.Seen{_listing}:
460 unweighed(hash)
461
462def weighed.heard(got: F.Heard, +hash: String, +nar: String, +url: String, +rev: String) -> String:
463 match got:
464 case F.Heard{a}:
465 weighed.answer(a, hash, nar, url, rev)
466 case F.Open{}:
467 unweighed(hash)
468
469def weighed.src(src: L.Src, +rs: List<&2, F.Reply>, +here: String, +hash: String) -> String:
470 match src:
471 case L.Hub{}:
472 ""
473 case L.Git{+url, +rev, _entry, _root, nar, _tag}:
474 weighed.heard(F.heard(rs, F.Git{hash, Up.Clone{Path.anchor(here, url), rev}}),
475 hash, nar, url, rev)
476 case L.Member{_path, _entry}:
477 ""
478
479
480
481
482
483
484def weighed(+ss: List<&2, T.Sect>, +rs: List<&2, F.Reply>, +here: String, +hash: String) -> String:
485 weighed.src(L.pack.src(L.pack_of(ss, hash)), rs, here, hash)
486
487def weigh.got(ok: Bool, +why: String, ls: List<&2, W.Source>) -> Part:
488 match ok:
489 case True{}:
490 Got{ls}
491 case False{}:
492 No{why}
493
494
495
496
497def weigh.part(+why: String, part: Part) -> Part:
498 match part:
499 case AskLaid{}:
500 AskLaid{}
501 case AskFetch{}:
502 AskFetch{}
503 case Have{}:
504 Have{}
505 case Got{ls}:
506 weigh.got(String.is_empty(why), why, ls)
507 case No{w}:
508 No{w}
509
510
511type Pt is Data:
512 Pt{hash: String, src: L.Src, files: List<&2, K.Item>, part: Part}
513
514
515def parts(
516 +ss: List<&2, T.Sect>,
517 +rs: List<&2, F.Reply>,
518 +here: String,
519 +hub: String,
520 hs: List<&2, String>
521) -> List<&2, Pt>:
522 match hs:
523 case []:
524 []
525 case +h <> t:
526 +pk = L.pack_of(ss, h)
527 +src = L.pack.src(pk)
528 +fs = L.pack.files(pk)
529 Pt{h, src, fs, weigh.part(weighed(ss, rs, here, h), part(rs, src, fs, here, hub, h))}
530 <> parts(ss, rs, here, hub, t)
531
532
533
534
535
536
537
538type Nm is Data:
539 NmAsk{nv: String}
540 NmKeep{}
541 NmPut{nv: String, hash: String, was: String}
542 NmStop{why: String}
543
544
545
546def name.held(same: Bool, +nv: String, +hash: String, +was: String) -> Nm:
547 match same:
548 case True{}:
549 NmKeep{}
550 case False{}:
551 NmPut{nv, hash, was}
552
553
554def name.got(got: Maybe<&2, String>, +nv: String, +hash: String) -> Nm:
555 match got:
556 case None{}:
557 NmPut{nv, hash, ""}
558 case Some{text}:
559 +was = String.trim(text)
560 name.held(String.eq(was, hash), nv, hash, was)
561
562
563def name.answer(answer: F.Answer, +nv: String, +hash: String) -> Nm:
564 match answer:
565 case F.Held{got}:
566 name.got(got, nv, hash)
567 case _:
568 NmStop{"ez: " ++ nv ++ ": its names file was not read"}
569
570def name.heard(got: F.Heard, +nv: String, +hash: String) -> Nm:
571 match got:
572 case F.Open{}:
573 NmAsk{nv}
574 case F.Heard{a}:
575 name.answer(a, nv, hash)
576
577
578
579def name.valid(ok: Bool, +rs: List<&2, F.Reply>, +nv: String, +hash: String) -> Nm:
580 match ok:
581 case True{}:
582 name.heard(F.heard(rs, F.Named{nv}), nv, hash)
583 case False{}:
584 NmStop{"ez: the lock names " ++ nv ++ " as " ++ hash ++
585 ", which bend would not read back; run ez lock"}
586
587
588def name.part(+rs: List<&2, F.Reply>, name: L.Name) -> Nm:
589 L.Name{+nv, +hash} = name
590 name.valid(L.name.valid(L.Name{nv, hash}), rs, nv, hash)
591
592
593def name.parts(+rs: List<&2, F.Reply>, ns: List<&2, L.Name>) -> List<&2, Nm>:
594 match ns:
595 case []:
596 []
597 case h <> t:
598 name.part(rs, h) <> name.parts(rs, t)
599
600
601def name.rewrote(+nv: String, +hash: String, +was: String) -> String:
602 "ez: BEND_LIB/names/" ++ nv ++ " named " ++ was ++ ", and the lock names " ++ hash ++
603 "; rewrote it"
604
605
606def name.put.at(none: Bool, +nv: String, +hash: String, was: String, rest: List<&2, P.Effect>) -> List<&2, P.Effect>:
607 match none:
608 case True{}:
609 P.Name{nv, hash} <> rest
610 case False{}:
611 P.Say{name.rewrote(nv, hash, was)} <> P.Name{nv, hash} <> rest
612
613def name.put(+nv: String, +hash: String, +was: String, rest: List<&2, P.Effect>) -> List<&2, P.Effect>:
614 name.put.at(String.is_empty(was), nv, hash, was, rest)
615
616
617def names.lay(ms: List<&2, Nm>, rest: List<&2, P.Effect>) -> List<&2, P.Effect>:
618 match ms:
619 case []:
620 rest
621 case m <> t:
622 match m:
623 case NmPut{nv, hash, was}:
624 name.put(nv, hash, was, names.lay(t, rest))
625 case _:
626 names.lay(t, rest)
627
628
629
630def names.outcome(ms: List<&2, Nm>) -> P.Outcome:
631 match ms:
632 case []:
633 P.Success{}
634 case m <> t:
635 match m:
636 case NmStop{why}:
637 P.Refused{why}
638 case NmAsk{nv}:
639 P.Refused{"ez: " ++ nv ++ " was never looked for under BEND_LIB"}
640 case _:
641 names.outcome(t)
642
643
644def outcome.then(first: P.Outcome, then: P.Outcome) -> P.Outcome:
645 match first:
646 case P.Success{}:
647 then
648 case P.Refused{why}:
649 P.Refused{why}
650
651
652def names.asks(ms: List<&2, Nm>) -> List<&2, F.Ask>:
653 match ms:
654 case []:
655 []
656 case m <> t:
657 match m:
658 case NmAsk{nv}:
659 F.Named{nv} <> names.asks(t)
660 case _:
661 names.asks(t)
662
663
664def name.parts.of(+ss: List<&2, T.Sect>, +world: F.World) -> List<&2, Nm>:
665 name.parts(F.replies(world), L.names(ss))
666
667
668
669
670
671type Verdict is Data:
672 Keep{}
673 Put{hash: String, files: List<&2, W.Source>}
674 Stop{why: String}
675
676
677def judge.got(ok: Bool, fs: List<&2, K.Item>, +hash: String, +ls: List<&2, W.Source>) -> Verdict:
678 match ok:
679 case True{}:
680 Put{hash, ls}
681 case False{}:
682 Stop{bad.why(fs, hash, ls)}
683
684
685def judge(+fs: List<&2, K.Item>, +hash: String, part: Part) -> Verdict:
686 match part:
687 case AskLaid{}:
688 Stop{"ez: " ++ hash ++ " was never looked for under BEND_LIB"}
689 case AskFetch{}:
690 Stop{"ez: " ++ hash ++ " was never fetched"}
691 case Have{}:
692 Keep{}
693 case Got{+ls}:
694 judge.got(good(fs, hash, ls), fs, hash, ls)
695 case No{why}:
696 Stop{why}
697
698
699def judged(ps: List<&2, Pt>) -> List<&2, Verdict>:
700 match ps:
701 case []:
702 []
703 case Pt{h, _src, fs, p} <> t:
704 judge(fs, h, p) <> judged(t)
705
706
707
708def lay(+hash: String, +ls: List<&2, W.Source>) -> P.Effect:
709 P.Lay{P.Cache{}, hash, List.append(&2, W.Source, ls, [W.Source{"manifest", manifest(ls)}])}
710
711
712def effect.v(verdict: Verdict, rest: List<&2, P.Effect>) -> List<&2, P.Effect>:
713 match verdict:
714 case Keep{}:
715 rest
716 case Put{hash, ls}:
717 lay(hash, ls) <> rest
718 case Stop{_why}:
719 rest
720
721
722def effects.of(vs: List<&2, Verdict>) -> List<&2, P.Effect>:
723 match vs:
724 case []:
725 []
726 case v <> t:
727 effect.v(v, effects.of(t))
728
729
730def outcome.of(vs: List<&2, Verdict>) -> P.Outcome:
731 match vs:
732 case []:
733 P.Success{}
734 case v <> t:
735 match v:
736 case Stop{why}:
737 P.Refused{why}
738 case _:
739 outcome.of(t)
740
741
742def seal(es: List<&2, P.Effect>, outcome: P.Outcome) -> P.Plan:
743 match outcome:
744 case P.Success{}:
745 P.Plan{es, P.Success{}}
746 case P.Refused{why}:
747 P.Plan{[], P.Refused{why}}
748
749
750
751
752def parts.plan(ps: List<&2, Pt>, +ms: List<&2, Nm>) -> P.Plan:
753 +vs = judged(ps)
754 seal(names.lay(ms, effects.of(vs)), outcome.then(outcome.of(vs), names.outcome(ms)))
755
756
757
758
759
760def parts.of(+ss: List<&2, T.Sect>, +world: F.World) -> List<&2, Pt>:
761 parts(ss, F.replies(world), F.here(world), L.lock.hub(ss), L.hashes(ss))
762
763
764def need.no() -> String:
765 "ez: no " ++ F.toml() ++ " here; ez fetch fills BEND_LIB for a project's lock, " ++
766 "and ez init makes one"
767
768def need.lock() -> String:
769 "ez: no " ++ F.lockfile() ++ " here; ez fetch reads the lock and resolves " ++
770 "nothing, and ez lock writes it"
771
772
773def plan.lock(got: Maybe<&2, String>, +world: F.World) -> P.Plan:
774 match got:
775 case None{}:
776 P.Plan{[], P.Refused{need.lock()}}
777 case Some{_text}:
778 +ss = F.sects(world)
779 parts.plan(parts.of(ss, world), name.parts.of(ss, world))
780
781
782def plan.led(led: Bool, got: Maybe<&2, String>, +world: F.World) -> P.Plan:
783 match led:
784 case False{}:
785 P.Plan{[], P.Refused{need.no()}}
786 case True{}:
787 plan.lock(got, world)
788
789
790def plan(+world: F.World) -> P.Plan:
791 plan.led(F.ledger(world), F.lock(world), world)
792
793
794def refuses.plan(pl: P.Plan) -> Bool:
795 P.Plan{_es, outcome} = pl
796 match outcome:
797 case P.Success{}:
798 False{}
799 case P.Refused{_why}:
800 True{}
801
802
803def refuses(+world: F.World) -> Bool:
804 refuses.plan(plan(world))
805
806
807
808
809
810def ask.pt(pt: Pt, +here: String, +hub: String, rest: List<&2, F.Ask>) -> List<&2, F.Ask>:
811 Pt{+h, src, _fs, p} = pt
812 match p:
813 case AskLaid{}:
814 F.Laid{h} <> rest
815 case AskFetch{}:
816 fetch.ask(src, here, hub, h) <> rest
817 case _:
818 rest
819
820
821def asks.of(ps: List<&2, Pt>, +here: String, +hub: String) -> List<&2, F.Ask>:
822 match ps:
823 case []:
824 []
825 case p <> t:
826 ask.pt(p, here, hub, asks.of(t, here, hub))
827
828
829def wants.lock(got: Maybe<&2, String>, +world: F.World) -> List<&2, F.Ask>:
830 match got:
831 case None{}:
832 []
833 case Some{_text}:
834 +ss = F.sects(world)
835 List.append(&2, F.Ask, asks.of(parts.of(ss, world), F.here(world), L.lock.hub(ss)),
836 names.asks(name.parts.of(ss, world)))
837
838def wants.led(led: Bool, got: Maybe<&2, String>, +world: F.World) -> List<&2, F.Ask>:
839 match led:
840 case False{}:
841 []
842 case True{}:
843 wants.lock(got, world)
844
845
846
847
848def wants(+world: F.World) -> List<&2, F.Ask>:
849 wants.led(F.ledger(world), F.lock(world), world)
850
851
852type Step is Data:
853 Asking{asks: List<&2, F.Ask>}
854 Run{plan: P.Plan}
855
856def step.asks(asks: List<&2, F.Ask>, ps: List<&2, Pt>, ms: List<&2, Nm>) -> Step:
857 match asks:
858 case []:
859 Run{parts.plan(ps, ms)}
860 case h <> t:
861 Asking{h <> t}
862
863def step.lock(got: Maybe<&2, String>, +world: F.World) -> Step:
864 match got:
865 case None{}:
866 Run{P.Plan{[], P.Refused{need.lock()}}}
867 case Some{_text}:
868 +ss = F.sects(world)
869 +ps = parts.of(ss, world)
870 +ms = name.parts.of(ss, world)
871 step.asks(List.append(&2, F.Ask, asks.of(ps, F.here(world), L.lock.hub(ss)), names.asks(ms)),
872 ps, ms)
873
874def step.led(led: Bool, got: Maybe<&2, String>, +world: F.World) -> Step:
875 match led:
876 case False{}:
877 Run{P.Plan{[], P.Refused{need.no()}}}
878 case True{}:
879 step.lock(got, world)
880
881
882
883def step(+world: F.World) -> Step:
884 step.led(F.ledger(world), F.lock(world), world)
885
886
887
888
889
890def effects(pl: P.Plan) -> List<&2, P.Effect>:
891 P.Plan{es, _outcome} = pl
892 es
893
894
895def front(fs: List<&2, W.Source>) -> List<&2, W.Source>:
896 match fs:
897 case []:
898 []
899 case h <> t:
900 match t:
901 case []:
902 []
903 case h2 <> t2:
904 h <> front(h2 <> t2)
905
906
907def locked(+ss: List<&2, T.Sect>, +hash: String) -> List<&2, K.Item>:
908 L.pack.files(L.pack_of(ss, hash))
909
910
911
912def lays.one(+ss: List<&2, T.Sect>, effect: P.Effect) -> Bool:
913 match effect:
914 case P.Lay{_place, hash, fs}:
915 fits(locked(ss, hash), front(fs))
916 case _:
917 True{}
918
919
920def lays.locked(+ss: List<&2, T.Sect>, es: List<&2, P.Effect>) -> Bool:
921 match es:
922 case []:
923 True{}
924 case h <> t:
925 +rest = lays.locked(ss, t)
926 Bool.and(lays.one(ss, h), rest)
927
928
929def clone.url(ask: Up.Ask) -> String:
930 match ask:
931 case Up.Clone{url, _rev}:
932 url
933 case _:
934 ""
935
936
937def ask.hash(ask: F.Ask) -> String:
938 match ask:
939 case F.Laid{hash}:
940 hash
941 case F.Git{hash, _q}:
942 hash
943 case F.Hub{q}:
944 W.ask.hash(q)
945 case F.Named{nv}:
946 nv
947 case F.Tree{hash, _entry}:
948 hash
949
950
951
952def ask.anchored(+here: String, src: L.Src, ask: F.Ask) -> Bool:
953 match ask:
954 case F.Git{_hash, q}:
955 String.eq(clone.url(q), Path.anchor(here, L.src.url(src)))
956 case _:
957 True{}
958
959
960def asks.anchored(+here: String, +ss: List<&2, T.Sect>, as: List<&2, F.Ask>) -> Bool:
961 match as:
962 case []:
963 True{}
964 case +h <> t:
965 +rest = asks.anchored(here, ss, t)
966 Bool.and(ask.anchored(here, L.pack.src(L.pack_of(ss, ask.hash(h))), h), rest)
967
968
969
970def weighed.lay(
971 +ss: List<&2, T.Sect>,
972 +rs: List<&2, F.Reply>,
973 +here: String,
974 effect: P.Effect
975) -> Bool:
976 match effect:
977 case P.Lay{_place, hash, _fs}:
978 String.is_empty(weighed(ss, rs, here, hash))
979 case _:
980 True{}
981
982
983def lays.weighed(
984 +ss: List<&2, T.Sect>,
985 +rs: List<&2, F.Reply>,
986 +here: String,
987 es: List<&2, P.Effect>
988) -> Bool:
989 match es:
990 case []:
991 True{}
992 case h <> t:
993 +rest = lays.weighed(ss, rs, here, t)
994 Bool.and(weighed.lay(ss, rs, here, h), rest)