src/share/spin.bend
ezx@1.5.0.0 · 76 lines · 0 definitions
1# share/spin: the spinner share/say's words are drawn with. This is the half
2# that runs programs: it asks which terminal this process writes to, starts
3# the mark as a child and stops it. It lives apart from the words because a
4# program runs through snap's foreign effect, and bend 2.0.32 fails a proof
5# whose imports reach foreign code, so a law may import share/say but never
6# this.
7import Base
8import snap@1.1.0.0/main.bend as R
9import ezx@1.5.0.0/src/share/say.bend as Say
10
11# one fd of this process, as a path. A failure is not a terminal.
12def link(+fd: String) -> IO(String):
13 do IO<String>:
14 +out : String <- R.exec(["sh", "-c", Say.probe(), "sh", fd])
15 return String.trim(R.text(out))
16
17# the terminal this process writes to, stderr's first, or empty for none
18def dev() -> IO(String):
19 do IO<String>:
20 +err : String <- link("2")
21 +out : String <- link("1")
22 return Say.tty.pick(err, out)
23
24# the mark, when there is a terminal to draw it on
25def start(on: Bool, +dev: String, +msg: String) -> IO(Say.Spin):
26 match on:
27 case False{}:
28 IO.pure(Say.Spin, Say.Spin{"", "", ""})
29 case True{}:
30 do IO<Say.Spin>:
31 +pid : String <- R.start(["sh", "-c", Say.frames(), "sh", dev, msg])
32 return Say.Spin{dev, String.trim(pid), msg}
33
34def hold.at(plain: Bool, +dev: String, +msg: String) -> IO(Say.Spin):
35 match plain:
36 case True{}:
37 do IO<Say.Spin>:
38 IO.print_err(msg)
39 return Say.Spin{"", "", ""}
40 case False{}:
41 start(True{}, dev, msg)
42
43# the line, then the mark when a terminal will show it moving. A pipe gets
44# the line and nothing else.
45def hold(+msg: String) -> IO(Say.Spin):
46 do IO<Say.Spin>:
47 +tty : String <- dev()
48 hold.at(String.is_empty(tty), tty, msg)
49
50# a spinner that never started still owes its line, unless the plain path
51# already printed it and left the message empty
52def owed(empty: Bool, +msg: String) -> IO(Unit):
53 match empty:
54 case True{}:
55 IO.pure(Unit, Unit{})
56 case False{}:
57 IO.print_err(msg)
58
59# the mark stopped, and the line written where it was
60def done.live(+dev: String, +pid: String, +msg: String) -> IO(Unit):
61 do IO<Unit>:
62 _k : String <- R.exec(["kill", pid])
63 _s : String <- R.exec(["sh", "-c", Say.settle(), "sh", dev, msg])
64 return Unit{}
65
66def done.bad(bad: Bool, +dev: String, +pid: String, +msg: String) -> IO(Unit):
67 match bad:
68 case True{}:
69 owed(String.is_empty(msg), msg)
70 case False{}:
71 done.live(dev, pid, msg)
72
73# the step finished: the mark stopped and the line left, or the line owed
74def done(spin: Say.Spin) -> IO(Unit):
75 Say.Spin{+term, +pid, +msg} = spin
76 done.bad(Bool.or(String.is_empty(pid), String.eq(pid, "0")), term, pid, msg)