1 # ez: the one binary. The line is a Shake.Cli (`L.spec`): `parse` binds the 2 # command and its flags, and `get` / `on` read the bindings. How a line ends 3 # is decided in ez/line.bend (`L.line`, `L.status`): a command runs, help is 4 # shown and the line exits 0, or the line is misused and exits 1. This file 5 # prints and exits as they say and runs the command, which is trusted under 6 # EZ-TRUST-2. `IO.args()` is copied so each word can be read more than once. 7 # The repo's top-level main.bend calls this `main`, and is the file that gets 8 # built. Build it native (`bend main.bend -o bin/ez.bin`); interpreted, bend's 9 # own CLI would take the flags meant for ez. 10 import Base 11 import 0xa383b26a5a0ef35663b3615d7aedf9e3/main.bend as Shake 12 import ../share/args.bend as Args 13 import ./cmd.bend as Cmd 14 import ./test.bend as Test 15 import ./prove.bend as Prove 16 import ../doctor/run.bend as DoctorRun 17 import ../tool/run.bend as ToolRun 18 import ../tool/world.bend as TW 19 import ../share/say.bend as Say 20 import ../io/file.bend as F 21 import ../ez/line.bend as L 22 import ../ws/run.bend as WsRun 23 24 # what a subcommand runs. `L.line` never runs `CHelp`, which names no 25 # command; it is here so the match is whole, and it writes the help a bare 26 # `ez` would. The directories `ez check` and `ez build` write into are made 27 # by those commands once they know they will start bend (`Env.dirs`), so a 28 # refused one leaves the directory as it found it. 29 def dispatch(sub: L.Sub, +matched: Shake.Matched, +as: List<&2, String>) -> IO(Unit): 30 match sub: 31 case L.CInit{}: 32 Cmd.init(L.get(matched, "name"), L.get(matched, "entry"), 33 L.get(matched, "description")) 34 case L.CAdd{}: 35 +target = L.get(matched, "target") 36 +ref = L.get(matched, "ref") 37 +entry = L.get(matched, "entry") 38 +rename = L.get(matched, "rename") 39 WsRun.in.member(_u => Cmd.add(target, ref, entry, rename)) 40 case L.CRemove{}: 41 +name = L.get(matched, "name") 42 WsRun.in.member(_u => Cmd.remove(name)) 43 case L.CLock{}: 44 +up = Shake.on(matched, "upgrade") 45 +only = L.get(matched, "package") 46 WsRun.at.root(_u => Cmd.lock(up, only)) 47 case L.CFetch{}: 48 WsRun.at.root(_u => Cmd.fetch()) 49 case L.CCheck{}: 50 WsRun.in.member(_u => Cmd.check()) 51 case L.CBuild{}: 52 +out = L.get(matched, "out") 53 WsRun.in.member(_u => Cmd.build(out)) 54 case L.CRun{}: 55 WsRun.in.member(_u => Cmd.run(as)) 56 case L.CToolSync{}: 57 ToolRun.sync() 58 case L.CToolRun{}: 59 ToolRun.run(TW.Run{Args.tool.rest(as)}, L.get(matched, "target"), 60 L.get(matched, "entry")) 61 case L.CToolInstall{}: 62 ToolRun.run(TW.Install{}, L.get(matched, "target"), L.get(matched, "entry")) 63 case L.CToolUpgrade{}: 64 ToolRun.run(TW.Upgrade{}, L.get(matched, "target"), L.get(matched, "entry")) 65 case L.CPublish{}: 66 WsRun.in.member(_u => Cmd.publish()) 67 case L.CTest{}: 68 Test.run() 69 case L.CProve{}: 70 Prove.run() 71 case L.CDoctor{}: 72 WsRun.at.root(_u => DoctorRun.run()) 73 case L.CHelp{}: 74 IO.write(Shake.help(L.spec(), [])) 75 76 # `sync` with no tools is lock or install; a ledger that pins tools already 77 # names the command that builds them. Anything else is the parser's own error. 78 def hint.sync(is_sync: Bool) -> IO(Unit): 79 match is_sync: 80 case False{}: 81 IO.pure(Unit, Unit{}) 82 case True{}: 83 do IO: 84 m : Maybe<&2, String> <- F.read("ez.toml") 85 IO.print_err(Say.hint(Say.has.tools(F.text_of(m)))) 86 87 # what a line says: the command it names run, its help on stdout, or what 88 # was wrong with it on stderr, with a hint for `ez sync` 89 def go.say(how: L.Line, +as: List<&2, String>) -> IO(Unit): 90 match how: 91 case L.Runs{sub, +m}: 92 dispatch(sub, m, as) 93 case L.Shows{text}: 94 IO.write(text) 95 case L.Misuse{text}: 96 do IO: 97 IO.print_err(text) 98 hint.sync(String.eq(Args.at(as, 0n), "sync")) 99 100 # a status that is 0 ends nothing, and any other exits with it 101 def go.exit(zero: Bool, +code: U32) -> IO(Unit): 102 match zero: 103 case True{}: 104 IO.pure(Unit, Unit{}) 105 case False{}: 106 IO.die(Unit, code, "") 107 108 # a status decided before any command ran, exited with; a command's own end 109 # is its own 110 def go.end(st: L.Status) -> IO(Unit): 111 match st: 112 case L.Exits{+code}: 113 go.exit(U32.is_eq(code, 0), code) 114 case L.Ends{}: 115 IO.pure(Unit, Unit{}) 116 117 # how the line ends, as `L.line` decided, and the status `L.status` gives it 118 def go.line(+how: L.Line, +as: List<&2, String>) -> IO(Unit): 119 do IO: 120 go.say(how, as) 121 go.end(L.status(how)) 122 123 # the line taken apart 124 def go(+app: Shake.Cli, +as: List<&2, String>) -> IO(Unit): 125 go.line(L.line(app, Shake.parse(app, Args.argv.of(as))), as) 126 127 def main() -> IO(Unit): 128 do IO: 129 as : List<&2, String> <- Args.all() 130 go(L.spec(), as)