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 --publish`, followed by the `@` 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. 21 import Base 22 import 0xead4c7939c9eaca8d47da53b4496b731/main.bend as R 23 import ../io/file.bend as F 24 import ../lock/run.bend as Run 25 import ../tool/run.bend as ToolRun 26 import ../tool/world.bend as TW 27 import ../hub/hub.bend as Web 28 import ../hub/get.bend as Net 29 import ../share/env.bend as Env 30 import ./world.bend as PW 31 import ./plan.bend as PP 32 33 # what a program printed, and whether it exited 0 34 def ran(+out: String) -> PW.Answer: 35 PW.Ran{R.ok(out), R.text(out)} 36 37 # one question, answered 38 def answer(+lib: String, ask: PW.Ask) -> IO(PW.Answer): 39 match ask: 40 case PW.Tool{q}: 41 do IO: 42 a : TW.Answer <- ToolRun.answer(q) 43 return PW.Told{a} 44 case PW.Tracked{}: 45 do IO: 46 +out : String <- R.exec(["git", "-c", "core.quotePath=false", "ls-files", "-v"]) 47 return ran(out) 48 case PW.Upload{+entry, +named, says}: 49 do IO: 50 IO.print_err(says) 51 +out : String <- R.exec(Env.line(lib, PP.upload.line(entry, named))) 52 return ran(out) 53 case PW.Lock{}: 54 do IO: 55 m : Maybe<&2, String> <- F.read(PW.lockfile()) 56 return PW.Told{TW.Plain{m}} 57 case PW.Hub{+url}: 58 do IO: 59 +out : String <- Net.get(url) 60 return ran(out) 61 62 # every question, answered 63 def answer.all(+lib: String, asks: List<&2, PW.Ask>) -> IO(List<&2, PW.Reply>): 64 match asks: 65 case []: 66 IO.pure(List<&2, PW.Reply>, []) 67 case +h <> t: 68 do IO>: 69 a : PW.Answer <- answer(lib, h) 70 rest : List<&2, PW.Reply> <- answer.all(lib, t) 71 return PW.Reply{h, a} <> rest 72 73 # the World with more answers in it 74 def more(world: PW.World, rs: List<&2, PW.Reply>) -> PW.World: 75 PW.World{here, ledger, replies} = world 76 PW.World{here, ledger, List.append(&2, PW.Reply, replies, rs)} 77 78 # the planner's step: its questions answered and the loop gone round again, 79 # or its plan run 80 def loop.step(st: PP.Step, +lib: String, world: PW.World, go: PW.World -> IO(Unit)) -> IO(Unit): 81 match st: 82 case PP.Asking{asks}: 83 do IO: 84 rs : List<&2, PW.Reply> <- answer.all(lib, asks) 85 go(more(world, rs)) 86 case PP.Run{pl}: 87 Run.exec.plan(lib, pl) 88 89 # the rounds the loop may take. The walk asks for one file a round and cannot 90 # know how many the package holds until it has read them, so this is a guard 91 # against a planner that keeps asking, not a limit on a package: running out 92 # of it dies loudly. 93 def rounds() -> Nat: 94 U32.to_nat(100000) 95 96 # the loop, under fuel. The walk asks for one file a round, so the fuel is a 97 # guard against a planner that keeps asking, not a limit on a package. 98 def loop(fuel: Nat, +lib: String, +world: PW.World) -> IO(Unit): 99 match fuel: 100 case 0n: 101 IO.die(Unit, 1, "ez: ez publish kept asking questions") 102 case 1n+f: 103 loop.step(PP.step(world), lib, world, w => loop(f, lib, w)) 104 105 # `ez publish`: the package the project's ledger names, checked, sent, and 106 # the hash bend answered checked against ez's own 107 def run() -> IO(Unit): 108 do IO: 109 lib : String <- Env.lib() 110 +pwd : String <- R.exec(["pwd"]) 111 led : Maybe<&2, String> <- F.read(PW.toml()) 112 loop(rounds(), lib, PW.World{String.trim(R.text(pwd)), led, []})