src/pub/plan.bend
ezx@1.11.0.0 · 1,320 lines · 170 definitions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24import Base
25import ezx@1.11.0.0/src/lock/plan.bend as P
26import ezx@1.11.0.0/src/tool/world.bend as TW
27import ezx@1.11.0.0/src/ledger/manifest.bend as M
28import ezx@1.11.0.0/src/pkg/pkg.bend as K
29import ezx@1.11.0.0/src/pkg/path.bend as Path
30import ezx@1.11.0.0/src/git/git.bend as G
31import ezx@1.11.0.0/src/pub/world.bend as PW
32import ezx@1.11.0.0/src/pub/blurb.bend as B
33import ezx@1.11.0.0/src/lock/lock.bend as L
34import ezx@1.11.0.0/src/share/sha.bend as Sha
35import ezx@1.11.0.0/src/io/file.bend as F
36
37
38
39
40
41
42def name.length() -> Nat:
43 34n
44
45
46
47
48
49
50
51
52
53
54
55
56def is_name(+word: String) -> Bool:
57 Bool.and(String.starts_with(word, "0x"),
58 Bool.and(Nat.is_eq(String.length(word), name.length()),
59 G.hex.all(String.to_list(String.drop(word, 2n)))))
60
61
62
63
64
65def agrees.step(name: Bool, same: Bool, seen: Bool, rest: Bool -> Bool) -> Bool:
66 match name:
67 case True{}:
68 Bool.and(same, rest(True{}))
69 case False{}:
70 rest(seen)
71
72
73
74def agrees.go(+ours: String, ls: List<&2, String>, seen: Bool) -> Bool:
75 match ls:
76 case []:
77 seen
78 case +h <> t:
79 agrees.step(is_name(h), String.eq(ours, h), seen,
80 s => agrees.go(ours, t, s))
81
82
83
84
85
86def agrees(+ours: String, ls: List<&2, String>) -> Bool:
87 agrees.go(ours, ls, False{})
88
89
90def other.step(odd: Bool, line: String, rest: Unit -> String) -> String:
91 match odd:
92 case True{}:
93 line
94 case False{}:
95 rest(Unit{})
96
97
98def other(+ours: String, ls: List<&2, String>) -> String:
99 match ls:
100 case []:
101 ""
102 case +h <> t:
103 other.step(Bool.and(is_name(h), Bool.not(String.eq(ours, h))), h,
104 _u => other(ours, t))
105
106
107
108
109
110
111
112
113
114def dirty.step(blank: Bool, rest: Unit -> Bool) -> Bool:
115 match blank:
116 case True{}:
117 rest(Unit{})
118 case False{}:
119 True{}
120
121
122
123
124def dirty.lines(ls: List<&2, String>) -> Bool:
125 match ls:
126 case []:
127 False{}
128 case +h <> t:
129 dirty.step(String.is_empty(h), _u => dirty.lines(t))
130
131
132def blanks.step(blank: Bool, rest: Unit -> Bool) -> Bool:
133 match blank:
134 case True{}:
135 rest(Unit{})
136 case False{}:
137 False{}
138
139
140
141
142def blanks(ls: List<&2, String>) -> Bool:
143 match ls:
144 case []:
145 True{}
146 case +h <> t:
147 blanks.step(String.is_empty(h), _u => blanks(t))
148
149
150
151
152
153
154def dirty(text: String) -> Bool:
155 dirty.lines(String.lines(text))
156
157
158
159
160
161def semver.char(+ch: Char) -> Bool:
162 Bool.or(Char.is_digit(ch), Char.is_eq(ch, '.'))
163
164
165
166
167def semver.chars(text: String) -> Bool:
168 match text:
169 case SNil{}:
170 True{}
171 case SCon{+h, t}:
172 +rest = semver.chars(t)
173 Bool.and(semver.char(h), rest)
174
175
176
177
178def semver(+text: String) -> Bool:
179 +ps = String.split(text, '.')
180 Bool.and(semver.chars(text),
181 Bool.and(Nat.is_eq(List.length(&2, String, ps), 3n), K.ver.nums(ps)))
182
183
184
185
186type Named is Data:
187 ByHash{}
188 ByName{nv: String}
189 Unnamed{why: String}
190
191
192def why.nover(+name: String) -> String:
193 "ez: ez.toml's [package] has publish-as = \"" ++ name ++ "\" and no version; " ++
194 "a named publish needs both, and one with neither publishes by hash"
195
196
197def why.noname(+ver: String) -> String:
198 "ez: ez.toml's [package] has version = \"" ++ ver ++ "\" and no publish-as; " ++
199 "a named publish needs both, and one with neither publishes by hash"
200
201
202def why.name(+name: String) -> String:
203 "ez: publish-as = \"" ++ name ++ "\" is not a hub name: a lowercase letter, " ++
204 "then a-z, 0-9 and -, 1 to 64 characters in all"
205
206
207def why.ver(+ver: String) -> String:
208 "ez: version = \"" ++ ver ++ "\" is not MAJOR.MINOR.PATCH: three numbers " ++
209 "with no leading zeros and no pre-release or build suffix, which ez " ++
210 "publishes as bend's four-part MAJOR.MINOR.PATCH.0"
211
212
213
214def naming.both(name: Bool, ver: Bool, +pa: String, +pv: String) -> Named:
215 match name:
216 case False{}:
217 Unnamed{why.name(pa)}
218 case True{}:
219 match ver:
220 case False{}:
221 Unnamed{why.ver(pv)}
222 case True{}:
223 ByName{pa ++ "@" ++ pv ++ ".0"}
224
225
226
227
228def naming.go(noname: Bool, nover: Bool, +pa: String, +pv: String) -> Named:
229 match noname:
230 case True{}:
231 match nover:
232 case True{}:
233 ByHash{}
234 case False{}:
235 Unnamed{why.noname(pv)}
236 case False{}:
237 match nover:
238 case True{}:
239 Unnamed{why.nover(pa)}
240 case False{}:
241 naming.both(K.name.part(pa), semver(pv), pa, pv)
242
243
244def naming(+pa: String, +pv: String) -> Named:
245 naming.go(String.is_empty(pa), String.is_empty(pv), pa, pv)
246
247
248
249
250
251
252type Led is Data:
253 Led{entry: String, named: Named}
254 LedNo{why: String}
255
256
257def led.named(+entry: String, got: Named) -> Led:
258 match got:
259 case ByHash{}:
260 Led{entry, ByHash{}}
261 case ByName{nv}:
262 Led{entry, ByName{nv}}
263 case Unnamed{why}:
264 LedNo{why}
265
266
267
268def led.read(ledger: M.Read) -> Led:
269 match ledger:
270 case M.Bad{why}:
271 LedNo{"ez: " ++ M.show(M.Bad{why})}
272 case M.Good{m}:
273 M.Manifest{_n, +e, _b, _h, +pa, +pv, _ds, _ts} = m
274 led.named(Bool.pick(String, String.is_empty(e), "main.bend", e), naming(pa, pv))
275
276
277
278
279def led.of(got: Maybe<&2, String>) -> Led:
280 match got:
281 case None{}:
282 LedNo{"ez: no " ++ PW.toml() ++ " here; ez publish sends the package " ++
283 "a project's ledger names, and ez init makes one"}
284 case Some{text}:
285 led.read(M.parse(text))
286
287
288def led(world: PW.World) -> Led:
289 led.of(PW.ledger(world))
290
291
292def entry.of(ledger: Led) -> String:
293 match ledger:
294 case Led{e, _nv}:
295 Path.norm(e)
296 case LedNo{_why}:
297 ""
298
299
300
301def named.of(ledger: Led) -> Named:
302 match ledger:
303 case Led{_e, got}:
304 got
305 case LedNo{_why}:
306 ByHash{}
307
308
309def named(world: PW.World) -> Named:
310 named.of(led(world))
311
312
313def named.word(got: Named) -> String:
314 match got:
315 case ByName{nv}:
316 nv
317 case ByHash{}:
318 ""
319 case Unnamed{_why}:
320 ""
321
322
323def entry(world: PW.World) -> String:
324 entry.of(led(world))
325
326
327
328
329
330
331type Status is Data:
332 Said{text: String}
333 StatusWait{}
334 StatusNo{why: String}
335
336
337def locate(world: PW.World) -> PW.Ask:
338 PW.Tool{TW.Locate{PW.here(world)}}
339
340
341
342
343def status.ran(ok: Bool, +text: String) -> Status:
344 match ok:
345 case True{}:
346 Said{text}
347 case False{}:
348 StatusNo{"ez: git status: " ++ text}
349
350def status.told(got: TW.Answer) -> Status:
351 match got:
352 case TW.Located{_abs, _top, _head, ok, text}:
353 status.ran(ok, text)
354 case TW.Miss{why}:
355 StatusNo{why}
356 case _:
357 StatusNo{"ez: git was not asked about the project"}
358
359def status.answer(got: PW.Answer) -> Status:
360 match got:
361 case PW.Told{t}:
362 status.told(t)
363 case PW.Ran{_ok, _text}:
364 StatusNo{"ez: git was not asked about the project"}
365
366def status.of(got: PW.Heard) -> Status:
367 match got:
368 case PW.Open{}:
369 StatusWait{}
370 case PW.Heard{a}:
371 status.answer(a)
372
373
374def status(+world: PW.World) -> Status:
375 status.of(PW.heard(world, locate(world)))
376
377
378
379
380
381
382def tree.told(got: TW.Answer, +path: String, rest: K.Tree) -> K.Tree:
383 match got:
384 case TW.Plain{m}:
385 K.tree.add(m, rest, path)
386 case _:
387 K.tree.gone(rest, path)
388
389def tree.read(got: PW.Answer, +path: String, rest: K.Tree) -> K.Tree:
390 match got:
391 case PW.Told{t}:
392 tree.told(t, path, rest)
393 case PW.Ran{_ok, _text}:
394 K.tree.gone(rest, path)
395
396def tree.tool(ask: TW.Ask, got: PW.Answer, rest: K.Tree) -> K.Tree:
397 match ask:
398 case TW.Read{path}:
399 tree.read(got, path, rest)
400 case _:
401 rest
402
403def tree.one(ask: PW.Ask, got: PW.Answer, rest: K.Tree) -> K.Tree:
404 match ask:
405 case PW.Tool{q}:
406 tree.tool(q, got, rest)
407 case _:
408 rest
409
410
411
412def tree.of(rs: List<&2, PW.Reply>) -> K.Tree:
413 match rs:
414 case []:
415 K.Tree{False{}, [], []}
416 case PW.Reply{ask, got} <> t:
417 tree.one(ask, got, tree.of(t))
418
419
420def tree(world: PW.World) -> K.Tree:
421 tree.of(PW.replies(world))
422
423
424
425def fuel(fs: List<&2, K.Source>) -> Nat:
426 match fs:
427 case []:
428 1n
429 case K.Source{_at, text} <> t:
430 (List.length(&2, String, String.lines(text)) + fuel(t) : Nat)
431
432def fuel.of(tr: K.Tree) -> Nat:
433 K.Tree{_whole, fs, _gone} = tr
434 fuel(fs)
435
436
437def walked(+world: PW.World) -> K.Walked:
438 K.of.tree(fuel.of(tree(world)), entry(world), tree(world))
439
440
441def hash.of(got: K.Walked) -> String:
442 match got:
443 case K.Walked{h, _root, _fs, _sums}:
444 h
445 case K.Asks{_at}:
446 ""
447 case K.Refused{_why}:
448 ""
449
450
451def hash(+world: PW.World) -> String:
452 hash.of(walked(world))
453
454
455def root.of(got: K.Walked) -> String:
456 match got:
457 case K.Walked{_h, root, _fs, _sums}:
458 root
459 case K.Asks{_at}:
460 "."
461 case K.Refused{_why}:
462 "."
463
464
465
466
467def at(+world: PW.World) -> String:
468 K.inside(root.of(walked(world)), entry(world))
469
470
471def paths(+root: String, fs: List<&2, K.Source>) -> List<&2, String>:
472 match fs:
473 case []:
474 []
475 case K.Source{at, _text} <> t:
476 Path.join(root, at) <> paths(root, t)
477
478
479
480
481
482
483
484
485def tracked.step(hit: Bool, rest: Unit -> Bool) -> Bool:
486 match hit:
487 case True{}:
488 True{}
489 case False{}:
490 rest(Unit{})
491
492def tracked.has(ls: List<&2, String>, +path: String) -> Bool:
493 match ls:
494 case []:
495 False{}
496 case +h <> t:
497 tracked.step(String.eq(h, "H " ++ path), _u => tracked.has(t, path))
498
499
500
501def stray.step(kept: Bool, +path: String, rest: Unit -> String) -> String:
502 match kept:
503 case True{}:
504 rest(Unit{})
505 case False{}:
506 path
507
508def stray.first(+ls: List<&2, String>, ps: List<&2, String>) -> String:
509 match ps:
510 case []:
511 ""
512 case +h <> t:
513 stray.step(tracked.has(ls, h), h, _u => stray.first(ls, t))
514
515
516
517type Kept is Data:
518 Kept{}
519 Stray{path: String}
520 KeptWait{}
521 KeptNo{why: String}
522
523def kept.of(+first: String) -> Kept:
524 Bool.pick(Kept, String.is_empty(first), Kept{}, Stray{first})
525
526def kept.ran(ok: Bool, +text: String, +ps: List<&2, String>) -> Kept:
527 match ok:
528 case True{}:
529 kept.of(stray.first(String.lines(text), ps))
530 case False{}:
531 KeptNo{"ez: git ls-files: " ++ text}
532
533def kept.answer(got: PW.Answer, +ps: List<&2, String>) -> Kept:
534 match got:
535 case PW.Ran{ok, text}:
536 kept.ran(ok, text, ps)
537 case PW.Told{_t}:
538 KeptNo{"ez: git was not asked what it tracks"}
539
540def kept.heard(got: PW.Heard, +ps: List<&2, String>) -> Kept:
541 match got:
542 case PW.Open{}:
543 KeptWait{}
544 case PW.Heard{a}:
545 kept.answer(a, ps)
546
547def kept.walked(got: K.Walked, +world: PW.World) -> Kept:
548 match got:
549 case K.Walked{_h, +root, fs, _sums}:
550 kept.heard(PW.heard(world, PW.Tracked{}), paths(root, fs))
551 case K.Asks{_at}:
552 KeptNo{"ez: the package was not walked"}
553 case K.Refused{why}:
554 KeptNo{why}
555
556
557
558
559def stray(+world: PW.World) -> Kept:
560 kept.walked(walked(world), world)
561
562
563
564
565
566
567
568
569def upload.go(bare: Bool, +entry: String, +named: String) -> List<&2, String>:
570 match bare:
571 case True{}:
572 ["bend", entry, "--publish"]
573 case False{}:
574 ["bend", entry, "--publish", named]
575
576def upload.line(+entry: String, +named: String) -> List<&2, String>:
577 upload.go(String.is_empty(named), entry, named)
578
579
580
581def files.of(got: K.Walked) -> List<&2, K.Source>:
582 match got:
583 case K.Walked{_h, _root, fs, _sums}:
584 fs
585 case K.Asks{_at}:
586 []
587 case K.Refused{_why}:
588 []
589
590
591
592
593
594def said(+world: PW.World) -> String:
595 "hub description: " ++ B.blurb(files.of(walked(world)))
596
597
598
599def upload(+world: PW.World) -> PW.Ask:
600 PW.Upload{entry(world), named.word(named(world)), said(world)}
601
602
603def uploaded(+world: PW.World) -> PW.Heard:
604 PW.heard(world, upload(world))
605
606
607
608def agreed.answer(got: PW.Answer, +ours: String) -> Bool:
609 match got:
610 case PW.Ran{ok, text}:
611 Bool.and(ok, agrees(ours, String.lines(text)))
612 case PW.Told{_t}:
613 False{}
614
615def agreed.of(got: PW.Heard, +ours: String) -> Bool:
616 match got:
617 case PW.Open{}:
618 False{}
619 case PW.Heard{a}:
620 agreed.answer(a, ours)
621
622
623def agreed(+world: PW.World) -> Bool:
624 agreed.of(uploaded(world), hash(world))
625
626
627
628
629
630def why.read(+ours: String, +text: String) -> String:
631 +theirs = other(ours, String.lines(text))
632 Bool.pick(String, String.is_empty(theirs),
633 "ez: bend printed no line that is a 0x name, so there is nothing to " ++
634 "check ez's hash against. bend said:\n" ++ text,
635 "ez: ez computed " ++ ours ++ " and bend published " ++ theirs ++
636 ". One of the two package walks has drifted from the other, so " ++
637 "neither name is safe to write down and ez has reported none.")
638
639
640
641
642def why.ran(ok: Bool, +words: String, +ours: String, +text: String) -> String:
643 match ok:
644 case False{}:
645 "ez: " ++ words ++ ": " ++ text
646 case True{}:
647 why.read(ours, text)
648
649def why.answer(got: PW.Answer, +words: String, +ours: String) -> String:
650 match got:
651 case PW.Ran{ok, text}:
652 why.ran(ok, words, ours, text)
653 case PW.Told{_t}:
654 "ez: bend was not asked to publish"
655
656def why.of(got: PW.Heard, +words: String, +ours: String) -> String:
657 match got:
658 case PW.Open{}:
659 "ez: bend was not asked to publish"
660 case PW.Heard{a}:
661 why.answer(a, words, ours)
662
663
664def why.upload(+world: PW.World) -> String:
665 why.of(uploaded(world),
666 String.join(upload.line(entry(world), named.word(named(world))), " "),
667 hash(world))
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684type Probe is Data:
685 ProbeHash{hash: String}
686 ProbeName{nv: String, hash: String}
687
688
689def texts(fs: List<&2, K.Source>) -> List<&2, String>:
690 match fs:
691 case []:
692 []
693 case K.Source{_at, text} <> t:
694 text <> texts(t)
695
696
697def once.pick(dup: Bool, +word: String, rest: List<&2, String>) -> List<&2, String>:
698 match dup:
699 case True{}:
700 rest
701 case False{}:
702 word <> rest
703
704
705def once.go(xs: List<&2, String>, +seen: List<&2, String>) -> List<&2, String>:
706 match xs:
707 case []:
708 []
709 case +h <> t:
710 once.pick(L.seen.holds(seen, h), h, once.go(t, h <> seen))
711
712
713def once(xs: List<&2, String>) -> List<&2, String>:
714 once.go(xs, [])
715
716
717def sources(+world: PW.World) -> List<&2, String>:
718 texts(files.of(walked(world)))
719
720
721def hashes(+world: PW.World) -> List<&2, String>:
722 once(L.kids(sources(world)))
723
724
725def nvs(+world: PW.World) -> List<&2, String>:
726 once(L.kids.named(sources(world)))
727
728
729def any(xs: List<&2, String>) -> Bool:
730 match xs:
731 case []:
732 False{}
733 case _h <> _t:
734 True{}
735
736
737
738def parsed.of(got: Maybe<&2, String>) -> M.Read:
739 match got:
740 case None{}:
741 M.Bad{"no " ++ PW.toml()}
742 case Some{text}:
743 M.parse(text)
744
745
746def parsed(world: PW.World) -> M.Read:
747 parsed.of(PW.ledger(world))
748
749
750def hub(world: PW.World) -> String:
751 M.hub_of(parsed(world))
752
753
754def deps.of(got: M.Read) -> List<&2, M.Dep>:
755 match got:
756 case M.Bad{_why}:
757 []
758 case M.Good{m}:
759 M.Manifest{_n, _e, _b, _h, _pa, _pv, ds, _ts} = m
760 ds
761
762
763def deps(world: PW.World) -> List<&2, M.Dep>:
764 deps.of(parsed(world))
765
766
767def lock.told(got: TW.Answer) -> String:
768 match got:
769 case TW.Plain{m}:
770 F.text_of(m)
771 case _:
772 ""
773
774def lock.answer(got: PW.Answer) -> String:
775 match got:
776 case PW.Told{t}:
777 lock.told(t)
778 case PW.Ran{_ok, _text}:
779 ""
780
781def lock.of(got: PW.Heard) -> String:
782 match got:
783 case PW.Open{}:
784 ""
785 case PW.Heard{a}:
786 lock.answer(a)
787
788
789def lock(+world: PW.World) -> String:
790 lock.of(PW.heard(world, PW.Lock{}))
791
792
793
794def names(+world: PW.World) -> List<&2, L.Name>:
795 List.append(&2, L.Name, L.names.read(lock(world)), L.ledger.names(parsed(world)))
796
797
798def probes.hash(hs: List<&2, String>) -> List<&2, Probe>:
799 match hs:
800 case []:
801 []
802 case h <> t:
803 ProbeHash{h} <> probes.hash(t)
804
805
806def probes.name(+ns: List<&2, L.Name>, vs: List<&2, String>) -> List<&2, Probe>:
807 match vs:
808 case []:
809 []
810 case +h <> t:
811 ProbeName{h, L.name.find(ns, h)} <> probes.name(ns, t)
812
813
814def probes(+world: PW.World) -> List<&2, Probe>:
815 List.append(&2, Probe, probes.hash(hashes(world)), probes.name(names(world), nvs(world)))
816
817
818def probe.url(+hub: String, one: Probe) -> String:
819 match one:
820 case ProbeHash{h}:
821 L.url_of(hub, h, "manifest")
822 case ProbeName{nv, _h}:
823 hub ++ "/name/" ++ nv
824
825
826def probe.ask(+hub: String, one: Probe) -> PW.Ask:
827 PW.Hub{probe.url(hub, one)}
828
829
830def probe.asks(+hub: String, ps: List<&2, Probe>) -> List<&2, PW.Ask>:
831 match ps:
832 case []:
833 []
834 case h <> t:
835 probe.ask(hub, h) <> probe.asks(hub, t)
836
837
838def probe.word(one: Probe) -> String:
839 match one:
840 case ProbeHash{h}:
841 h
842 case ProbeName{nv, _h}:
843 nv
844
845
846def origin.say(src: M.Source) -> String:
847 match src:
848 case M.Hub{_named}:
849 "hub"
850 case M.Git{url, _rev, _tag, _root, _nar, _vend}:
851 "git " ++ url
852 case M.Member{_path, _named}:
853 "workspace member"
854
855
856def dep.say(item: M.Dep) -> String:
857 M.Dep{n, _h, _e, src} = item
858 n ++ ", " ++ origin.say(src)
859
860
861
862def dep.is.go(one: Probe, +hash: String, src: M.Source) -> Bool:
863 match one:
864 case ProbeHash{ph}:
865 String.eq(hash, ph)
866 case ProbeName{nv, +ph}:
867 Bool.or(String.eq(M.source.nv(src), nv),
868 Bool.and(Bool.not(String.is_empty(ph)), String.eq(hash, ph)))
869
870def dep.is(one: Probe, item: M.Dep) -> Bool:
871 M.Dep{_n, h, _e, src} = item
872 dep.is.go(one, h, src)
873
874def dep.find.pick(hit: Bool, item: M.Dep, rest: Unit -> String) -> String:
875 match hit:
876 case True{}:
877 dep.say(item)
878 case False{}:
879 rest(Unit{})
880
881
882def dep.find(+one: Probe, ds: List<&2, M.Dep>) -> String:
883 match ds:
884 case []:
885 ""
886 case +h <> t:
887 dep.find.pick(dep.is(one, h), h, _u => dep.find(one, t))
888
889
890
891def who.of(+word: String, +dep: String) -> String:
892 Bool.pick(String, String.is_empty(dep), word, word ++ " (" ++ dep ++ ")")
893
894
895def who(+one: Probe, ds: List<&2, M.Dep>) -> String:
896 who.of(probe.word(one), dep.find(one, ds))
897
898
899
900type Seen is Data:
901 There{}
902 Unasked{}
903 Off{line: String}
904
905
906
907def absent(+text: String) -> Bool:
908 String.eq(text, "the server answered 404")
909
910
911def why.absent(+who: String) -> String:
912 " " ++ who ++ " is not on the hub; publish it first"
913
914
915def why.unasked(+who: String, +text: String) -> String:
916 " " ++ who ++ ": the hub could not be asked: " ++ text
917
918
919
920
921def seen.miss(gone: Bool, +text: String, +who: String) -> Seen:
922 match gone:
923 case True{}:
924 Off{why.absent(who)}
925 case False{}:
926 Off{why.unasked(who, text)}
927
928
929
930def seen.hash(ok: Bool, +hash: String, +text: String, +who: String) -> Seen:
931 match ok:
932 case True{}:
933 Bool.pick(Seen, String.starts_with(Sha.hex(text), L.want(hash)), There{},
934 Off{" " ++ who ++ ": the hub served a manifest that does not hash to it"})
935 case False{}:
936 seen.miss(absent(text), text, who)
937
938
939def why.named(+got: String, +want: String, +who: String) -> String:
940 Bool.pick(String, String.is_empty(want),
941 " " ++ who ++ ": the hub answered `" ++ got ++ "`, which is not a 0x name",
942 " " ++ who ++ ": the hub names `" ++ got ++ "` by it, and the lock " ++
943 "resolved it to " ++ want)
944
945
946
947def seen.named.go(good: Bool, +got: String, +want: String, +who: String) -> Seen:
948 match good:
949 case True{}:
950 There{}
951 case False{}:
952 Off{why.named(got, want, who)}
953
954def seen.named(+got: String, +want: String, +who: String) -> Seen:
955 seen.named.go(Bool.and(Bool.or(String.eq(got, want), String.is_empty(want)), L.hash.ok(got)),
956 got, want, who)
957
958
959def seen.name(ok: Bool, +want: String, +text: String, +who: String) -> Seen:
960 match ok:
961 case True{}:
962 seen.named(String.trim(text), want, who)
963 case False{}:
964 seen.miss(absent(text), text, who)
965
966
967def seen.ran(one: Probe, ok: Bool, +text: String, +who: String) -> Seen:
968 match one:
969 case ProbeHash{h}:
970 seen.hash(ok, h, text, who)
971 case ProbeName{_nv, h}:
972 seen.name(ok, h, text, who)
973
974def seen.answer(one: Probe, got: PW.Answer, +who: String) -> Seen:
975 match got:
976 case PW.Ran{ok, text}:
977 seen.ran(one, ok, text, who)
978 case PW.Told{_t}:
979 Off{" " ++ who ++ ": the hub was not asked"}
980
981def seen.heard(got: PW.Heard, one: Probe, +who: String) -> Seen:
982 match got:
983 case PW.Open{}:
984 Unasked{}
985 case PW.Heard{a}:
986 seen.answer(one, a, who)
987
988
989def seen(+world: PW.World, +hub: String, +ds: List<&2, M.Dep>, +one: Probe) -> Seen:
990 seen.heard(PW.heard(world, probe.ask(hub, one)), one, who(one, ds))
991
992
993type Tally is Data:
994 Tally{open: List<&2, Probe>, offs: List<&2, String>}
995
996
997def tally.unasked(one: Probe, sofar: Tally) -> Tally:
998 Tally{open, offs} = sofar
999 Tally{one <> open, offs}
1000
1001
1002def tally.offhub(line: String, sofar: Tally) -> Tally:
1003 Tally{open, offs} = sofar
1004 Tally{open, line <> offs}
1005
1006
1007def tally.put(standing: Seen, one: Probe, sofar: Tally) -> Tally:
1008 match standing:
1009 case There{}:
1010 sofar
1011 case Unasked{}:
1012 tally.unasked(one, sofar)
1013 case Off{line}:
1014 tally.offhub(line, sofar)
1015
1016
1017def tally(+world: PW.World, +hub: String, +ds: List<&2, M.Dep>, ps: List<&2, Probe>) -> Tally:
1018 match ps:
1019 case []:
1020 Tally{[], []}
1021 case +h <> t:
1022 tally.put(seen(world, hub, ds, h), h, tally(world, hub, ds, t))
1023
1024
1025
1026type Hubbed is Data:
1027 OnHub{}
1028 HubWait{open: List<&2, Probe>}
1029 OffHub{why: String}
1030
1031
1032
1033def why.hub(+hub: String, lines: List<&2, String>) -> String:
1034 "ez: the package imports hub packages that " ++ hub ++ " does not have, or " ++
1035 "could not be asked about, and a package on the hub has to build from the " ++
1036 "hub alone, so nothing was sent:\n" ++ String.join(lines, "\n")
1037
1038def hubbed.offs(offs: List<&2, String>, +hub: String) -> Hubbed:
1039 match offs:
1040 case []:
1041 OnHub{}
1042 case h <> t:
1043 OffHub{why.hub(hub, h <> t)}
1044
1045
1046
1047def hubbed.go(open: List<&2, Probe>, offs: List<&2, String>, +hub: String) -> Hubbed:
1048 match open:
1049 case []:
1050 hubbed.offs(offs, hub)
1051 case h <> rest:
1052 HubWait{h <> rest}
1053
1054def hubbed.of(sofar: Tally, +hub: String) -> Hubbed:
1055 Tally{open, offs} = sofar
1056 hubbed.go(open, offs, hub)
1057
1058
1059def hubbed(+world: PW.World) -> Hubbed:
1060 +hub = hub(world)
1061 hubbed.of(tally(world, hub, deps(world), probes(world)), hub)
1062
1063
1064def on.hub.v(got: Hubbed) -> Bool:
1065 match got:
1066 case OnHub{}:
1067 True{}
1068 case HubWait{_open}:
1069 False{}
1070 case OffHub{_why}:
1071 False{}
1072
1073def on.hub(+world: PW.World) -> Bool:
1074 on.hub.v(hubbed(world))
1075
1076
1077
1078
1079
1080type Verdict is Data:
1081 Go{}
1082 Pending{asks: List<&2, PW.Ask>}
1083 Stop{why: String}
1084
1085
1086
1087def why.dirty(+text: String) -> String:
1088 "ez: the working tree is dirty, and publishing is public and cannot be " ++
1089 "taken back, so what goes to the hub has to exist in a commit. git " ++
1090 "status says:\n" ++ text
1091
1092
1093def why.stray(+path: String) -> String:
1094 "ez: " ++ path ++ " is part of the package and git does not track it, " ++
1095 "so what goes to the hub would not exist in a commit"
1096
1097
1098def decide.agreed(ok: Bool, +world: PW.World) -> Verdict:
1099 match ok:
1100 case True{}:
1101 Go{}
1102 case False{}:
1103 Stop{why.upload(world)}
1104
1105
1106def decide.upload(got: PW.Heard, +world: PW.World) -> Verdict:
1107 match got:
1108 case PW.Open{}:
1109 Pending{[upload(world)]}
1110 case PW.Heard{_a}:
1111 decide.agreed(agreed(world), world)
1112
1113
1114
1115def decide.hub(on: Hubbed, +world: PW.World) -> Verdict:
1116 match on:
1117 case OnHub{}:
1118 decide.upload(uploaded(world), world)
1119 case HubWait{open}:
1120 Pending{probe.asks(hub(world), open)}
1121 case OffHub{why}:
1122 Stop{why}
1123
1124
1125
1126def decide.lock(need: Bool, got: PW.Heard, +world: PW.World) -> Verdict:
1127 match need:
1128 case False{}:
1129 decide.hub(hubbed(world), world)
1130 case True{}:
1131 match got:
1132 case PW.Open{}:
1133 Pending{[PW.Lock{}]}
1134 case PW.Heard{_a}:
1135 decide.hub(hubbed(world), world)
1136
1137
1138def decide.kept(kept: Kept, +world: PW.World) -> Verdict:
1139 match kept:
1140 case Kept{}:
1141 decide.lock(any(nvs(world)), PW.heard(world, PW.Lock{}), world)
1142 case Stray{path}:
1143 Stop{why.stray(path)}
1144 case KeptWait{}:
1145 Pending{[PW.Tracked{}]}
1146 case KeptNo{why}:
1147 Stop{why}
1148
1149
1150def decide.walk(got: K.Walked, +world: PW.World) -> Verdict:
1151 match got:
1152 case K.Walked{_h, _root, _fs, _sums}:
1153 decide.kept(stray(world), world)
1154 case K.Asks{at}:
1155 Pending{[PW.Tool{TW.Read{at}}]}
1156 case K.Refused{why}:
1157 Stop{why}
1158
1159
1160def decide.clean(bad: Bool, +text: String, +world: PW.World) -> Verdict:
1161 match bad:
1162 case True{}:
1163 Stop{why.dirty(text)}
1164 case False{}:
1165 decide.walk(walked(world), world)
1166
1167
1168def decide.status(said: Status, +world: PW.World) -> Verdict:
1169 match said:
1170 case Said{+text}:
1171 decide.clean(dirty(text), text, world)
1172 case StatusWait{}:
1173 Pending{[locate(world)]}
1174 case StatusNo{why}:
1175 Stop{why}
1176
1177
1178def decide.led(ledger: Led, +world: PW.World) -> Verdict:
1179 match ledger:
1180 case Led{_e, _nv}:
1181 decide.status(status(world), world)
1182 case LedNo{why}:
1183 Stop{why}
1184
1185
1186def decide(+world: PW.World) -> Verdict:
1187 decide.led(led(world), world)
1188
1189
1190
1191
1192
1193
1194def called.of(got: Named, +hash: String) -> String:
1195 match got:
1196 case ByName{nv}:
1197 nv
1198 case ByHash{}:
1199 hash
1200 case Unnamed{_why}:
1201 hash
1202
1203
1204def called(+world: PW.World) -> String:
1205 called.of(named(world), hash(world))
1206
1207
1208
1209
1210
1211def made(+world: PW.World) -> P.Plan:
1212 P.Plan{[P.Say{hash(world)}, P.Say{G.import.line(called(world), at(world))}],
1213 P.Success{}}
1214
1215
1216
1217def plan.v(verdict: Verdict, +world: PW.World) -> P.Plan:
1218 match verdict:
1219 case Go{}:
1220 made(world)
1221 case Pending{_asks}:
1222 P.Plan{[], P.Refused{"ez: the publish of " ++ entry(world) ++
1223 " was never resolved"}}
1224 case Stop{why}:
1225 P.Plan{[], P.Refused{why}}
1226
1227
1228def plan(+world: PW.World) -> P.Plan:
1229 plan.v(decide(world), world)
1230
1231
1232type Step is Data:
1233 Asking{asks: List<&2, PW.Ask>}
1234 Run{plan: P.Plan}
1235
1236
1237def step.v(verdict: Verdict, +world: PW.World) -> Step:
1238 match verdict:
1239 case Go{}:
1240 Run{made(world)}
1241 case Pending{asks}:
1242 Asking{asks}
1243 case Stop{why}:
1244 Run{plan.v(Stop{why}, world)}
1245
1246
1247def step(+world: PW.World) -> Step:
1248 step.v(decide(world), world)
1249
1250
1251
1252
1253
1254def succeeds.plan(pl: P.Plan) -> Bool:
1255 P.Plan{_es, outcome} = pl
1256 match outcome:
1257 case P.Success{}:
1258 True{}
1259 case P.Refused{_why}:
1260 False{}
1261
1262
1263def succeeds(+world: PW.World) -> Bool:
1264 succeeds.plan(plan(world))
1265
1266
1267def refuses(+world: PW.World) -> Bool:
1268 Bool.not(succeeds(world))
1269
1270
1271def stops.v(verdict: Verdict) -> Bool:
1272 match verdict:
1273 case Go{}:
1274 False{}
1275 case Pending{_asks}:
1276 False{}
1277 case Stop{_why}:
1278 True{}
1279
1280
1281def stops(+world: PW.World) -> Bool:
1282 stops.v(decide(world))
1283
1284
1285def is.upload(ask: PW.Ask) -> Bool:
1286 match ask:
1287 case PW.Upload{_entry, _named, _says}:
1288 True{}
1289 case PW.Tool{_q}:
1290 False{}
1291 case PW.Tracked{}:
1292 False{}
1293 case PW.Lock{}:
1294 False{}
1295 case PW.Hub{_url}:
1296 False{}
1297
1298
1299def any.upload(asks: List<&2, PW.Ask>) -> Bool:
1300 match asks:
1301 case []:
1302 False{}
1303 case h <> t:
1304 +rest = any.upload(t)
1305 Bool.or(is.upload(h), rest)
1306
1307
1308
1309def sends.v(verdict: Verdict) -> Bool:
1310 match verdict:
1311 case Go{}:
1312 True{}
1313 case Pending{asks}:
1314 any.upload(asks)
1315 case Stop{_why}:
1316 False{}
1317
1318
1319def sends(+world: PW.World) -> Bool:
1320 sends.v(decide(world))