src/ez/main.bend
ezx@1.10.0.0 · 130 lines · 0 definitions
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.
10import Base
11import shake@0.6.0.0/main.bend as Shake
12import ezx@1.10.0.0/src/share/args.bend as Args
13import ezx@1.10.0.0/src/ez/cmd.bend as Cmd
14import ezx@1.10.0.0/src/ez/test.bend as Test
15import ezx@1.10.0.0/src/ez/prove.bend as Prove
16import ezx@1.10.0.0/src/doctor/run.bend as DoctorRun
17import ezx@1.10.0.0/src/tool/run.bend as ToolRun
18import ezx@1.10.0.0/src/tool/world.bend as TW
19import ezx@1.10.0.0/src/share/say.bend as Say
20import ezx@1.10.0.0/src/io/file.bend as F
21import ezx@1.10.0.0/src/ez/line.bend as L
22import ezx@1.10.0.0/src/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.
29def 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.
78def 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<Unit>:
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`
89def 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<Unit>:
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
101def 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
110def 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
118def go.line(+how: L.Line, +as: List<&2, String>) -> IO(Unit):
119 do IO<Unit>:
120 go.say(how, as)
121 go.end(L.status(how))
122
123# the line taken apart
124def 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
127def main() -> IO(Unit):
128 do IO<Unit>:
129 as : List<&2, String> <- Args.all()
130 go(L.spec(), as)