~/bend-docscommunity

src/share/say.bend source

src/share/say.bend on the hub · documented module

# share/say: the words a long step says, and a spinner for the wait.## A clone, a hub fetch or a native build can sit there for half a minute, and# a step that prints nothing looks stopped. The line is printed before the# work. On a terminal it is drawn again in place, with a mark that moves,# until the step finishes; the mark is a child, because the work itself is# one program waited on to the end and nothing can be printed while it runs.# That child is started with its own session and its standard streams closed,# so it opens the terminal by the path this process's own stderr or stdout# already has. A pipe is not a terminal: the line is printed once and left,# which is what a log wants.## The words and the scripts the mark runs are here; what starts and stops the# mark is share/spin, which runs programs and so reaches foreign code a law# may not import.import Baseimport ../pkg/path.bend as P# a spinner that is running, or none. An empty pid means the line was already# printed and nothing has to be stopped. `msg` is what the terminal is owed# when the mark is cleared.type Spin is Data:  Spin{dev: String, pid: String, msg: String}# the first twelve characters of a commit, which is enough to tell one from# the next. A shorter name is already short.def brief.go(short: Bool, +rev: String) -> String:  match short:    case True{}:      rev    case False{}:      String.take(rev, 12n)# a commit as a line shows itdef brief(+rev: String) -> String:  brief.go(Nat.is_le(String.length(rev), 12n), rev)# a package's name when its entry has one, otherwise the hash. `main` is kept:# it is the name the file has.def label.go(empty: Bool, +hash: String, +name: String) -> String:  match empty:    case True{}:      hash    case False{}:      name# what a line calls a packagedef label(+hash: String, +entry: String) -> String:  +name = P.unbend(P.base(entry))  label.go(String.is_empty(name), hash, name)# `dep sha256: git fetch (https://github.com/…)`def dep.git(+name: String, +url: String) -> String:  "dep " ++ name ++ ": git fetch (" ++ url ++ ")"# a package taken from the hubdef dep.hub(+name: String) -> String:  "dep " ++ name ++ ": hub fetch"# a binary already built for this commitdef reused(+name: String) -> String:  name ++ ": cached"# the lock resolving every dependencydef resolving() -> String:  "resolving deps"def resolving.at(+name: String) -> String:  "resolving " ++ namedef git.fetch(+url: String) -> String:  "git fetch (" ++ url ++ ")"# a binary being builtdef building(+name: String) -> String:  "building " ++ name# a binary being linked into placedef linking(+at: String) -> String:  "linking " ++ at# a binary built and linkeddef installed(+name: String, +at: String) -> String:  "installed " ++ name ++ " -> " ++ at# a binary linked, with nothing to builddef linked(+name: String, +at: String) -> String:  "linked " ++ name ++ " -> " ++ at# a commit's tree being weighed for its NAR hashdef hashing(+rev: String) -> String:  "hashing " ++ brief(rev)# a device path that is a terminal. A pipe and `/dev/null` are not.def tty.at(+path: String) -> Bool:  Bool.or(String.starts_with(path, "/dev/pts/"),    Bool.or(String.eq(path, "/dev/console"), String.starts_with(path, "/dev/tty")))def tty.out(on: Bool, +out: String) -> String:  match on:    case True{}:      out    case False{}:      ""# stderr's terminal when it is one, otherwise stdout's, otherwise nonedef tty.pick.go(on: Bool, +err: String, +out: String) -> String:  match on:    case True{}:      err    case False{}:      tty.out(tty.at(out), out)def tty.pick(+err: String, +out: String) -> String:  tty.pick.go(tty.at(err), err, out)# the hub answered 404 for a manifest. A missing file is a different miss:# its path is not `/manifest`.def hub.miss(+why: String) -> Bool:  Bool.and(String.contains(why, "/manifest:"),    String.ends_with(why, "the server answered 404"))def hub.explain.go(miss: Bool, +hash: String, +url: String, +why: String) -> String:  match miss:    case True{}:      "ez: " ++ hash ++ ": the hub has no manifest for this package (" ++ url ++        " answered 404). Re-lock with `ez lock`, fix the dependency, or vendor it offline (`ez add`)."    case False{}:      "ez: " ++ whydef hub.explain(+hash: String, +url: String, +why: String) -> String:  hub.explain.go(hub.miss(why), hash, url, why)# the commit the words name. A tag is what a person pinned; a bare rev is not.def drift.where(bare: Bool, +tag: String, +rev: String) -> String:  match bare:    case True{}:      "commit " ++ rev    case False{}:      "tagged commit " ++ tag ++ " (" ++ rev ++ ")"# the tagged commit's tree no longer hashes to the pin. Upgrade will not# invent a new hash for that: the pin is what the ledger says, and a drift# is the ledger and the tree disagreeing.def drift(+name: String, +tag: String, +rev: String, +old: String, +new: String) -> String:  "ez: " ++ name ++ ": the tree hash of " ++    drift.where(String.is_empty(tag), tag, rev) ++    " no longer matches the pin (the package moved, or the hash is wrong: " ++    old ++ " on the pin, " ++ new ++    " in the tree). `ez lock --upgrade` cannot fix a drifted pin. Update the dependency hash, re-add the package, or check the tag."# a drift judged before the tree was weighed, so there are not two hashes to showdef stuck(+name: String) -> String:  "ez: " ++ name ++    ": the pin drifted. `ez lock --upgrade` cannot fix a drifted pin. Update the dependency hash, re-add the package, or check the tag."# a `[tools.` section in the ledger text. The parser is not asked: a ledger# that does not parse can still have named the command.def has.tools(+text: String) -> Bool:  String.contains(text, "[tools.")# `ez sync` is not a command. A ledger that pins tools already has the one# that builds them; a ledger that does not is pointed at lock and install.def hint(tools: Bool) -> String:  match tools:    case True{}:      "hint: `sync` is not a command; use `ez tool sync`"    case False{}:      "hint: `sync` is not a command. Use `ez lock` to resolve dependencies, or `ez tool install <target>` to link a binary."# the parent's fd, by number. The child cannot look at its own: that one was# replaced with a pipe before this ran. `$1` is the number.def probe() -> String:  "ppid=; while read -r k v; do case $k in PPid:) ppid=$v;; esac; done < /proc/self/status; readlink /proc/$ppid/fd/$1 2>/dev/null"# a mark on the terminal while the parent is alive. `$1` is the terminal,# `$2` the line. The mark walks ⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏. An empty parent# id exits rather than spinning on `/proc`.def frames() -> String:  "ppid=; while read -r k v; do case $k in PPid:) ppid=$v;; esac; done < /proc/self/status; [ -n \"$ppid\" ] || exit 0; while [ -d /proc/$ppid ]; do printf '\\r%s %s' '⠋' \"$2\" >\"$1\"; sleep 0.08; [ -d /proc/$ppid ] || break; printf '\\r%s %s' '⠙' \"$2\" >\"$1\"; sleep 0.08; [ -d /proc/$ppid ] || break; printf '\\r%s %s' '⠹' \"$2\" >\"$1\"; sleep 0.08; [ -d /proc/$ppid ] || break; printf '\\r%s %s' '⠸' \"$2\" >\"$1\"; sleep 0.08; [ -d /proc/$ppid ] || break; printf '\\r%s %s' '⠼' \"$2\" >\"$1\"; sleep 0.08; [ -d /proc/$ppid ] || break; printf '\\r%s %s' '⠴' \"$2\" >\"$1\"; sleep 0.08; [ -d /proc/$ppid ] || break; printf '\\r%s %s' '⠦' \"$2\" >\"$1\"; sleep 0.08; [ -d /proc/$ppid ] || break; printf '\\r%s %s' '⠧' \"$2\" >\"$1\"; sleep 0.08; [ -d /proc/$ppid ] || break; printf '\\r%s %s' '⠇' \"$2\" >\"$1\"; sleep 0.08; [ -d /proc/$ppid ] || break; printf '\\r%s %s' '⠏' \"$2\" >\"$1\"; sleep 0.08; done"# the mark wiped, and the line left in its placedef settle() -> String:  "printf '\\r\\033[K%s\\n' \"$2\" >\"$1\""