src/ez/quiet.bend
ezx@1.4.0.0 · 95 lines · 0 definitions
1# ez/quiet: bend's own chatter dropped from a run's output, so a test sees only
2# what its program printed. Since 2.0.32 bend prints no check report in front
3# of a program it runs: a verdict (`ALL PROOFS CHECK` or `SOME PROOFS FAIL`)
4# is printed only for a file with no main, and a run that fails its check
5# fails the test on what it printed, which is kept. The report lines older
6# releases printed in front of every run are no longer dropped.
7#
8# bend also nags about its own updates: `bend 2.0.21 is available: run bend
9# update`, printed whenever a newer release exists and the network can be
10# reached. That line is not the program's output either, and leaving it in
11# fails every test in the tree the day upstream cuts a release. It is worth
12# being precise about how bad that is: the gate went from green to 33 of 52
13# here without a line of ez changing, and it did it the moment a laptop
14# regained its network, so it is invisible offline and arrives looking like a
15# regression someone just introduced. It is dropped.
16import Base
17
18# whether this line is bend telling us a newer bend exists. Anchored at both
19# ends rather than matched loosely: a test that legitimately prints the words
20# `bend update` in prose should still be compared on them.
21def is_nag(+line: String) -> Bool:
22 Bool.and(String.starts_with(line, "bend "),
23 String.ends_with(line, " is available: run bend update"))
24
25# a trailing run of blank lines, being dropped from the right. `live` turns true
26# at the first line that survives, after which nothing is dropped.
27type Tail is Data:
28 Tail{live: Bool, out: List<&2, String>}
29
30# a line kept or dropped
31def put(drop: Bool, line: String, rest: List<&2, String>) -> List<&2, String>:
32 match drop:
33 case True{}:
34 rest
35 case False{}:
36 line <> rest
37
38# the lines with the nag dropped
39def go(ls: List<&2, String>) -> List<&2, String>:
40 match ls:
41 case []:
42 []
43 case +h <> t:
44 put(is_nag(h), h, go(t))
45
46# a blank line: kept once something after it has survived, dropped while the
47# run of trailing blanks is still open
48def tail.blank(live: Bool, out: List<&2, String>) -> Tail:
49 match live:
50 case True{}:
51 Tail{True{}, "" <> out}
52 case False{}:
53 Tail{False{}, out}
54
55# one line offered to the tail. The line is taken apart rather than asked about,
56# because a fold's step may not declare a `+` parameter to duplicate it.
57def tail.line(line: String, live: Bool, out: List<&2, String>) -> Tail:
58 match line:
59 case SNil{}:
60 tail.blank(live, out)
61 case SCon{c, t}:
62 Tail{True{}, SCon{c, t} <> out}
63
64# a fold from the right: until a line survives, a blank one is dropped
65def tail.step(line: String, st: Tail) -> Tail:
66 Tail{live, out} = st
67 tail.line(line, live, out)
68
69# what a finished fold collected
70def tail.out(st: Tail) -> List<&2, String>:
71 Tail{_live, out} = st
72 out
73
74# the fold from the right, written out. Base's List.foldr takes its step as an
75# erased argument, and the termination check cannot read through one.
76def tail.walk(ls: List<&2, String>) -> Tail:
77 match ls:
78 case []:
79 Tail{False{}, []}
80 case h <> t:
81 tail.step(h, tail.walk(t))
82
83# the lines with the blank ones at the end dropped, the way a shell's `$(...)`
84# drops a trailing newline, so a comparison never turns on one
85def chomp.lines(ls: List<&2, String>) -> List<&2, String>:
86 tail.out(tail.walk(ls))
87
88# the text with its trailing blank lines dropped
89def chomp(text: String) -> String:
90 String.join(chomp.lines(String.lines(text)), "\n")
91
92# a run's output as a test's `#|` trailer states it: no update nag, no
93# trailing newline
94def filter(text: String) -> String:
95 String.join(chomp.lines(go(String.lines(text))), "\n")