src/ez/main.bend

ezx@1.9.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.9.0.0/src/share/args.bend as Args
13import ezx@1.9.0.0/src/ez/cmd.bend as Cmd
14import ezx@1.9.0.0/src/ez/test.bend as Test
15import ezx@1.9.0.0/src/ez/prove.bend as Prove
16import ezx@1.9.0.0/src/doctor/run.bend as DoctorRun
17import ezx@1.9.0.0/src/tool/run.bend as ToolRun
18import ezx@1.9.0.0/src/tool/world.bend as TW
19import ezx@1.9.0.0/src/share/say.bend as Say
20import ezx@1.9.0.0/src/io/file.bend as F
21import ezx@1.9.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)