src/pub/run.bend

ezx@1.11.0.0 · 114 lines · 8 definitions

1# pub/run: the interpreter for `ez publish`. It reads the directory the
2# project is in and the ledger's text into a World, then loops: it asks the
3# planner (pub/plan.bend) what is still open, answers each question, and adds
4# the answers to the World, until nothing is left to ask. Then it runs the
5# planner's plan with the lock's effect runner (`Run.exec.plan`), which prints
6# what a publish that agreed says, or exits 1 with the reason.
7#
8# What git says of the project and a file's text are answered as the tool
9# commands answer them (`ToolRun.answer`). What git tracks is `git ls-files
10# -v` in the project, with paths written as they are rather than quoted. The
11# upload is `bend <entry> --publish`, followed by the `<name>@<version>` the
12# planner put in the question when the ledger names one, with the project's
13# packages on BEND_LIB, the way every other `bend` ez starts has them; the
14# planner asks for it last, once every other check has passed. Just before it
15# runs, the line the question carries, the hub description, goes to stderr,
16# where bend's own notices go, so stdout stays the hash and the import line.
17# Before that, the lock is read as a file, and a url under the hub is asked
18# for with the lock's own GET (`Web.get`), whose answer is a status line and
19# the text: whether the hub answered 200, and the body or why not.
20# It decides nothing; that it reads and executes faithfully is EZ-TRUST-2.
21import Base
22import snap@1.4.0.0/main.bend as R
23import ezx@1.11.0.0/src/io/file.bend as F
24import ezx@1.11.0.0/src/lock/run.bend as Run
25import ezx@1.11.0.0/src/tool/run.bend as ToolRun
26import ezx@1.11.0.0/src/tool/world.bend as TW
27import ezx@1.11.0.0/src/hub/hub.bend as Web
28import ezx@1.11.0.0/src/hub/get.bend as Net
29import ezx@1.11.0.0/src/share/env.bend as Env
30import ezx@1.11.0.0/src/pub/world.bend as PW
31import ezx@1.11.0.0/src/pub/plan.bend as PP
32import ezx@1.11.0.0/src/ws/run.bend as WsRun
33
34# what a program printed, and whether it exited 0
35def ran(+out: String) -> PW.Answer:
36  PW.Ran{R.ok(out), R.text(out)}
37
38# one question, answered
39def answer(+lib: String, ask: PW.Ask) -> IO(PW.Answer):
40  match ask:
41    case PW.Tool{q}:
42      do IO<PW.Answer>:
43        a : TW.Answer <- ToolRun.answer(q)
44        return PW.Told{a}
45    case PW.Tracked{}:
46      do IO<PW.Answer>:
47        +out : String <- R.exec(["git", "-c", "core.quotePath=false", "ls-files", "-v"])
48        return ran(out)
49    case PW.Upload{+entry, +named, says}:
50      do IO<PW.Answer>:
51        IO.print_err(says)
52        +out : String <- R.exec(Env.line(lib, PP.upload.line(entry, named)))
53        return ran(out)
54    case PW.Lock{}:
55      do IO<PW.Answer>:
56        at : String <- WsRun.lock.path()
57        m : Maybe<&2, String> <- F.read(at)
58        return PW.Told{TW.Plain{m}}
59    case PW.Hub{+url}:
60      do IO<PW.Answer>:
61        +out : String <- Net.get(url)
62        return ran(out)
63
64# every question, answered
65def answer.all(+lib: String, asks: List<&2, PW.Ask>) -> IO(List<&2, PW.Reply>):
66  match asks:
67    case []:
68      IO.pure(List<&2, PW.Reply>, [])
69    case +h <> t:
70      do IO<List<&2, PW.Reply>>:
71        a : PW.Answer <- answer(lib, h)
72        rest : List<&2, PW.Reply> <- answer.all(lib, t)
73        return PW.Reply{h, a} <> rest
74
75# the World with more answers in it
76def more(world: PW.World, rs: List<&2, PW.Reply>) -> PW.World:
77  PW.World{here, ledger, replies} = world
78  PW.World{here, ledger, List.append(&2, PW.Reply, replies, rs)}
79
80# the planner's step: its questions answered and the loop gone round again,
81# or its plan run
82def loop.step(st: PP.Step, +lib: String, world: PW.World, go: PW.World -> IO(Unit)) -> IO(Unit):
83  match st:
84    case PP.Asking{asks}:
85      do IO<Unit>:
86        rs : List<&2, PW.Reply> <- answer.all(lib, asks)
87        go(more(world, rs))
88    case PP.Run{pl}:
89      Run.exec.plan(lib, pl)
90
91# the rounds the loop may take. The walk asks for one file a round and cannot
92# know how many the package holds until it has read them, so this is a guard
93# against a planner that keeps asking, not a limit on a package: running out
94# of it dies loudly.
95def rounds() -> Nat:
96  U32.to_nat(100000)
97
98# the loop, under fuel. The walk asks for one file a round, so the fuel is a
99# guard against a planner that keeps asking, not a limit on a package.
100def loop(fuel: Nat, +lib: String, +world: PW.World) -> IO(Unit):
101  match fuel:
102    case 0n:
103      IO.die(Unit, 1, "ez: ez publish kept asking questions")
104    case 1n+f:
105      loop.step(PP.step(world), lib, world, w => loop(f, lib, w))
106
107# `ez publish`: the package the project's ledger names, checked, sent, and
108# the hash bend answered checked against ez's own
109def run() -> IO(Unit):
110  do IO<Unit>:
111    lib : String <- Env.lib()
112    +pwd : String <- R.exec(["pwd"])
113    led : Maybe<&2, String> <- F.read(PW.toml())
114    loop(rounds(), lib, PW.World{String.trim(R.text(pwd)), led, []})