src/ez/main.bend
ezx@1.8.0.0 · 122 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.8.0.0/src/share/args.bend as Args
13import ezx@1.8.0.0/src/ez/cmd.bend as Cmd
14import ezx@1.8.0.0/src/ez/test.bend as Test
15import ezx@1.8.0.0/src/ez/prove.bend as Prove
16import ezx@1.8.0.0/src/doctor/run.bend as DoctorRun
17import ezx@1.8.0.0/src/tool/run.bend as ToolRun
18import ezx@1.8.0.0/src/tool/world.bend as TW
19import ezx@1.8.0.0/src/share/say.bend as Say
20import ezx@1.8.0.0/src/io/file.bend as F
21import ezx@1.8.0.0/src/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.
28def 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.
70def 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<Unit>:
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`
81def 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<Unit>:
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
93def 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
102def 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
110def go.line(+how: L.Line, +as: List<&2, String>) -> IO(Unit):
111 do IO<Unit>:
112 go.say(how, as)
113 go.end(L.status(how))
114
115# the line taken apart
116def 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
119def main() -> IO(Unit):
120 do IO<Unit>:
121 as : List<&2, String> <- Args.all()
122 go(L.spec(), as)