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 0xcab8a7a189cec2b51e8db0484f69c593/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 23 # what a subcommand runs. `L.line` never runs `CHelp`, which names no 24 # command; it is here so the match is whole, and it writes the help a bare 25 # `ez` would. The directories `ez check` and `ez build` write into are made 26 # by those commands once they know they will start bend (`Env.dirs`), so a 27 # refused one leaves the directory as it found it. 28 def dispatch(sub: L.Sub, +matched: Shake.Matched, +as: List<&2, String>) -> IO(Unit): 29 match sub: 30 case L.CInit{}: 31 Cmd.init(L.get(matched, "name"), L.get(matched, "entry"), 32 L.get(matched, "description")) 33 case L.CAdd{}: 34 Cmd.add(L.get(matched, "target"), L.get(matched, "ref"), 35 L.get(matched, "entry"), L.get(matched, "rename")) 36 case L.CRemove{}: 37 Cmd.remove(L.get(matched, "name")) 38 case L.CLock{}: 39 Cmd.lock(Shake.on(matched, "upgrade"), L.get(matched, "package")) 40 case L.CFetch{}: 41 Cmd.fetch() 42 case L.CCheck{}: 43 Cmd.check() 44 case L.CBuild{}: 45 Cmd.build(L.get(matched, "out")) 46 case L.CRun{}: 47 Cmd.run(as) 48 case L.CToolSync{}: 49 ToolRun.sync() 50 case L.CToolRun{}: 51 ToolRun.run(TW.Run{Args.tool.rest(as)}, L.get(matched, "target"), 52 L.get(matched, "entry")) 53 case L.CToolInstall{}: 54 ToolRun.run(TW.Install{}, L.get(matched, "target"), L.get(matched, "entry")) 55 case L.CToolUpgrade{}: 56 ToolRun.run(TW.Upgrade{}, L.get(matched, "target"), L.get(matched, "entry")) 57 case L.CPublish{}: 58 Cmd.publish() 59 case L.CTest{}: 60 Test.run() 61 case L.CProve{}: 62 Prove.run() 63 case L.CDoctor{}: 64 DoctorRun.run() 65 case L.CHelp{}: 66 IO.write(Shake.help(L.spec(), [])) 67 68 # `sync` with no tools is lock or install; a ledger that pins tools already 69 # names the command that builds them. Anything else is the parser's own error. 70 def hint.sync(is_sync: Bool) -> IO(Unit): 71 match is_sync: 72 case False{}: 73 IO.pure(Unit, Unit{}) 74 case True{}: 75 do IO: 76 m : Maybe<&2, String> <- F.read("ez.toml") 77 IO.print_err(Say.hint(Say.has.tools(F.text_of(m)))) 78 79 # what a line says: the command it names run, its help on stdout, or what 80 # was wrong with it on stderr, with a hint for `ez sync` 81 def go.say(how: L.Line, +as: List<&2, String>) -> IO(Unit): 82 match how: 83 case L.Runs{sub, +m}: 84 dispatch(sub, m, as) 85 case L.Shows{text}: 86 IO.write(text) 87 case L.Misuse{text}: 88 do IO: 89 IO.print_err(text) 90 hint.sync(String.eq(Args.at(as, 0n), "sync")) 91 92 # a status that is 0 ends nothing, and any other exits with it 93 def go.exit(zero: Bool, +code: U32) -> IO(Unit): 94 match zero: 95 case True{}: 96 IO.pure(Unit, Unit{}) 97 case False{}: 98 IO.die(Unit, code, "") 99 100 # a status decided before any command ran, exited with; a command's own end 101 # is its own 102 def go.end(st: L.Status) -> IO(Unit): 103 match st: 104 case L.Exits{+code}: 105 go.exit(U32.is_eq(code, 0), code) 106 case L.Ends{}: 107 IO.pure(Unit, Unit{}) 108 109 # how the line ends, as `L.line` decided, and the status `L.status` gives it 110 def go.line(+how: L.Line, +as: List<&2, String>) -> IO(Unit): 111 do IO: 112 go.say(how, as) 113 go.end(L.status(how)) 114 115 # the line taken apart 116 def go(+app: Shake.Cli, +as: List<&2, String>) -> IO(Unit): 117 go.line(L.line(app, Shake.parse(app, Args.argv.of(as))), as) 118 119 def main() -> IO(Unit): 120 do IO: 121 as : List<&2, String> <- Args.all() 122 go(L.spec(), as)