1 # add/run: the interpreter for `ez add`. It reads the directory it runs in, 2 # HOME, ez.toml and `.gitignore` into a World, then loops: it asks the 3 # planner (add/plan.bend) what is still open, answers each question with the 4 # lock's interpreter (`Run.answer.ups`: what git prints for tags, HEAD or a 5 # ref, and a checkout at a rev with its NAR hash and every file's text; for a 6 # hub package's name, `Run.answer.all`: what the hub says the name names, and 7 # the package's manifest and files), and adds the answers to the World, until 8 # nothing is left to ask. Then it runs 9 # the planner's plan with the lock's effect runner (`Run.exec.plan`), which 10 # exits 1 with the reason when the plan refuses. It decides nothing; that it 11 # reads and executes faithfully is EZ-TRUST-2. 12 import Base 13 import 0xead4c7939c9eaca8d47da53b4496b731/main.bend as R 14 import ../io/file.bend as F 15 import ../lock/run.bend as Run 16 import ../lock/up.bend as Up 17 import ../lock/world.bend as W 18 import ./world.bend as A 19 import ./plan.bend as AP 20 21 # the World with more answers in it 22 def more(world: A.World, rs: List<&2, Up.Reply>) -> A.World: 23 A.World{args, here, home, ledger, ignore, replies, hubs} = world 24 A.World{args, here, home, ledger, ignore, List.append(&2, Up.Reply, replies, rs), hubs} 25 26 # the World with more of the hub's answers in it 27 def more.hub(world: A.World, rs: List<&2, W.Reply>) -> A.World: 28 A.World{args, here, home, ledger, ignore, replies, hubs} = world 29 A.World{args, here, home, ledger, ignore, replies, List.append(&2, W.Reply, hubs, rs)} 30 31 # the planner's step: its questions answered and the loop gone round again, 32 # or its plan run 33 def loop.step(st: AP.Next, +lib: String, world: A.World, go: A.World -> IO(Unit)) -> IO(Unit): 34 match st: 35 case AP.AskGit{asks}: 36 do IO: 37 rs : List<&2, Up.Reply> <- Run.answer.ups(lib, asks) 38 go(more(world, rs)) 39 case AP.AskHub{asks}: 40 do IO: 41 rs : List<&2, W.Reply> <- Run.answer.all(lib, asks) 42 go(more.hub(world, rs)) 43 case AP.Planned{pl}: 44 Run.exec.plan(lib, pl) 45 46 # the rounds the loop may take. The planner asks at most three rounds for a 47 # git target and two for a name, so this is only a guard against a planner 48 # that keeps asking, and running out of it dies loudly. 49 def rounds() -> Nat: 50 U32.to_nat(100) 51 52 # the loop, under fuel. Every round adds an answer, since the planner never 53 # asks what the World answers already, and it asks at most three rounds for 54 # a git target and two for a name, so the fuel is only a guard. 55 def loop(fuel: Nat, +lib: String, +world: A.World) -> IO(Unit): 56 match fuel: 57 case 0n: 58 IO.die(Unit, 1, "ez: ez add kept asking questions") 59 case 1n+f: 60 loop.step(AP.next(world), lib, world, w => loop(f, lib, w)) 61 62 # `ez add [ref] [entry.bend] [--rename NAME]` 63 def run(+target: String, +ref: String, +entry: String, +rename: String) -> IO(Unit): 64 do IO: 65 lib : String <- Run.bend.lib() 66 +pwd : String <- R.exec(["pwd"]) 67 h : Result<&1, &1, U32 & String, String> <- IO.get_env("HOME") 68 led : Maybe<&2, String> <- F.read(A.toml()) 69 ig : Maybe<&2, String> <- F.read(A.ignore.file()) 70 loop(rounds(), lib, A.World{A.Args{target, ref, entry, rename}, String.trim(R.text(pwd)), 71 Run.env.text(h), led, F.text_of(ig), [], []})