~/bend-docscommunity

src/ez/line.bend source

src/ez/line.bend on the hub · documented module

# ez/line: the command line, taken apart before any command runs. `spec` is# the program, `command` names what a matched path runs, and `line` decides# how a parse ends: a command runs, help is shown, or the line is misused.# All of it is pure, so the status a line exits with before any command runs# is a law (EZ-OUT-1); ez/main.bend prints and exits as `line` and `status`# say, and runs the command a line names, whose own end decides the rest.import Baseimport 0xcab8a7a189cec2b51e8db0484f69c593/main.bend as Shake# a subcommandtype Sub is Data:  CInit{}  CAdd{}  CRemove{}  CLock{}  CFetch{}  CCheck{}  CBuild{}  CRun{}  CPublish{}  CTest{}  CProve{}  CDoctor{}  CToolSync{}  CToolRun{}  CToolInstall{}  CToolUpgrade{}  CHelp{}# the program: every command, and the flags and positionals it bindsdef spec() -> Shake.Cli:  Shake.app("ez", "dependency tracking and builds for Bend 2", None{},    [],    [      Shake.sub("init", "scaffold a project: ez.toml, ignore, entry, src/",        [          Shake.pos("name", "package name", False{}, None{}, []),          Shake.pos("entry", "entry file", False{}, None{}, []),          Shake.opt("description", None{}, Some{"description"},            "one line: the entry's first line, which the hub shows", False{}, None{}, [])        ], []),      Shake.sub("add", "record a git or hub package",        [          Shake.pos("target", "owner/repo, git url, path, or name@version", True{}, None{},            []),          Shake.pos("ref", "commit, tag or branch", False{}, None{}, []),          Shake.pos("entry", "entry file", False{}, None{}, []),          Shake.opt("rename", None{}, Some{"rename"},            "record it under this name, ahead of its own (a TOML bare key)",            False{}, None{}, [])        ], []),      Shake.sub("remove", "drop a package from the ledger",        [          Shake.pos("name", "package name", True{}, None{}, [])        ], []),      Shake.sub("lock", "resolve every import, write ez.lock.toml",        [          Shake.flag("upgrade", None{}, Some{"upgrade"},            "re-pin git deps and tools that moved, then write the lock"),          Shake.opt("package", None{}, Some{"package"},            "with --upgrade, only this dependency or tool", False{}, None{}, [])        ], []),      Shake.sub("fetch", "fill BEND_LIB from the lock", [], []),      Shake.sub("check", "check the entry, without running it", [], []),      Shake.sub("build", "build the entry to a native binary",        [          Shake.pos("out", "output path", False{}, None{}, [])        ], []),      Shake.sub("run", "check and run the entry", [], []),      Shake.sub("tool", "build or run another project's binary", [], [        Shake.sub("sync", "build and link every pinned tool at its lock rev",          [], []),        Shake.sub("run", "fetch, build and run the binary a repo declares",          [            Shake.pos("target", "owner/repo, git url, path, or name@version", True{}, None{},              []),            Shake.opt("entry", None{}, Some{"entry"},              "build this file of the checkout, not the one it names", False{}, None{}, [])          ], []),        Shake.sub("install", "build the binary and link it on PATH",          [            Shake.pos("target", "owner/repo, git url, path, or name@version", True{}, None{},              []),            Shake.opt("entry", None{}, Some{"entry"},              "build this file of the checkout, not the one it names", False{}, None{}, [])          ], []),        Shake.sub("upgrade", "rebuild when the commit moved, and refresh the link",          [            Shake.pos("target", "owner/repo, git url, path, or name@version", True{}, None{},              []),            Shake.opt("entry", None{}, Some{"entry"},              "build this file of the checkout, not the one it names", False{}, None{}, [])          ], [])      ]),      Shake.sub("publish", "send the entry to the hub, under ez's 0x name",        [], []),      Shake.sub("test", "run every */tests/*.bend against its trailer", [], []),      Shake.sub("prove", "check every PROOF.bend: the proof gate",        [], []),      Shake.sub("doctor", "report on the toolchain and the project", [], [])    ])# a subcommand by its path, the words joined with `/`. A path that names no# command, the empty one of a bare `ez` or a group such as `tool`, is `CHelp`.def command(+name: String) -> Sub:  Bool.pick(Sub, String.eq(name, "init"), CInit{},    Bool.pick(Sub, String.eq(name, "add"), CAdd{},      Bool.pick(Sub, String.eq(name, "remove"), CRemove{},        Bool.pick(Sub, String.eq(name, "lock"), CLock{},          Bool.pick(Sub, String.eq(name, "fetch"), CFetch{},            Bool.pick(Sub, String.eq(name, "check"), CCheck{},              Bool.pick(Sub, String.eq(name, "build"), CBuild{},                Bool.pick(Sub, String.eq(name, "run"), CRun{},                  Bool.pick(Sub, String.eq(name, "publish"), CPublish{},                    Bool.pick(Sub, String.eq(name, "test"), CTest{},                      Bool.pick(Sub, String.eq(name, "prove"), CProve{},                        Bool.pick(Sub, String.eq(name, "doctor"), CDoctor{},                          Bool.pick(Sub, String.eq(name, "tool/sync"), CToolSync{},                            Bool.pick(Sub, String.eq(name, "tool/run"), CToolRun{},                              Bool.pick(Sub, String.eq(name, "tool/install"),                                CToolInstall{},                                Bool.pick(Sub, String.eq(name, "tool/upgrade"),                                  CToolUpgrade{},                                  CHelp{}))))))))))))))))# whether a subcommand is the one that names no commanddef is.help(sub: Sub) -> Bool:  match sub:    case CHelp{}:      True{}    case _:      False{}# the path a match names, its words joined with `/`def path(matched: Shake.Matched) -> String:  String.join(Shake.path_of(matched), "/")# the Matched of the command the path names, whose bindings are the ones the# line made for that command (SHAKE-GET-2: `at(m, path_of(m))` follows the# selected path). A None here would be a Matched `parse` never answers.def leaf(+matched: Shake.Matched) -> Shake.Matched:  Maybe.default(&2, Shake.Matched, Shake.at(matched, Shake.path_of(matched)), matched)# the value the command bound to `name`, or "" when it bound nonedef get(found: Shake.Matched, +name: String) -> String:  Maybe.default(&2, String, Shake.get(found, name), "")# ---------------------------------------------------------------------------# how a line ends# a command runs, with what the line bound for it; help is shown, on stdout;# or the line is misused, and says so on stderrtype Line is Data:  Runs{sub: Sub, matched: Shake.Matched}  Shows{text: String}  Misuse{text: String}# a line Shake would not parse: a request for help, whose path Shake has# already checked word by word (SHAKE-PARSE-8: `help bogus` fails as# `Unexpected`, not as a request for help), or a usage errordef line.err(help: Maybe<&2, List<&2, String>>, +app: Shake.Cli, err: Shake.ParseErr) -> Line:  match help:    case Some{path}:      Shows{Shake.help(app, path)}    case None{}:      Misuse{Shake.err_text(app, err)}# a path that names no command: a bare `ez` is help, as a bare `cargo` is,# and a bare group such as `ez tool` is a line that did not say which of its# commands to run, as `cargo report` with no command is, so its help goes to# stderr and the line failsdef line.group(bare: Bool, +app: Shake.Cli, +matched: Shake.Matched) -> Line:  match bare:    case True{}:      Shows{Shake.help(app, [])}    case False{}:      Misuse{Shake.help(app, Shake.path_of(matched))}# a line Shake parsed: the command its path names runs, or it names nonedef line.run(help: Bool, sub: Sub, +app: Shake.Cli, +matched: Shake.Matched) -> Line:  match help:    case True{}:      line.group(String.is_empty(path(matched)), app, matched)    case False{}:      Runs{sub, leaf(matched)}# how a line ends, from what Shake made of itdef line(+app: Shake.Cli, parsed: Result<&2, &2, Shake.ParseErr, Shake.Matched>) -> Line:  match parsed:    case Fail{+e}:      line.err(Shake.help_path(e), app, e)    case Done{+m}:      +sub = command(path(m))      line.run(is.help(sub), sub, app, m)# ---------------------------------------------------------------------------# the status# the status a line exits with, when it is decided here, or the command's# own end (EZ-OUT-1)type Status is Data:  Exits{code: U32}  Ends{}# help exits 0, a misused line 1, and a command that runs ends as it endsdef status(how: Line) -> Status:  match how:    case Runs{_sub, _m}:      Ends{}    case Shows{_text}:      Exits{0}    case Misuse{_text}:      Exits{1}