1 # ez/ends: how the commands that are not planners end. `ez check` and 2 # `ez build` start one bend and end on its answer; `ez test` and `ez prove` 3 # start one per file and end on the count. Each ends with a `P.Outcome`, the 4 # same end a planner's plan has, and the interpreter exits with its 5 # `P.status` (`Run.end`), so the status each exits with is a law (EZ-OUT-1). 6 # That the answer is what bend printed is EZ-TRUST-2. `ez run` reads no 7 # answer: it exits with the program's own status (`code` in ez/start.bend). 8 import Base 9 # snap's answer reading, from the file its main.bend re-exports it from: 10 # main.bend itself reaches the foreign effects that run programs, which a 11 # law may not import (bend 2.0.32). `R.ok` and `R.text` are the same defs 12 # (snap's SNAP-TRUST-5, trusted as EZ-TRUST-9). 13 import 0xead4c7939c9eaca8d47da53b4496b731/src/answer.bend as R 14 import ../lock/plan.bend as P 15 16 # a command that ends on whether it passed: success, or a refusal whose 17 # reason was already printed 18 def outcome.of(passed: Bool) -> P.Outcome: 19 Bool.pick(P.Outcome, passed, P.Success{}, P.Refused{""}) 20 21 # `ez lock --package NAME` without `--upgrade` is a line ez refuses before it 22 # reads anything, since `--package` only filters an upgrade 23 def lock.flags(up: Bool, +name: String) -> P.Outcome: 24 Bool.pick(P.Outcome, Bool.or(up, String.is_empty(name)), P.Success{}, 25 P.Refused{"ez: --package needs --upgrade"}) 26 27 # `ez build`: bend exited 0 28 def ran(+out: String) -> P.Outcome: 29 outcome.of(R.ok(out)) 30 31 # `bend f -o out` checks without running main. A clean file prints nothing at 32 # all, and a library entry, having no main, checks clean and then exits 1 with 33 # "no main to run" from the emit. A check failure throws before the emit, so 34 # that message can only follow a check that passed. 35 # 36 # It is matched as a whole line, because that is the shape of the thing being 37 # claimed: bend prints it as `Error: no main to run` and nothing else. Asked as 38 # a substring of the whole output, the same words appearing anywhere (inside 39 # some other error's text, or in a source line bend echoed back while 40 # reporting on it) would answer yes and mean nothing, and a failing check 41 # would read as a passing one. Two other line-shaped facts in this tree were 42 # being asked of whole files the same way, and one of them silently dropped a 43 # test before it was caught. 44 def no.main(+line: String) -> Bool: 45 String.eq(line, "Error: no main to run") 46 47 # whether any line of the output is that one. The rest of the walk is bound 48 # before the `Bool.or` rather than written under it: `Bool.or` is an ordinary 49 # function and reduces both sides, so a recursive call sitting inside one is 50 # not a branch the condition can decline. Bound first it is a plain fold over 51 # a handful of lines, which is what the rest of this tree does too. 52 def said.no.main(ls: List<&2, String>) -> Bool: 53 match ls: 54 case Nil{}: 55 False{} 56 case Con{+h, t}: 57 +rest = said.no.main(t) 58 Bool.or(no.main(h), rest) 59 60 # whether `ez check` passed: bend exited 0, or said only that there is no main 61 def check.passed(+out: String) -> Bool: 62 Bool.or(R.ok(out), said.no.main(String.lines(R.text(out)))) 63 64 # `ez check` 65 def checked(+out: String) -> P.Outcome: 66 outcome.of(check.passed(out)) 67 68 # `ez test` and `ez prove`: every file passed 69 def counted(+passed: Nat, +total: Nat) -> P.Outcome: 70 outcome.of(Nat.is_eq(passed, total))