src/doctor/plan.bend
ezx@1.11.0.0 · 1,041 lines · 112 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
43
44
45
46
47
48
49
50
51
52import Base
53import ezx@1.11.0.0/src/toml/toml.bend as T
54import ezx@1.11.0.0/src/doctor/world.bend as DW
55import ezx@1.11.0.0/src/lock/plan.bend as P
56import ezx@1.11.0.0/src/lock/world.bend as W
57import ezx@1.11.0.0/src/lock/lock.bend as L
58import ezx@1.11.0.0/src/ledger/manifest.bend as M
59import ezx@1.11.0.0/src/share/args.bend as Args
60
61
62type Note is Data:
63 Note{wrong: Bool, line: String}
64
65
66def plural(+count: Nat, one: String, many: String) -> String:
67 Nat.show(count) ++ " " ++ Bool.pick(String, Nat.is_eq(count, 1n), one, many)
68
69
70
71def answered(ok: Bool, +text: String) -> List<&2, String>:
72 match ok:
73 case True{}:
74 Args.nonempty(String.lines(text))
75 case False{}:
76 []
77
78
79
80
81
82def banner.of(ls: List<&2, String>) -> String:
83 match ls:
84 case []:
85 ""
86 case h <> _t:
87 h
88
89
90def banner(text: String) -> String:
91 banner.of(String.lines(text))
92
93
94def probe.note(ok: Bool, name: String, +text: String) -> Note:
95 match ok:
96 case True{}:
97 Note{False{}, name ++ ": " ++ banner(text)}
98 case False{}:
99 Note{True{}, name ++ ": not on PATH"}
100
101
102def probe.answer(answer: DW.Answer, +name: String) -> Note:
103 match answer:
104 case DW.Printed{ok, text}:
105 probe.note(ok, name, text)
106 case _:
107 Note{True{}, name ++ ": not on PATH"}
108
109
110def probe.heard(got: DW.Heard, +name: String) -> Note:
111 match got:
112 case DW.Heard{a}:
113 probe.answer(a, name)
114 case DW.Open{}:
115 Note{True{}, name ++ ": not on PATH"}
116
117
118
119
120def cc.name(+var: String) -> String:
121 Bool.pick(String, String.is_empty(var), "cc", var)
122
123
124
125
126def programs(+cc: String) -> List<&2, String>:
127 ["bend", cc.name(cc), "git"]
128
129
130def probes(ns: List<&2, String>, +rs: List<&2, DW.Reply>) -> List<&2, Note>:
131 match ns:
132 case []:
133 []
134 case +h <> t:
135 probe.heard(DW.heard(rs, DW.Probe{h}), h) <> probes(t, rs)
136
137
138
139
140
141def deps.of(manifest: M.Manifest) -> List<&2, M.Dep>:
142 M.Manifest{_n, _e, _b, _h, _pa, _pv, ds, _ts} = manifest
143 ds
144
145
146def deps.count(ledger: M.Read) -> Nat:
147 match ledger:
148 case M.Bad{_why}:
149 0n
150 case M.Good{m}:
151 List.length(&2, M.Dep, deps.of(m))
152
153
154def ledger.note(ledger: M.Read) -> Note:
155 match ledger:
156 case M.Bad{why}:
157 Note{True{}, DW.toml() ++ ": " ++ why}
158 case M.Good{m}:
159 M.Manifest{n, e, _b, _h, _pa, _pv, ds, _ts} = m
160 Note{False{}, DW.toml() ++ ": " ++ n ++ ", entry " ++ e ++ ", " ++
161 plural(List.length(&2, M.Dep, ds), "dependency", "dependencies")}
162
163
164
165type Locked is Data:
166 Locked{hashes: List<&2, String>}
167 Unread{why: String}
168 Unlocked{}
169
170
171def locked.pick(ok: Bool, why: String, ss: List<&2, T.Sect>) -> Locked:
172 match ok:
173 case True{}:
174 Locked{L.hashes(ss)}
175 case False{}:
176 Unread{why}
177
178
179def locked.parsed(toml: T.Toml) -> Locked:
180 T.Toml{+bad, _name, _pairs, ss} = toml
181 locked.pick(String.is_empty(bad), bad, ss)
182
183
184def locked.of(got: Maybe<&2, String>) -> Locked:
185 match got:
186 case None{}:
187 Unlocked{}
188 case Some{text}:
189 locked.parsed(T.parse(text))
190
191
192def locked.hashes(lk: Locked) -> List<&2, String>:
193 match lk:
194 case Locked{hs}:
195 hs
196 case Unread{_why}:
197 []
198 case Unlocked{}:
199 []
200
201
202def lock.needs(deps: Nat) -> Bool:
203 match deps:
204 case 0n:
205 False{}
206 case 1n+_n:
207 True{}
208
209
210
211
212def lock.wrong(deps: Nat, hs: List<&2, String>) -> Bool:
213 match hs:
214 case []:
215 lock.needs(deps)
216 case _h <> _t:
217 False{}
218
219
220
221
222def lock.note(+deps: Nat, lk: Locked) -> Note:
223 match lk:
224 case Locked{+hs}:
225 +wrong = lock.wrong(deps, hs)
226 Note{wrong,
227 DW.lockfile() ++ ": " ++ plural(List.length(&2, String, hs), "package", "packages") ++
228 Bool.pick(String, wrong, " (it names none; run ez lock)", "")}
229 case Unread{why}:
230 Note{True{}, DW.lockfile() ++ ": " ++ why}
231 case Unlocked{}:
232 +wrong = lock.needs(deps)
233 Note{wrong, DW.lockfile() ++ ": " ++
234 Bool.pick(String, wrong, "none; run ez lock", "none, and nothing to lock")}
235
236
237
238
239
240
241
242def lib.at(+var: String) -> String:
243 Bool.pick(String, String.is_empty(var), ".ez/lib", var)
244
245
246def lib.have(got: DW.Heard) -> List<&2, String>:
247 match got:
248 case DW.Heard{a}:
249 match a:
250 case DW.Printed{ok, text}:
251 answered(ok, text)
252 case _:
253 []
254 case DW.Open{}:
255 []
256
257
258
259
260def has.step(here: Bool, rest: Unit -> Bool) -> Bool:
261 match here:
262 case True{}:
263 True{}
264 case False{}:
265 rest(Unit{})
266
267
268def has(ss: List<&2, String>, +want: String) -> Bool:
269 match ss:
270 case []:
271 False{}
272 case +h <> t:
273 has.step(String.eq(h, want), _u => has(t, want))
274
275
276def missing.put(hit: Bool, item: String, rest: List<&2, String>) -> List<&2, String>:
277 match hit:
278 case True{}:
279 rest
280 case False{}:
281 item <> rest
282
283
284def missing(xs: List<&2, String>, +known: List<&2, String>) -> List<&2, String>:
285 match xs:
286 case []:
287 []
288 case +h <> t:
289 missing.put(has(known, h), h, missing(t, known))
290
291
292
293def lock.names(lock: Maybe<&2, String>) -> List<&2, L.Name>:
294 match lock:
295 case None{}:
296 []
297 case Some{text}:
298 L.names.read(text)
299
300
301
302def name.held(same: Bool, +nv: String, +hash: String, +was: String) -> List<&2, String>:
303 match same:
304 case True{}:
305 []
306 case False{}:
307 ["names/" ++ nv ++ " names " ++ was ++ " where the lock names " ++ hash]
308
309def name.got(got: Maybe<&2, String>, +nv: String, +hash: String) -> List<&2, String>:
310 match got:
311 case None{}:
312 ["names/" ++ nv ++ " is not there"]
313 case Some{text}:
314 +was = String.trim(text)
315 name.held(String.eq(was, hash), nv, hash, was)
316
317def name.heard(got: DW.Heard, +nv: String, +hash: String) -> List<&2, String>:
318 match got:
319 case DW.Heard{a}:
320 match a:
321 case DW.Held{m}:
322 name.got(m, nv, hash)
323 case _:
324 ["names/" ++ nv ++ " was not read"]
325 case DW.Open{}:
326 ["names/" ++ nv ++ " was not read"]
327
328
329
330
331def names.wrong(ns: List<&2, L.Name>, +rs: List<&2, DW.Reply>) -> List<&2, String>:
332 match ns:
333 case []:
334 []
335 case L.Name{+nv, +hash} <> t:
336 List.append(&2, String, name.heard(DW.heard(rs, DW.Name{nv}), nv, hash), names.wrong(t, rs))
337
338
339def names.said(+count: Nat) -> String:
340 match count:
341 case 0n:
342 ""
343 case 1n+_p:
344 " with their " ++ plural(count, "name", "names")
345
346
347def lib.faults(gone: List<&2, String>, bad: List<&2, String>) -> String:
348 match gone:
349 case []:
350 String.join(bad, "; ")
351 case _g <> _t:
352 match bad:
353 case []:
354 "lacks " ++ String.join(gone, " ")
355 case _b <> _u:
356 "lacks " ++ String.join(gone, " ") ++ "; " ++ String.join(bad, "; ")
357
358
359
360
361
362def lib.said(all: Bool, +dir: String, +count: Nat, +names: Nat, gone: List<&2, String>, bad: List<&2, String>) -> Note:
363 match all:
364 case True{}:
365 Note{False{}, "BEND_LIB: " ++ dir ++ ", all " ++
366 plural(count, "locked package", "locked packages") ++ " present" ++ names.said(names)}
367 case False{}:
368 Note{True{}, "BEND_LIB: " ++ dir ++ " " ++ lib.faults(gone, bad) ++ "; run ez fetch"}
369
370
371def lib.judge(
372 +dir: String,
373 +want: List<&2, String>,
374 +ns: List<&2, L.Name>,
375 have: List<&2, String>,
376 +rs: List<&2, DW.Reply>
377) -> Note:
378 +gone = missing(want, have)
379 +bad = names.wrong(ns, rs)
380 lib.said(Bool.and(List.is_empty(&2, String, gone), List.is_empty(&2, String, bad)), dir,
381 List.length(&2, String, want), List.length(&2, L.Name, ns), gone, bad)
382
383
384def lib.note(+dir: String, +want: List<&2, String>, +ns: List<&2, L.Name>, +rs: List<&2, DW.Reply>) -> Note:
385 lib.judge(dir, want, ns, lib.have(DW.heard(rs, DW.Lib{dir})), rs)
386
387
388
389
390
391def once.put(dup: Bool, item: String, rest: List<&2, String>) -> List<&2, String>:
392 match dup:
393 case True{}:
394 rest
395 case False{}:
396 item <> rest
397
398
399def once(ss: List<&2, String>) -> List<&2, String>:
400 match ss:
401 case []:
402 []
403 case +h <> +t:
404 once.put(has(t, h), h, once(t))
405
406
407
408
409def imports(+fs: List<&2, W.Source>) -> List<&2, String>:
410 once(List.append(&2, String, P.roots(fs), P.roots.named(fs)))
411
412
413def pinned.at(none: Bool, nv: String, rest: List<&2, String>) -> List<&2, String>:
414 match none:
415 case True{}:
416 rest
417 case False{}:
418 nv <> rest
419
420def pinned.nv(+nv: String, rest: List<&2, String>) -> List<&2, String>:
421 pinned.at(String.is_empty(nv), nv, rest)
422
423
424def pinned(ds: List<&2, M.Dep>) -> List<&2, String>:
425 match ds:
426 case []:
427 []
428 case +h <> t:
429 M.dep.hash(h) <> pinned.nv(M.source.nv(M.source.dep(h)), pinned(t))
430
431
432def used(+hs: List<&2, String>, +dependency: M.Dep) -> Bool:
433 +nv = M.source.nv(M.source.dep(dependency))
434 Bool.or(has(hs, M.dep.hash(dependency)),
435 Bool.and(Bool.not(String.is_empty(nv)), has(hs, nv)))
436
437
438def unrecorded.line(+hash: String) -> String:
439 "drift: " ++ hash ++ " is imported but ez.toml does not name it"
440
441
442def unrecorded(hs: List<&2, String>) -> List<&2, String>:
443 match hs:
444 case []:
445 []
446 case h <> t:
447 unrecorded.line(h) <> unrecorded(t)
448
449
450def unused.line(+dependency: M.Dep) -> String:
451 "drift: " ++ M.dep.name(dependency) ++ " = " ++ M.dep.hash(dependency) ++
452 " is in ez.toml but no import line uses it"
453
454
455def unused.put(hit: Bool, +dependency: M.Dep, rest: List<&2, String>) -> List<&2, String>:
456 match hit:
457 case True{}:
458 rest
459 case False{}:
460 unused.line(dependency) <> rest
461
462
463def unused(ds: List<&2, M.Dep>, +hs: List<&2, String>) -> List<&2, String>:
464 match ds:
465 case []:
466 []
467 case +h <> t:
468 unused.put(used(hs, h), h, unused(t, hs))
469
470
471
472def drift(+hs: List<&2, String>, +ds: List<&2, M.Dep>) -> List<&2, String>:
473 List.append(&2, String, unrecorded(missing(hs, pinned(ds))), unused(ds, hs))
474
475
476def flag(ls: List<&2, String>) -> List<&2, Note>:
477 match ls:
478 case []:
479 []
480 case h <> t:
481 Note{True{}, h} <> flag(t)
482
483
484def drift.notes(+hs: List<&2, String>, lines: List<&2, String>) -> List<&2, Note>:
485 match lines:
486 case []:
487 [Note{False{}, "imports: " ++ plural(List.length(&2, String, hs), "package", "packages") ++
488 " imported, every one named in ez.toml"}]
489 case h <> t:
490 flag(h <> t)
491
492
493def listing.notes(listing: W.Listing, +ds: List<&2, M.Dep>) -> List<&2, Note>:
494 match listing:
495 case W.Listed{fs}:
496 +hs = imports(fs)
497 drift.notes(hs, drift(hs, ds))
498 case W.Unlisted{_why}:
499 [Note{True{}, "imports: git could not list the .bend files it tracks here"}]
500
501
502def sources.notes(got: DW.Heard, +ds: List<&2, M.Dep>) -> List<&2, Note>:
503 match got:
504 case DW.Heard{a}:
505 match a:
506 case DW.Listed{l}:
507 listing.notes(l, ds)
508 case _:
509 [Note{True{}, "imports: the sources were not read"}]
510 case DW.Open{}:
511 [Note{True{}, "imports: the sources were not read"}]
512
513
514
515def imports.notes(ledger: M.Read, got: DW.Heard) -> List<&2, Note>:
516 match ledger:
517 case M.Bad{_why}:
518 [Note{False{}, "imports: not compared, since ez.toml was not read"}]
519 case M.Good{m}:
520 sources.notes(got, deps.of(m))
521
522
523
524
525
526
527
528
529
530
531def tree.how(src: L.Src) -> W.How:
532 match src:
533 case L.Hub{}:
534 W.Served{}
535 case L.Git{_url, _rev, _entry, _root, _nar, _tag}:
536 W.Lib{}
537 case L.Member{_path, _entry}:
538 W.Lib{}
539
540
541def ask.src(ask: W.Ask) -> L.Src:
542 match ask:
543 case W.Pkg{_hash, src, _hub}:
544 src
545 case W.Name{_nv, _hub}:
546 L.Hub{}
547 case W.Lock{}:
548 L.Hub{}
549
550
551
552def tree.put(+ask: W.Ask, answer: DW.Answer, rest: List<&2, W.Reply>) -> List<&2, W.Reply>:
553 match answer:
554 case DW.Laid{manifest, ss}:
555 W.Reply{ask, W.Got{tree.how(ask.src(ask)), manifest, ss}} <> rest
556 case _:
557 rest
558
559
560def tree.reply(ask: DW.Ask, answer: DW.Answer, rest: List<&2, W.Reply>) -> List<&2, W.Reply>:
561 match ask:
562 case DW.Tree{a}:
563 tree.put(a, answer, rest)
564 case _:
565 rest
566
567
568def trees(rs: List<&2, DW.Reply>) -> List<&2, W.Reply>:
569 match rs:
570 case []:
571 []
572 case DW.Reply{ask, answer} <> t:
573 tree.reply(ask, answer, trees(t))
574
575
576def lock.text(lock: Maybe<&2, String>) -> String:
577 match lock:
578 case None{}:
579 ""
580 case Some{text}:
581 text
582
583
584
585
586def name.put(+nv: String, got: Maybe<&2, String>, rest: List<&2, W.Reply>) -> List<&2, W.Reply>:
587 match got:
588 case Some{text}:
589 W.Reply{W.Name{nv, "BEND_LIB/names"}, W.Said{text}} <> rest
590 case None{}:
591 rest
592
593
594def name.reply(ask: DW.Ask, answer: DW.Answer, rest: List<&2, W.Reply>) -> List<&2, W.Reply>:
595 match ask:
596 case DW.Name{nv}:
597 match answer:
598 case DW.Held{got}:
599 name.put(nv, got, rest)
600 case _:
601 rest
602 case _:
603 rest
604
605
606def held(rs: List<&2, DW.Reply>) -> List<&2, W.Reply>:
607 match rs:
608 case []:
609 []
610 case DW.Reply{ask, answer} <> t:
611 name.reply(ask, answer, held(t))
612
613
614
615
616
617
618
619def relock.world(
620 ledger: Maybe<&2, String>,
621 lock: Maybe<&2, String>,
622 listing: W.Listing,
623 +rs: List<&2, DW.Reply>
624) -> W.World:
625 W.World{W.Args{False{}, "", ""}, ledger, listing,
626 List.append(&2, W.Reply, trees(rs), W.Reply{W.Lock{}, W.Locked{lock.text(lock)}} <> held(rs)),
627 []}
628
629
630def relock(+world: DW.World, fs: List<&2, W.Source>) -> W.World:
631 relock.world(DW.ledger(world), DW.lock(world), W.Listed{fs}, DW.replies(world))
632
633
634def fresh.line() -> String:
635 DW.lockfile() ++ ": up to date with ez.toml and the sources"
636
637
638def stale.line() -> String:
639 DW.lockfile() ++ ": out of date with ez.toml and the sources; run ez lock"
640
641
642def fresh.said(same: Bool) -> Note:
643 match same:
644 case True{}:
645 Note{False{}, fresh.line()}
646 case False{}:
647 Note{True{}, stale.line()}
648
649
650
651def fresh.doc(doc: P.Doc, +text: String) -> Note:
652 match doc:
653 case P.Doc{made}:
654 fresh.said(String.eq(made, text))
655 case P.Stop{why}:
656 Note{True{}, DW.lockfile() ++ ": not checked, since ez lock refuses here: " ++ why}
657
658
659def ask.hashes(as: List<&2, W.Ask>) -> List<&2, String>:
660 match as:
661 case []:
662 []
663 case h <> t:
664 W.ask.hash(h) <> ask.hashes(t)
665
666
667
668
669def fresh.named(as: List<&2, W.Ask>, +lw: W.World, +text: String) -> Note:
670 match as:
671 case []:
672 fresh.doc(P.decide(W.inputs(lw)), text)
673 case h <> t:
674 Note{True{}, DW.lockfile() ++ ": not checked, since BEND_LIB lacks names/" ++
675 String.join(ask.hashes(h <> t), " names/") ++ " and doctor does not use the network"}
676
677
678
679
680
681def fresh.away(as: List<&2, W.Ask>, +lw: W.World, +text: String) -> Note:
682 match as:
683 case []:
684 fresh.named(P.wants.names(lw), lw, text)
685 case h <> t:
686 Note{True{}, DW.lockfile() ++ ": not checked, since BEND_LIB lacks " ++
687 String.join(ask.hashes(h <> t), " ") ++ " and doctor does not use the network"}
688
689
690
691def fresh.listing(+world: DW.World, listing: W.Listing, +text: String) -> List<&2, Note>:
692 match listing:
693 case W.Listed{fs}:
694 +lw = relock(world, fs)
695 [fresh.away(P.wants(lw), lw, text)]
696 case W.Unlisted{_why}:
697 []
698
699
700
701def fresh.heard(got: DW.Heard, +world: DW.World, +text: String) -> List<&2, Note>:
702 match got:
703 case DW.Heard{a}:
704 match a:
705 case DW.Listed{l}:
706 fresh.listing(world, l, text)
707 case _:
708 []
709 case DW.Open{}:
710 []
711
712
713
714def fresh.notes(lock: Maybe<&2, String>, +world: DW.World) -> List<&2, Note>:
715 match lock:
716 case None{}:
717 []
718 case Some{text}:
719 fresh.heard(DW.sources(world), world, text)
720
721
722def checked(+world: DW.World) -> List<&2, Note>:
723 fresh.notes(DW.lock(world), world)
724
725
726def trees.asked(as: List<&2, W.Ask>) -> List<&2, DW.Ask>:
727 match as:
728 case []:
729 []
730 case h <> t:
731 DW.Tree{h} <> trees.asked(t)
732
733
734
735def names.asked(as: List<&2, W.Ask>) -> List<&2, DW.Ask>:
736 match as:
737 case []:
738 []
739 case h <> t:
740 DW.Name{W.ask.hash(h)} <> names.asked(t)
741
742
743def trees.world(+lw: W.World) -> List<&2, DW.Ask>:
744 List.append(&2, DW.Ask, trees.asked(P.wants(lw)), names.asked(P.wants.names(lw)))
745
746def trees.listing(
747 ledger: Maybe<&2, String>,
748 lock: Maybe<&2, String>,
749 listing: W.Listing,
750 rs: List<&2, DW.Reply>
751) -> List<&2, DW.Ask>:
752 match listing:
753 case W.Listed{fs}:
754 trees.world(relock.world(ledger, lock, W.Listed{fs}, rs))
755 case W.Unlisted{_why}:
756 []
757
758
759def trees.heard(
760 got: DW.Heard,
761 ledger: Maybe<&2, String>,
762 lock: Maybe<&2, String>,
763 rs: List<&2, DW.Reply>
764) -> List<&2, DW.Ask>:
765 match got:
766 case DW.Heard{a}:
767 match a:
768 case DW.Listed{l}:
769 trees.listing(ledger, lock, l, rs)
770 case _:
771 []
772 case DW.Open{}:
773 []
774
775
776
777
778def trees.of(
779 ledger: Maybe<&2, String>,
780 lock: Maybe<&2, String>,
781 held: Maybe<&2, String>,
782 +rs: List<&2, DW.Reply>
783) -> List<&2, DW.Ask>:
784 match lock:
785 case None{}:
786 []
787 case Some{_text}:
788 trees.heard(DW.heard(rs, DW.Sources{}), ledger, held, rs)
789
790
791
792
793
794
795
796
797def ledger.seen(ledger: M.Read) -> M.Read:
798 match ledger:
799 case M.Bad{why}:
800 M.Bad{why}
801 case M.Good{m}:
802 M.Manifest{n, e, b, h, pa, pv, ds, _ts} = m
803 M.Good{M.Manifest{n, e, b, h, pa, pv, ds, []}}
804
805
806
807def head.notes(
808 +ledger: M.Read,
809 +cc: String,
810 +lib: String,
811 +lock: Maybe<&2, String>,
812 +rs: List<&2, DW.Reply>,
813 cs: List<&2, Note>
814) -> List<&2, Note>:
815 +lk = locked.of(lock)
816 List.append(&2, Note, probes(programs(cc), rs),
817 List.append(&2, Note,
818 [ledger.note(ledger), lock.note(deps.count(ledger), lk),
819 lib.note(lib.at(lib), locked.hashes(lk), lock.names(lock), rs)], cs))
820
821
822
823
824def notes.of(
825 +ledger: M.Read,
826 got: DW.Heard,
827 +cc: String,
828 +lib: String,
829 lock: Maybe<&2, String>,
830 +rs: List<&2, DW.Reply>,
831 cs: List<&2, Note>
832) -> List<&2, Note>:
833 List.append(&2, Note, head.notes(ledger, cc, lib, lock, rs, cs), imports.notes(ledger, got))
834
835
836def note.wrong(finding: Note) -> Bool:
837 Note{w, _l} = finding
838 w
839
840
841
842def wrong(ns: List<&2, Note>) -> Bool:
843 match ns:
844 case []:
845 False{}
846 case h <> t:
847 has.step(note.wrong(h), _u => wrong(t))
848
849
850def says(ns: List<&2, Note>) -> List<&2, P.Effect>:
851 match ns:
852 case []:
853 []
854 case Note{_w, l} <> t:
855 P.Say{l} <> says(t)
856
857
858
859def outcome.of(bad: Bool) -> P.Outcome:
860 match bad:
861 case True{}:
862 P.Refused{""}
863 case False{}:
864 P.Success{}
865
866
867
868def plan.notes(+ns: List<&2, Note>) -> P.Plan:
869 P.Plan{says(ns), outcome.of(wrong(ns))}
870
871
872
873def plan.of(
874 +ledger: M.Read,
875 +cc: String,
876 +lib: String,
877 lock: Maybe<&2, String>,
878 +rs: List<&2, DW.Reply>,
879 cs: List<&2, Note>
880) -> P.Plan:
881 plan.notes(notes.of(ledger, DW.heard(rs, DW.Sources{}), cc, lib, lock, rs, cs))
882
883
884def plan(+world: DW.World) -> P.Plan:
885 plan.of(ledger.seen(DW.read(world)), DW.cc(world), DW.lib(world), DW.lock(world),
886 DW.replies(world), checked(world))
887
888
889def refuses.plan(pl: P.Plan) -> Bool:
890 P.Plan{_es, outcome} = pl
891 match outcome:
892 case P.Success{}:
893 False{}
894 case P.Refused{_why}:
895 True{}
896
897
898def refuses(+world: DW.World) -> Bool:
899 refuses.plan(plan(world))
900
901
902
903
904
905def open.put(got: DW.Heard, ask: DW.Ask, rest: List<&2, DW.Ask>) -> List<&2, DW.Ask>:
906 match got:
907 case DW.Heard{_a}:
908 rest
909 case DW.Open{}:
910 ask <> rest
911
912
913def open(asks: List<&2, DW.Ask>, +rs: List<&2, DW.Reply>) -> List<&2, DW.Ask>:
914 match asks:
915 case []:
916 []
917 case +h <> t:
918 open.put(DW.heard(rs, h), h, open(t, rs))
919
920
921def probe.asks(ns: List<&2, String>) -> List<&2, DW.Ask>:
922 match ns:
923 case []:
924 []
925 case h <> t:
926 DW.Probe{h} <> probe.asks(t)
927
928
929def lib.asks(+dir: String, hs: List<&2, String>) -> List<&2, DW.Ask>:
930 match hs:
931 case []:
932 []
933 case _h <> _t:
934 [DW.Lib{dir}]
935
936
937def sources.asks(ledger: M.Read) -> List<&2, DW.Ask>:
938 match ledger:
939 case M.Bad{_why}:
940 []
941 case M.Good{_m}:
942 [DW.Sources{}]
943
944
945def names.asks(ns: List<&2, L.Name>) -> List<&2, DW.Ask>:
946 match ns:
947 case []:
948 []
949 case L.Name{nv, _hash} <> t:
950 DW.Name{nv} <> names.asks(t)
951
952
953def asks.of(ledger: M.Read, +cc: String, +lib: String, +lock: Maybe<&2, String>) -> List<&2, DW.Ask>:
954 List.append(&2, DW.Ask, probe.asks(programs(cc)),
955 List.append(&2, DW.Ask, lib.asks(lib.at(lib), locked.hashes(locked.of(lock))),
956 List.append(&2, DW.Ask, names.asks(lock.names(lock)), sources.asks(ledger))))
957
958
959type Step is Data:
960 Asking{asks: List<&2, DW.Ask>}
961 Run{plan: P.Plan}
962
963
964def step.asks(
965 asks: List<&2, DW.Ask>,
966 +ledger: M.Read,
967 +cc: String,
968 +lib: String,
969 lock: Maybe<&2, String>,
970 +rs: List<&2, DW.Reply>,
971 cs: List<&2, Note>
972) -> Step:
973 match asks:
974 case []:
975 Run{plan.of(ledger, cc, lib, lock, rs, cs)}
976 case h <> t:
977 Asking{h <> t}
978
979
980
981
982
983def step.of(
984 +ledger: M.Read,
985 +cc: String,
986 +lib: String,
987 +lock: Maybe<&2, String>,
988 +rs: List<&2, DW.Reply>,
989 ts: List<&2, DW.Ask>,
990 cs: List<&2, Note>
991) -> Step:
992 step.asks(open(List.append(&2, DW.Ask, asks.of(ledger, cc, lib, lock), ts), rs),
993 ledger, cc, lib, lock, rs, cs)
994
995
996def step(+world: DW.World) -> Step:
997 step.of(ledger.seen(DW.read(world)), DW.cc(world), DW.lib(world), DW.lock(world),
998 DW.replies(world), trees.of(DW.ledger(world), DW.lock(world), DW.lock(world), DW.replies(world)),
999 checked(world))
1000
1001
1002def wants(+world: DW.World) -> List<&2, DW.Ask>:
1003 +ledger = ledger.seen(DW.read(world))
1004 open(List.append(&2, DW.Ask, asks.of(ledger, DW.cc(world), DW.lib(world), DW.lock(world)),
1005 trees.of(DW.ledger(world), DW.lock(world), DW.lock(world), DW.replies(world))), DW.replies(world))
1006
1007
1008
1009
1010
1011def said.one(effect: P.Effect, +line: String) -> Bool:
1012 match effect:
1013 case P.Say{text}:
1014 String.eq(text, line)
1015 case P.Write{_at, _text}:
1016 False{}
1017 case P.Lay{_place, _hash, _files}:
1018 False{}
1019 case P.Drop{_hash}:
1020 False{}
1021 case P.Name{_nv, _hash}:
1022 False{}
1023
1024
1025def said.in(es: List<&2, P.Effect>, +line: String) -> Bool:
1026 match es:
1027 case []:
1028 False{}
1029 case e <> t:
1030 has.step(said.one(e, line), _u => said.in(t, line))
1031
1032
1033def said(pl: P.Plan, +line: String) -> Bool:
1034 P.Plan{es, _outcome} = pl
1035 said.in(es, line)
1036
1037
1038
1039def drift.of(fs: List<&2, W.Source>, +ds: List<&2, M.Dep>) -> List<&2, String>:
1040 +hs = imports(fs)
1041 drift(hs, ds)