src/tool/plan.bend
ezx@1.11.0.0 · 1,332 lines · 174 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
37import Base
38import ezx@1.11.0.0/src/tool/world.bend as TW
39import ezx@1.11.0.0/src/lock/plan.bend as P
40import ezx@1.11.0.0/src/lock/up.bend as Up
41import ezx@1.11.0.0/src/ledger/manifest.bend as M
42import ezx@1.11.0.0/src/toml/toml.bend as T
43import ezx@1.11.0.0/src/pkg/pkg.bend as K
44import ezx@1.11.0.0/src/pkg/path.bend as Path
45import ezx@1.11.0.0/src/git/git.bend as Git
46import ezx@1.11.0.0/src/ez/target.bend as Tgt
47import ezx@1.11.0.0/src/ez/named.bend as Named
48import ezx@1.11.0.0/src/ez/key.bend as Key
49import ezx@1.11.0.0/src/share/say.bend as Say
50import ezx@1.11.0.0/src/share/sha.bend as Sha
51
52
53
54
55
56def none(got: Maybe<&2, String>) -> Bool:
57 match got:
58 case None{}:
59 True{}
60 case Some{_t}:
61 False{}
62
63
64def links.act(act: TW.Act) -> Bool:
65 match act:
66 case TW.Run{_args}:
67 False{}
68 case TW.Install{}:
69 True{}
70 case TW.Upgrade{}:
71 True{}
72
73
74def tools.of(manifest: M.Manifest) -> List<&2, M.Tool>:
75 M.Manifest{_n, _e, _b, _h, _pa, _pv, _ds, ts} = manifest
76 ts
77
78
79def locked(+body: String) -> List<&2, M.Tool>:
80 M.tools(T.sects(T.parse(body)))
81
82
83
84
85
86
87
88type Src is Data:
89 Pin{name: String, url: String, rev: String, over: String}
90 Free{place: Tgt.Place}
91 NoSrc{why: String}
92
93
94
95def over.of(+binary: String, +entry: String) -> String:
96 Bool.pick(String, String.is_empty(binary), entry, binary)
97
98
99def pin.go(empty: Bool, +tool: M.Tool, +name: String) -> Src:
100 match empty:
101 case True{}:
102 NoSrc{"ez: " ++ name ++ " is not pinned in ez.lock.toml"}
103 case False{}:
104 Pin{name, M.source.url(M.tool.source(tool)), M.source.rev(M.tool.source(tool)),
105 over.of(M.tool.bin(tool), M.tool.entry(tool))}
106
107
108def pin.of(+tool: M.Tool, +name: String) -> Src:
109 pin.go(String.is_empty(M.tool.name(tool)), tool, name)
110
111
112def pin.in(+body: String, +name: String) -> M.Tool:
113 M.tool.find(locked(body), name)
114
115
116def pinned(got: Maybe<&2, String>, +name: String) -> Src:
117 match got:
118 case None{}:
119 NoSrc{"ez: ez.lock.toml is not there"}
120 case Some{body}:
121 pin.of(pin.in(body, name), name)
122
123
124def src.pick(free: Bool, +world: TW.World) -> Src:
125 match free:
126 case True{}:
127 Free{Tgt.classify(TW.target(world))}
128 case False{}:
129 pinned(TW.lock(world), TW.target(world))
130
131
132def unpinned(+manifest: M.Manifest, +name: String) -> Bool:
133 String.is_empty(M.tool.name(M.tool.find(tools.of(manifest), name)))
134
135
136def src.read(led: M.Read, +world: TW.World) -> Src:
137 match led:
138 case M.Bad{why}:
139 NoSrc{"ez: error: " ++ why}
140 case M.Good{m}:
141 src.pick(unpinned(m, TW.target(world)), world)
142
143
144def src(+world: TW.World) -> Src:
145 src.read(TW.project(world), world)
146
147
148def place.pin(pl: Tgt.Place) -> Tgt.Place:
149 match pl:
150 case Tgt.Bad{why}:
151 Tgt.Bad{why}
152 case Tgt.Local{_p}:
153 Tgt.Bad{"a pinned tool names a git repo"}
154 case Tgt.Remote{url, slug}:
155 Tgt.Remote{url, slug}
156
157
158def place.of(found: Src) -> Tgt.Place:
159 match found:
160 case Pin{_n, url, _r, _o}:
161 place.pin(Tgt.classify(url))
162 case Free{pl}:
163 pl
164 case NoSrc{why}:
165 Tgt.Bad{why}
166
167
168def place(+world: TW.World) -> Tgt.Place:
169 place.of(src(world))
170
171
172def over.src(found: Src) -> String:
173 match found:
174 case Pin{_n, _u, _r, o}:
175 o
176 case Free{_pl}:
177 ""
178 case NoSrc{_why}:
179 ""
180
181
182def over(+world: TW.World) -> String:
183 over.src(src(world))
184
185
186
187
188
189type Loc is Data:
190 Loc{abs: String, top: String, head: String, ok: Bool, status: String}
191 LocWait{ask: TW.Ask}
192 LocNo{why: String}
193
194
195def loc.of(got: TW.Heard, ask: TW.Ask) -> Loc:
196 match got:
197 case TW.Open{}:
198 LocWait{ask}
199 case TW.Heard{answer}:
200 match answer:
201 case TW.Located{abs, top, head, ok, status}:
202 Loc{abs, top, head, ok, status}
203 case TW.Miss{why}:
204 LocNo{why}
205 case _:
206 LocNo{"ez: a path was not answered with what git says of it"}
207
208
209def locate(+path: String, +world: TW.World) -> TW.Ask:
210 TW.Locate{Tgt.expand(TW.home(world), path)}
211
212
213def loc(+path: String, +world: TW.World) -> Loc:
214 loc.of(TW.heard(world, locate(path, world)), locate(path, world))
215
216
217def loc.abs(spot: Loc) -> String:
218 match spot:
219 case Loc{abs, _t, _h, _ok, _s}:
220 abs
221 case LocWait{_a}:
222 ""
223 case LocNo{_why}:
224 ""
225
226
227
228
229
230def local.same(same: Bool, +head: String) -> String:
231 match same:
232 case True{}:
233 head
234 case False{}:
235 ""
236
237def local.clean(clean: Bool, same: Bool, +head: String) -> String:
238 match clean:
239 case True{}:
240 local.same(same, head)
241 case False{}:
242 ""
243
244def local.go(ok: Bool, clean: Bool, same: Bool, +head: String) -> String:
245 match ok:
246 case True{}:
247 local.clean(clean, same, head)
248 case False{}:
249 ""
250
251def local.rev(+abs: String, +top: String, +head: String, ok: Bool, +status: String) -> String:
252 local.go(ok, String.is_empty(status), String.eq(top, abs), head)
253
254
255def loc.rev(spot: Loc) -> String:
256 match spot:
257 case Loc{abs, top, head, ok, status}:
258 local.rev(abs, top, head, ok, status)
259 case LocWait{_a}:
260 ""
261 case LocNo{_why}:
262 ""
263
264
265def head.said(+world: TW.World, +url: String) -> Up.Said:
266 Up.printed(TW.ups(world), Up.Head{url}, "ls-remote " ++ url)
267
268
269def head.rev(said: Up.Said) -> String:
270 match said:
271 case Up.Said{text}:
272 Git.tip.rev(Git.rows(String.lines(text)))
273 case Up.Unsaid{_why}:
274 ""
275 case Up.Unasked{_ask}:
276 ""
277
278
279def rev.free(pl: Tgt.Place, +world: TW.World) -> String:
280 match pl:
281 case Tgt.Bad{_why}:
282 ""
283 case Tgt.Local{p}:
284 loc.rev(loc(p, world))
285 case Tgt.Remote{url, _slug}:
286 head.rev(head.said(world, url))
287
288
289def rev.of(found: Src, +world: TW.World) -> String:
290 match found:
291 case Pin{_n, _u, r, _o}:
292 r
293 case Free{pl}:
294 rev.free(pl, world)
295 case NoSrc{_why}:
296 ""
297
298
299def rev(+world: TW.World) -> String:
300 rev.of(src(world), world)
301
302
303
304
305
306def root(+world: TW.World) -> String:
307 TW.text.or(TW.cache.of(world))
308
309
310
311def dir.of(pl: Tgt.Place, +root: String, +world: TW.World) -> String:
312 match pl:
313 case Tgt.Bad{_why}:
314 root
315 case Tgt.Local{p}:
316 root ++ "/local" ++ loc.abs(loc(p, world))
317 case Tgt.Remote{_url, slug}:
318 root ++ "/" ++ slug
319
320
321def dir(+world: TW.World) -> String:
322 dir.of(place(world), root(world), world)
323
324
325
326def revdir(+world: TW.World) -> String:
327 dir(world) ++ "/" ++ rev(world)
328
329
330def stamp.at(+world: TW.World) -> String:
331 revdir(world) ++ "/rev"
332
333
334def top.of(pl: Tgt.Place, +world: TW.World) -> String:
335 match pl:
336 case Tgt.Bad{_why}:
337 ""
338 case Tgt.Local{p}:
339 loc.abs(loc(p, world))
340 case Tgt.Remote{_url, _slug}:
341 revdir(world) ++ "/src"
342
343
344def top(+world: TW.World) -> String:
345 top.of(place(world), world)
346
347
348
349def lib.of(pl: Tgt.Place, +world: TW.World) -> String:
350 match pl:
351 case Tgt.Bad{_why}:
352 ""
353 case Tgt.Local{_p}:
354 dir(world) ++ "/lib"
355 case Tgt.Remote{_url, _slug}:
356 top(world) ++ "/.ez/lib"
357
358
359
360
361
362def text.of(got: TW.Heard) -> Maybe<&2, String>:
363 match got:
364 case TW.Open{}:
365 None{}
366 case TW.Heard{answer}:
367 match answer:
368 case TW.Plain{m}:
369 m
370 case _:
371 None{}
372
373def read.got(+world: TW.World, +path: String) -> Maybe<&2, String>:
374 text.of(TW.heard(world, TW.Read{path}))
375
376
377def open.of(got: TW.Heard) -> Bool:
378 match got:
379 case TW.Open{}:
380 True{}
381 case TW.Heard{_a}:
382 False{}
383
384
385def open(+world: TW.World, +ask: TW.Ask) -> Bool:
386 open.of(TW.heard(world, ask))
387
388
389def opens.step(open: Bool, ask: TW.Ask, rest: List<&2, TW.Ask>) -> List<&2, TW.Ask>:
390 match open:
391 case True{}:
392 ask <> rest
393 case False{}:
394 rest
395
396
397def opens(+world: TW.World, asks: List<&2, TW.Ask>) -> List<&2, TW.Ask>:
398 match asks:
399 case []:
400 []
401 case +h <> t:
402 opens.step(open(world, h), h, opens(world, t))
403
404
405def yes.of(got: TW.Heard) -> Bool:
406 match got:
407 case TW.Open{}:
408 False{}
409 case TW.Heard{answer}:
410 match answer:
411 case TW.Is{yes}:
412 yes
413 case _:
414 False{}
415
416
417
418
419
420
421type Co is Data:
422 Kept{toml: Maybe<&2, String>, lock: Maybe<&2, String>}
423 Laid{files: List<&2, K.Source>}
424 CoWait{asks: List<&2, TW.Ask>, ups: List<&2, Up.Ask>}
425 CoNo{why: String}
426
427
428def clone.ask(+world: TW.World, +url: String) -> Up.Ask:
429 Up.Clone{url, rev(world)}
430
431
432def co.clone(got: Up.Heard, ask: Up.Ask) -> Co:
433 match got:
434 case Up.Open{}:
435 CoWait{[], [ask]}
436 case Up.Heard{answer}:
437 match answer:
438 case Up.Cloned{_nar, fs}:
439 Laid{fs}
440 case Up.Miss{why}:
441 CoNo{why}
442 case _:
443 CoNo{"ez: a checkout was not answered with one"}
444
445
446def reads(+world: TW.World) -> List<&2, TW.Ask>:
447 [TW.Read{stamp.at(world)}, TW.Read{top(world) ++ "/ez.toml"},
448 TW.Read{top(world) ++ "/ez.lock.toml"}]
449
450
451def co.fresh(fresh: Bool, +url: String, +world: TW.World) -> Co:
452 match fresh:
453 case True{}:
454 Kept{read.got(world, top(world) ++ "/ez.toml"), read.got(world, top(world) ++ "/ez.lock.toml")}
455 case False{}:
456 co.clone(Up.heard(TW.ups(world), clone.ask(world, url)), clone.ask(world, url))
457
458
459
460
461def fresh(+world: TW.World) -> Bool:
462 Key.checkout(String.trim(TW.text.or(read.got(world, stamp.at(world)))), rev(world))
463
464
465def co.remote(open: List<&2, TW.Ask>, +url: String, +world: TW.World) -> Co:
466 match open:
467 case []:
468 co.fresh(fresh(world), url, world)
469 case h <> t:
470 CoWait{h <> t, []}
471
472
473def co.local(open: List<&2, TW.Ask>, +world: TW.World) -> Co:
474 match open:
475 case []:
476 Kept{read.got(world, top(world) ++ "/ez.toml"), read.got(world, top(world) ++ "/ez.lock.toml")}
477 case h <> t:
478 CoWait{h <> t, []}
479
480
481def co.of(pl: Tgt.Place, +world: TW.World) -> Co:
482 match pl:
483 case Tgt.Bad{why}:
484 CoNo{"ez: " ++ why}
485 case Tgt.Local{_p}:
486 co.local(opens(world, [TW.Read{top(world) ++ "/ez.toml"},
487 TW.Read{top(world) ++ "/ez.lock.toml"}]), world)
488 case Tgt.Remote{url, _slug}:
489 co.remote(opens(world, reads(world)), url, world)
490
491
492def co(+world: TW.World) -> Co:
493 co.of(place(world), world)
494
495
496def co.toml(checkout: Co) -> Maybe<&2, String>:
497 match checkout:
498 case Kept{t, _l}:
499 t
500 case Laid{fs}:
501 K.look(fs, "ez.toml")
502 case CoWait{_as, _us}:
503 None{}
504 case CoNo{_why}:
505 None{}
506
507
508def co.lock(checkout: Co) -> Maybe<&2, String>:
509 match checkout:
510 case Kept{_t, l}:
511 l
512 case Laid{fs}:
513 K.look(fs, "ez.lock.toml")
514 case CoWait{_as, _us}:
515 None{}
516 case CoNo{_why}:
517 None{}
518
519
520
521
522
523def label.of(pl: Tgt.Place, +world: TW.World) -> String:
524 match pl:
525 case Tgt.Bad{why}:
526 why
527 case Tgt.Local{_p}:
528 top(world)
529 case Tgt.Remote{url, _slug}:
530 url ++ " at " ++ rev(world)
531
532
533def label(+world: TW.World) -> String:
534 label.of(place(world), world)
535
536
537
538def led.of(got: Maybe<&2, String>) -> M.Read:
539 match got:
540 case None{}:
541 M.Good{M.Manifest{"", "", "", "", "", "", [], []}}
542 case Some{text}:
543 M.parse(text)
544
545
546def led(+world: TW.World) -> M.Read:
547 led.of(co.toml(co(world)))
548
549
550
551
552def plain(+world: TW.World) -> Bool:
553 none(co.toml(co(world)))
554
555
556
557
558def lib(+world: TW.World) -> String:
559 Bool.pick(String, plain(world), dir(world) ++ "/lib", lib.of(place(world), world))
560
561
562def man.of(parsed: M.Read) -> M.Manifest:
563 match parsed:
564 case M.Bad{_why}:
565 M.Manifest{"", "", "", "", "", "", [], []}
566 case M.Good{m}:
567 m
568
569
570def man(+world: TW.World) -> M.Manifest:
571 man.of(led(world))
572
573
574def man.name(manifest: M.Manifest) -> String:
575 M.Manifest{n, _e, _b, _h, _pa, _pv, _ds, _ts} = manifest
576 n
577
578
579def man.bin(manifest: M.Manifest) -> String:
580 M.Manifest{_n, _e, b, _h, _pa, _pv, _ds, _ts} = manifest
581 b
582
583
584def man.entry(manifest: M.Manifest) -> String:
585 M.Manifest{_n, e, _b, _h, _pa, _pv, _ds, _ts} = manifest
586 e
587
588
589def stem(+file: String) -> String:
590 Path.unbend(Path.base(file))
591
592
593
594def repo.name(+url: String) -> String:
595 Path.base(Tgt.ungit(Tgt.unslash(url)))
596
597
598
599def plain.name(+file: String, +url: String) -> String:
600 Bool.pick(String, String.eq(stem(file), "main"), repo.name(url), stem(file))
601
602
603
604def origin.of(pl: Tgt.Place, +world: TW.World) -> String:
605 match pl:
606 case Tgt.Bad{_why}:
607 ""
608 case Tgt.Local{_p}:
609 top(world)
610 case Tgt.Remote{url, _slug}:
611 url
612
613
614def origin(+world: TW.World) -> String:
615 origin.of(place(world), world)
616
617
618def bin(+world: TW.World) -> String:
619 man.bin(man(world))
620
621
622def entry(+world: TW.World) -> String:
623 man.entry(man(world))
624
625
626
627def name.ok(+name: String) -> Bool:
628 Bool.or(String.is_empty(name), Named.bare(name))
629
630
631
632def chosen(+over: String, +binary: String, +entry: String) -> String:
633 Bool.pick(String, String.is_empty(over), Tgt.file.of(binary, entry), over)
634
635
636
637
638
639def wish(+over: String, +want: String) -> String:
640 Bool.pick(String, String.is_empty(over), want, over)
641
642
643
644def choice(+world: TW.World) -> String:
645 chosen(wish(over(world), TW.want(world)), bin(world), entry(world))
646
647
648def file(+world: TW.World) -> String:
649 Path.join(top(world), choice(world))
650
651
652def inside(+dir: String, +path: String) -> String:
653 Bool.pick(String, String.starts_with(path, dir ++ "/"),
654 String.drop(path, Nat.add(String.length(dir), 1n)), "")
655
656
657
658def present.of(checkout: Co, +world: TW.World) -> Bool:
659 match checkout:
660 case Laid{fs}:
661 Bool.not(none(K.look(fs, inside(top(world), file(world)))))
662 case Kept{_t, _l}:
663 Bool.and(Bool.not(String.is_empty(inside(top(world), file(world)))),
664 Bool.not(none(read.got(world, file(world)))))
665 case CoWait{_as, _us}:
666 False{}
667 case CoNo{_why}:
668 False{}
669
670
671def present(+world: TW.World) -> Bool:
672 present.of(co(world), world)
673
674
675
676def file.asks.of(checkout: Co, +world: TW.World) -> List<&2, TW.Ask>:
677 match checkout:
678 case Kept{_t, _l}:
679 opens(world, [TW.Read{file(world)}])
680 case Laid{_fs}:
681 []
682 case CoWait{_as, _us}:
683 []
684 case CoNo{_why}:
685 []
686
687def file.asks(+world: TW.World) -> List<&2, TW.Ask>:
688 file.asks.of(co(world), world)
689
690
691
692def name.of(bare: Bool, +world: TW.World) -> String:
693 match bare:
694 case True{}:
695 plain.name(choice(world), origin(world))
696 case False{}:
697 man.name(man(world))
698
699
700def name.raw(+world: TW.World) -> String:
701 name.of(plain(world), world)
702
703
704def name(+world: TW.World) -> String:
705 Tgt.out.name(name.raw(world))
706
707
708def key(+world: TW.World) -> Key.Stamp:
709 Key.Stamp{rev(world), file(world), TW.bend(world)}
710
711
712def stamp.of(key: Key.Stamp) -> String:
713 String.take(Sha.hex(Key.show(key)), 16n)
714
715
716def bdir(+world: TW.World) -> String:
717 dir(world) ++ "/bin/" ++ stamp.of(key(world))
718
719
720def out(+world: TW.World) -> String:
721 bdir(world) ++ "/" ++ name(world) ++ ".out"
722
723def key.at(+world: TW.World) -> String:
724 bdir(world) ++ "/key"
725
726
727def was(+world: TW.World) -> Key.Stamp:
728 Key.read(String.trim(TW.text.or(read.got(world, key.at(world)))))
729
730
731def there(+world: TW.World) -> Bool:
732 yes.of(TW.heard(world, TW.Exec{out(world)}))
733
734
735def reuse(+world: TW.World) -> Bool:
736 Bool.and(there(world), Key.reuse(was(world), key(world)))
737
738
739
740
741
742type Lay is Data:
743 Lay{dir: String, writes: List<&2, P.Effect>}
744 Still{}
745
746
747
748
749
750type Make is Data:
751 Make{top: String, lib: String, fetch: Bool, file: String, out: String, at: String,
752 key: String, name: String}
753 Reuse{name: String}
754 Skip{}
755
756
757
758type Finish is Data:
759 Launch{bin: String, args: List<&2, String>}
760 Link{bin: String, dir: String, name: String, says: List<&2, String>}
761 Idle{}
762
763
764type Plan is Data:
765 Plan{lay: Lay, make: Make, finish: Finish, outcome: P.Outcome}
766
767
768def writes.of(+top: String, fs: List<&2, K.Source>) -> List<&2, P.Effect>:
769 match fs:
770 case []:
771 []
772 case K.Source{at, text} <> t:
773 P.Write{top ++ "/" ++ at, text} <> writes.of(top, t)
774
775
776def lay.of(checkout: Co, +world: TW.World) -> Lay:
777 match checkout:
778 case Laid{fs}:
779 Lay{revdir(world), List.append(&2, P.Effect, writes.of(top(world), fs),
780 [P.Write{stamp.at(world), rev(world) ++ "\n"}])}
781 case Kept{_t, _l}:
782 Still{}
783 case CoWait{_as, _us}:
784 Still{}
785 case CoNo{_why}:
786 Still{}
787
788
789def lay(+world: TW.World) -> Lay:
790 lay.of(co(world), world)
791
792
793def make.of(reused: Bool, +world: TW.World) -> Make:
794 match reused:
795 case True{}:
796 Reuse{name(world)}
797 case False{}:
798 Make{top(world), lib(world), Bool.not(plain(world)), file(world), out(world),
799 key.at(world), Key.show(key(world)), name(world)}
800
801
802def make(+world: TW.World) -> Make:
803 make.of(reuse(world), world)
804
805
806def seen.step(here: Bool, rest: Unit -> Bool) -> Bool:
807 match here:
808 case True{}:
809 True{}
810 case False{}:
811 rest(Unit{})
812
813
814def bare(+dir: String) -> String:
815 Bool.pick(String, String.ends_with(dir, "/"),
816 String.take(dir, Nat.sub(String.length(dir), 1n)), dir)
817
818
819def seen(+dir: String, ps: List<&2, String>) -> Bool:
820 match ps:
821 case []:
822 False{}
823 case +h <> t:
824 seen.step(String.eq(bare(h), bare(dir)), _u => seen(dir, t))
825
826
827def on.path(+dir: String, +path: String) -> Bool:
828 seen(dir, String.split(path, ':'))
829
830
831def bin.at(+world: TW.World) -> String:
832 TW.text.or(TW.bin.of(TW.env(world)))
833
834
835
836def says(install: Bool, +world: TW.World) -> List<&2, String>:
837 +called = name(world)
838 +at = bin.at(world) ++ "/" ++ called
839 +done = Bool.pick(String, install, Say.installed(called, at), Say.linked(called, at))
840 Bool.pick(List<&2, String>, on.path(bin.at(world), TW.env.path(TW.env(world))), [done],
841 ["warning: " ++ bin.at(world) ++ " is not on PATH", done])
842
843
844def finish.of(act: TW.Act, +world: TW.World) -> Finish:
845 match act:
846 case TW.Run{args}:
847 Launch{out(world), args}
848 case TW.Install{}:
849 Link{out(world), bin.at(world), name(world), says(True{}, world)}
850 case TW.Upgrade{}:
851 Link{out(world), bin.at(world), name(world), says(False{}, world)}
852
853
854def finish(+world: TW.World) -> Finish:
855 finish.of(TW.act(world), world)
856
857
858def made(+world: TW.World) -> Plan:
859 Plan{lay(world), make(world), finish(world), P.Success{}}
860
861
862
863
864
865type Verdict is Data:
866 Go{}
867 Pending{asks: List<&2, TW.Ask>, ups: List<&2, Up.Ask>}
868 Stop{why: String}
869
870
871
872def decide.lock(missing: Bool, +world: TW.World) -> Verdict:
873 match missing:
874 case True{}:
875 Stop{"ez: " ++ label(world) ++ " has an ez.toml but no ez.lock.toml; its author " ++
876 "runs ez lock to write one"}
877 case False{}:
878 Go{}
879
880
881def unlocked(+world: TW.World) -> Bool:
882 Bool.and(Bool.not(reuse(world)),
883 Bool.and(Bool.not(plain(world)), none(co.lock(co(world)))))
884
885
886def cache.asks(+world: TW.World) -> List<&2, TW.Ask>:
887 opens(world, [TW.Read{key.at(world)}, TW.Exec{out(world)}])
888
889
890def decide.cache(open: List<&2, TW.Ask>, +world: TW.World) -> Verdict:
891 match open:
892 case []:
893 decide.lock(unlocked(world), world)
894 case h <> t:
895 Pending{h <> t, []}
896
897
898
899def missing.why(+world: TW.World) -> String:
900 +why = "ez: " ++ label(world) ++ " has no " ++ choice(world)
901 Bool.pick(String, String.is_empty(over(world)),
902 why ++ "; name the file to build with --entry <path.bend>", why)
903
904
905def decide.present(ok: Bool, +world: TW.World) -> Verdict:
906 match ok:
907 case True{}:
908 decide.cache(cache.asks(world), world)
909 case False{}:
910 Stop{missing.why(world)}
911
912
913def decide.file(open: List<&2, TW.Ask>, +world: TW.World) -> Verdict:
914 match open:
915 case []:
916 decide.present(present(world), world)
917 case h <> t:
918 Pending{h <> t, []}
919
920
921
922def name.what(+world: TW.World) -> String:
923 Bool.pick(String, plain(world), " would be linked as ", " names its package ")
924
925
926def decide.name(ok: Bool, +world: TW.World) -> Verdict:
927 match ok:
928 case True{}:
929 decide.file(file.asks(world), world)
930 case False{}:
931 Stop{"ez: " ++ label(world) ++ name.what(world) ++ name.raw(world) ++
932 ", which is not a TOML bare key"}
933
934
935def decide.led(parsed: M.Read, +world: TW.World) -> Verdict:
936 match parsed:
937 case M.Bad{why}:
938 Stop{"ez: error: " ++ why}
939 case M.Good{_m}:
940 decide.name(name.ok(name.raw(world)), world)
941
942
943def decide.co(checkout: Co, +world: TW.World) -> Verdict:
944 match checkout:
945 case Kept{_t, _l}:
946 decide.led(led(world), world)
947 case Laid{_fs}:
948 decide.led(led(world), world)
949 case CoWait{as, us}:
950 Pending{as, us}
951 case CoNo{why}:
952 Stop{why}
953
954
955def decide.loc(spot: Loc, +world: TW.World) -> Verdict:
956 match spot:
957 case Loc{_a, _t, _h, _ok, _s}:
958 decide.co(co(world), world)
959 case LocWait{ask}:
960 Pending{[ask], []}
961 case LocNo{why}:
962 Stop{why}
963
964
965def decide.tip(ok: Bool, +url: String, +world: TW.World) -> Verdict:
966 match ok:
967 case True{}:
968 decide.co(co(world), world)
969 case False{}:
970 Stop{"ez: " ++ url ++ " has no HEAD"}
971
972def decide.said(said: Up.Said, +url: String, +world: TW.World) -> Verdict:
973 match said:
974 case Up.Said{_text}:
975 decide.tip(Git.is_rev(rev(world)), url, world)
976 case Up.Unsaid{why}:
977 Stop{why}
978 case Up.Unasked{ask}:
979 Pending{[], [ask]}
980
981
982def decide.head(found: Src, +url: String, +world: TW.World) -> Verdict:
983 match found:
984 case Pin{_n, _u, _r, _o}:
985 decide.co(co(world), world)
986 case Free{_pl}:
987 decide.said(head.said(world, url), url, world)
988 case NoSrc{why}:
989 Stop{why}
990
991
992def decide.slug(ok: Bool, +url: String, +world: TW.World) -> Verdict:
993 match ok:
994 case True{}:
995 decide.head(src(world), url, world)
996 case False{}:
997 Stop{"ez: not a repository"}
998
999def decide.place(pl: Tgt.Place, +world: TW.World) -> Verdict:
1000 match pl:
1001 case Tgt.Bad{why}:
1002 Stop{"ez: " ++ why}
1003 case Tgt.Local{p}:
1004 decide.loc(loc(p, world), world)
1005 case Tgt.Remote{url, slug}:
1006 decide.slug(Tgt.slug.ok(slug), url, world)
1007
1008
1009
1010def decide.pin(ok: Bool, +name: String, +rev: String, +world: TW.World) -> Verdict:
1011 match ok:
1012 case True{}:
1013 decide.place(place(world), world)
1014 case False{}:
1015 Stop{"ez: tools." ++ name ++ " in ez.lock.toml has no commit" ++
1016 Bool.pick(String, String.is_empty(rev), "", ": " ++ rev)}
1017
1018
1019def decide.src(found: Src, +world: TW.World) -> Verdict:
1020 match found:
1021 case Pin{+n, _u, +r, _o}:
1022 decide.pin(Git.is_rev(r), n, r, world)
1023 case Free{_pl}:
1024 decide.place(place(world), world)
1025 case NoSrc{why}:
1026 Stop{why}
1027
1028
1029def decide.root(got: Maybe<&2, String>, +world: TW.World) -> Verdict:
1030 match got:
1031 case None{}:
1032 Stop{"ez: HOME is unset, so there is no cache"}
1033 case Some{_r}:
1034 decide.src(src(world), world)
1035
1036
1037def decide.bin(nowhere: Bool, +world: TW.World) -> Verdict:
1038 match nowhere:
1039 case True{}:
1040 Stop{"ez: HOME is unset, so there is no bin directory"}
1041 case False{}:
1042 decide.root(TW.cache.of(world), world)
1043
1044
1045def decide(+world: TW.World) -> Verdict:
1046 decide.bin(Bool.and(links.act(TW.act(world)), none(TW.bin.of(TW.env(world)))), world)
1047
1048
1049
1050def plan.v(verdict: Verdict, +world: TW.World) -> Plan:
1051 match verdict:
1052 case Go{}:
1053 made(world)
1054 case Pending{_as, _us}:
1055 Plan{Still{}, Skip{}, Idle{}, P.Refused{"ez: " ++ TW.target(world) ++ " was never resolved"}}
1056 case Stop{why}:
1057 Plan{Still{}, Skip{}, Idle{}, P.Refused{why}}
1058
1059
1060def plan(+world: TW.World) -> Plan:
1061 plan.v(decide(world), world)
1062
1063
1064def refuses.plan(pl: Plan) -> Bool:
1065 Plan{_l, _m, _f, outcome} = pl
1066 match outcome:
1067 case P.Success{}:
1068 False{}
1069 case P.Refused{_why}:
1070 True{}
1071
1072
1073def refuses(+world: TW.World) -> Bool:
1074 refuses.plan(plan(world))
1075
1076
1077type Step is Data:
1078 Asking{asks: List<&2, TW.Ask>, ups: List<&2, Up.Ask>}
1079 Run{plan: Plan}
1080
1081
1082def step.v(verdict: Verdict, +world: TW.World) -> Step:
1083 match verdict:
1084 case Go{}:
1085 Run{made(world)}
1086 case Pending{as, us}:
1087 Asking{as, us}
1088 case Stop{why}:
1089 Run{plan.v(Stop{why}, world)}
1090
1091
1092def step(+world: TW.World) -> Step:
1093 step.v(decide(world), world)
1094
1095
1096
1097
1098
1099
1100type Sync is Data:
1101 Sync{names: List<&2, String>}
1102 SyncNo{why: String}
1103
1104
1105
1106def sync.step(bad: Bool, +name: String, rest: Unit -> String) -> String:
1107 match bad:
1108 case True{}:
1109 "ez: tools." ++ name ++ " is not pinned at a commit in ez.lock.toml"
1110 case False{}:
1111 rest(Unit{})
1112
1113
1114def sync.pinned(+tool: M.Tool) -> Bool:
1115 Bool.and(Bool.not(String.is_empty(M.tool.name(tool))),
1116 Git.is_rev(M.source.rev(M.tool.source(tool))))
1117
1118
1119
1120def sync.check(ts: List<&2, M.Tool>, +body: String) -> String:
1121 match ts:
1122 case []:
1123 ""
1124 case +h <> t:
1125 sync.step(Bool.not(sync.pinned(pin.in(body, M.tool.name(h)))), M.tool.name(h),
1126 _u => sync.check(t, body))
1127
1128
1129def names.of(ts: List<&2, M.Tool>) -> List<&2, String>:
1130 match ts:
1131 case []:
1132 []
1133 case h <> t:
1134 M.tool.name(h) <> names.of(t)
1135
1136
1137def sync.why(ok: Bool, +why: String, ts: List<&2, M.Tool>) -> Sync:
1138 match ok:
1139 case True{}:
1140 Sync{names.of(ts)}
1141 case False{}:
1142 SyncNo{why}
1143
1144
1145def sync.lock(got: Maybe<&2, String>, +ts: List<&2, M.Tool>) -> Sync:
1146 match got:
1147 case None{}:
1148 Bool.pick(Sync, List.is_empty(&2, M.Tool, ts), Sync{[]},
1149 SyncNo{"ez: ez.lock.toml is not there"})
1150 case Some{body}:
1151 +why = sync.check(ts, body)
1152 sync.why(String.is_empty(why), why, ts)
1153
1154
1155def sync.read(led: M.Read, got: Maybe<&2, String>) -> Sync:
1156 match led:
1157 case M.Bad{why}:
1158 SyncNo{"ez: error: " ++ why}
1159 case M.Good{m}:
1160 sync.lock(got, tools.of(m))
1161
1162
1163
1164
1165def sync(ledger: Maybe<&2, String>, lock: Maybe<&2, String>) -> Sync:
1166 match ledger:
1167 case None{}:
1168 SyncNo{"ez: no ez.toml here; ez tool sync installs the tools a project's " ++
1169 "ledger pins, and ez init makes one"}
1170 case Some{text}:
1171 sync.read(M.parse(text), lock)
1172
1173
1174
1175
1176def sync.outcome(synced: Sync) -> P.Outcome:
1177 match synced:
1178 case Sync{_ns}:
1179 P.Success{}
1180 case SyncNo{why}:
1181 P.Refused{why}
1182
1183
1184def sync.refuses(synced: Sync) -> Bool:
1185 match synced:
1186 case Sync{_ns}:
1187 False{}
1188 case SyncNo{_why}:
1189 True{}
1190
1191
1192def sync.names(synced: Sync) -> List<&2, String>:
1193 match synced:
1194 case Sync{ns}:
1195 ns
1196 case SyncNo{_why}:
1197 []
1198
1199
1200
1201
1202
1203def lays(layout: Lay) -> Bool:
1204 match layout:
1205 case Lay{_d, _ws}:
1206 True{}
1207 case Still{}:
1208 False{}
1209
1210
1211def makes(making: Make) -> Bool:
1212 match making:
1213 case Make{_lk, _lb, _fe, _f, _o, _at, _k, _n}:
1214 True{}
1215 case Reuse{_n}:
1216 False{}
1217 case Skip{}:
1218 False{}
1219
1220
1221def linked(fin: Finish) -> Bool:
1222 match fin:
1223 case Launch{_b, _as}:
1224 False{}
1225 case Link{_b, _d, _n, _s}:
1226 True{}
1227 case Idle{}:
1228 False{}
1229
1230
1231def writes(pl: Plan) -> Bool:
1232 Plan{l, m, f, _o} = pl
1233 Bool.or(lays(l), Bool.or(makes(m), linked(f)))
1234
1235
1236def builds(pl: Plan) -> Bool:
1237 Plan{_l, m, _f, _o} = pl
1238 makes(m)
1239
1240
1241def built(pl: Plan) -> String:
1242 Plan{_l, m, _f, _o} = pl
1243 match m:
1244 case Make{_lk, _lb, _fe, f, _o, _at, _k, _n}:
1245 f
1246 case Reuse{_n}:
1247 ""
1248 case Skip{}:
1249 ""
1250
1251
1252def fetches(pl: Plan) -> Bool:
1253 Plan{_l, m, _f, _o} = pl
1254 match m:
1255 case Make{_lk, _lb, fe, _f, _o, _at, _k, _n}:
1256 fe
1257 case Reuse{_n}:
1258 False{}
1259 case Skip{}:
1260 False{}
1261
1262
1263def built.lib(pl: Plan) -> String:
1264 Plan{_l, m, _f, _o} = pl
1265 match m:
1266 case Make{_lk, lb, _fe, _f, _o, _at, _k, _n}:
1267 lb
1268 case Reuse{_n}:
1269 ""
1270 case Skip{}:
1271 ""
1272
1273
1274def launches(pl: Plan) -> Bool:
1275 Plan{_l, _m, f, _o} = pl
1276 match f:
1277 case Launch{_b, _as}:
1278 True{}
1279 case Link{_b, _d, _n, _s}:
1280 False{}
1281 case Idle{}:
1282 False{}
1283
1284
1285def link.at(pl: Plan) -> String:
1286 Plan{_l, _m, f, _o} = pl
1287 match f:
1288 case Launch{_b, _as}:
1289 ""
1290 case Link{_b, d, n, _s}:
1291 d ++ "/" ++ n
1292 case Idle{}:
1293 ""
1294
1295
1296
1297type Exit is Data:
1298 Refusal{}
1299 Finished{}
1300 Exits{bin: String, args: List<&2, String>}
1301
1302
1303def exit.of(fin: Finish) -> Exit:
1304 match fin:
1305 case Launch{b, as}:
1306 Exits{b, as}
1307 case Link{_b, _d, _n, _s}:
1308 Finished{}
1309 case Idle{}:
1310 Finished{}
1311
1312
1313def exit(pl: Plan) -> Exit:
1314 Plan{_l, _m, f, o} = pl
1315 match o:
1316 case P.Success{}:
1317 exit.of(f)
1318 case P.Refused{_why}:
1319 Refusal{}
1320
1321
1322
1323
1324
1325def code(ended: Exit, program: U32) -> U32:
1326 match ended:
1327 case Refusal{}:
1328 1
1329 case Finished{}:
1330 0
1331 case Exits{_b, _as}:
1332 program