src/tool/world.bend source
src/tool/world.bend on the hub · documented module
# tool/world: what `ez tool run`, `install` and `upgrade` read. The act and# the target they were given; the directory they run in (`here`), which a# relative cache root is taken from; the variables that place the cache and# the link (`EZ_TOOL_BIN`, `XDG_BIN_HOME`, `XDG_CACHE_HOME`, `HOME`, `PATH`);# the bend on the PATH, as its banner's first line; the file `--entry` names,# "" when it names none; the project's ledger and lock, None when there is# none; and the answers to the questions the planner (tool/plan.bend)# asked. `ez tool sync` reads only the ledger and the lock.## A question is a file's text, whether a binary is there, what git says of a# local path, or one of the upgrade's (lock/up.bend): `Head` for the commit a# remote's HEAD names, and `Clone` for the checkout at a rev with every# file's text. The upgrade's are answered by the lock's interpreter and kept# apart, in `ups`, so its lookups serve here as they are.## Everything else here is a projection: what a World says, read one way.import Baseimport ../lock/up.bend as Upimport ../ledger/manifest.bend as Mimport ../pkg/path.bend as P# what the command does with the binary once it is current: `Run` starts it# with these words, `Install` and `Upgrade` link it onto PATHtype Act is Data: Run{args: List<&2, String>} Install{} Upgrade{}# the variables the command reads, each "" when it is unsettype Env is Data: Env{tool_bin: String, xdg_bin: String, xdg_cache: String, home: String, path: String}# a question the planner asks that is not git's about a remotetype Ask is Data: Read{path: String} # a file's text, None when there is none Exec{path: String} # whether a file is there and executable Locate{path: String} # what git says of a local path# the answer to one: a file's text, whether a binary is there, or what git# said of a path: its real path, the top of the checkout holding it, the# commit HEAD names, and whether `git status` answered and what it printed.# A path that has no real path, or a question IO could not answer, is a miss.type Answer is Data: Text{got: Maybe<&2, String>} Is{yes: Bool} Located{abs: String, top: String, head: String, ok: Bool, status: String} Miss{why: String}# one question, answeredtype Reply is Data: Reply{ask: Ask, answer: Answer}# everything `ez tool run`, `install` and `upgrade` readtype World is Data: World{act: Act, target: String, want: String, here: String, env: Env, bend: String, ledger: Maybe<&2, String>, lock: Maybe<&2, String>, replies: List<&2, Reply>, ups: List<&2, Up.Reply>}# the actdef act(world: World) -> Act: World{a, _t, _want, _here, _env, _bend, _ledger, _lock, _rs, _ups} = world a# the target asked fordef target(world: World) -> String: World{_a, t, _want, _here, _env, _bend, _ledger, _lock, _rs, _ups} = world t# the file `--entry` names, "" when it names nonedef want(world: World) -> String: World{_a, _t, w, _here, _env, _bend, _ledger, _lock, _rs, _ups} = world w# the directory ez runs indef here(world: World) -> String: World{_a, _t, _want, h, _env, _bend, _ledger, _lock, _rs, _ups} = world h# the variablesdef env(world: World) -> Env: World{_a, _t, _want, _here, e, _bend, _ledger, _lock, _rs, _ups} = world e# the bend on the PATH, "" when it did not answerdef bend(world: World) -> String: World{_a, _t, _want, _here, _env, b, _ledger, _lock, _rs, _ups} = world b# the project's ez.toml, None when there is nonedef ledger(world: World) -> Maybe<&2, String>: World{_a, _t, _want, _here, _env, _bend, l, _lock, _rs, _ups} = world l# the project's ez.lock.toml, None when there is nonedef lock(world: World) -> Maybe<&2, String>: World{_a, _t, _want, _here, _env, _bend, _ledger, l, _rs, _ups} = world l# every question of ours answered so fardef replies(world: World) -> List<&2, Reply>: World{_a, _t, _want, _here, _env, _bend, _ledger, _lock, rs, _ups} = world rs# every one of the upgrade's questions answered so fardef ups(world: World) -> List<&2, Up.Reply>: World{_a, _t, _want, _here, _env, _bend, _ledger, _lock, _rs, us} = world usdef env.tool_bin(vars: Env) -> String: Env{b, _x, _c, _h, _p} = vars bdef env.xdg_bin(vars: Env) -> String: Env{_b, x, _c, _h, _p} = vars xdef env.xdg_cache(vars: Env) -> String: Env{_b, _x, c, _h, _p} = vars cdef env.home(vars: Env) -> String: Env{_b, _x, _c, h, _p} = vars hdef env.path(vars: Env) -> String: Env{_b, _x, _c, _h, p} = vars p# HOMEdef home(world: World) -> String: env.home(env(world))# ---------------------------------------------------------------------------# where things go# `~/.local/bin`, when there is a HOMEdef bin.home(+home: String) -> Maybe<&2, String>: Bool.pick(Maybe<&2, String>, String.is_empty(home), None{}, Some{home ++ "/.local/bin"})# `$XDG_BIN_HOME` when it is set, otherwise the one abovedef bin.xdg(+xdg: String, +home: String) -> Maybe<&2, String>: Bool.pick(Maybe<&2, String>, String.is_empty(xdg), bin.home(home), Some{xdg})# where an installed command is linked: `$EZ_TOOL_BIN` when it is set,# otherwise `$XDG_BIN_HOME`, otherwise `$HOME/.local/bin`, an empty value# counting as unset; None when all three are empty (EZ-TOOL-1)def bin.dir(+ez: String, +xdg: String, +home: String) -> Maybe<&2, String>: Bool.pick(Maybe<&2, String>, String.is_empty(ez), bin.xdg(xdg, home), Some{ez})# the link directory of a World's variablesdef bin.of(vars: Env) -> Maybe<&2, String>: Env{+b, +x, _c, +h, _p} = vars bin.dir(b, x, h)# the cache root: `$XDG_CACHE_HOME/ez/tool`, otherwise `$HOME/.cache/ez/tool`,# taken from where ez runs when it is relative, so every path the plan names# is absolute; None when neither variable is setdef cache.home(+here: String, +home: String) -> Maybe<&2, String>: Bool.pick(Maybe<&2, String>, String.is_empty(home), None{}, Some{P.anchor(here, home) ++ "/.cache/ez/tool"})def cache.dir(+here: String, +xdg: String, +home: String) -> Maybe<&2, String>: Bool.pick(Maybe<&2, String>, String.is_empty(xdg), cache.home(here, home), Some{P.anchor(here, xdg) ++ "/ez/tool"})# the cache root of a Worlddef cache.of(world: World) -> Maybe<&2, String>: World{_a, _t, _want, +here, vars, _bend, _ledger, _lock, _rs, _ups} = world Env{_b, _x, +c, +h, _p} = vars cache.dir(here, c, h)# a Maybe's text, "" for nonedef text.or(got: Maybe<&2, String>) -> String: match got: case None{}: "" case Some{t}: t# ---------------------------------------------------------------------------# the project# the project's ledger as read. A project with no ez.toml pins nothing, so# every target is a free one; one that does not parse is its own refusal.def project.of(got: Maybe<&2, String>) -> M.Read: match got: case None{}: M.Good{M.Manifest{"", "", "", "", "", "", [], []}} case Some{text}: M.parse(text)# the project's ledger as readdef project(world: World) -> M.Read: project.of(ledger(world))# ---------------------------------------------------------------------------# answers# the key a question is looked up bydef key(ask: Ask) -> String: match ask: case Read{path}: "read\n" ++ path case Exec{path}: "exec\n" ++ path case Locate{path}: "locate\n" ++ path# an answer looked up, or none yettype Heard is Data: Heard{answer: Answer} Open{}# this answer when it is the question's, otherwise the rest'sdef look.step(hit: Bool, answer: Answer, rest: Unit -> Heard) -> Heard: match hit: case True{}: Heard{answer} case False{}: rest(Unit{})# the first answer given to a questiondef look(rs: List<&2, Reply>, +want: String) -> Heard: match rs: case []: Open{} case Reply{ask, answer} <> t: look.step(String.eq(key(ask), want), answer, _u => look(t, want))# the answer to a question, or none yetdef heard(+world: World, ask: Ask) -> Heard: look(replies(world), key(ask))