src/share/spin.bend

ezx@1.8.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.3.0.0/main.bend as R
9import ezx@1.8.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)