daemon/live.bend

cswap-bend-cli@0.1.0.0 · 134 lines · 23 definitions

1import Base
2import cswap-bend-cli@0.1.0.0/json.bend as Json
3import cswap-bend-cli@0.1.0.0/fs.bend as Fs
4import cswap-bend-cli@0.1.0.0/setload.bend as Setload
5import cswap-bend-cli@0.1.0.0/profile.bend as P
6import cswap-bend-cli@0.1.0.0/daemon/config.bend as Cfg
7
8# The settings in force for one tick of `cswap auto`: settings.json, with the
9# profile in force laid over its `autoswitch` block, with the command-line
10# flags over both. The daemon reads settings.json again every tick, so
11# `cswap profile use NAME` takes effect without a restart.
12#
13# The profile in force is `--profile NAME` if given, else `active_profile`;
14# no profile, or a name that matches nothing, leaves the settings as they are.
15
16# The profile and strategy a tick decided under, for the events and the log.
17type Tag is Data:
18  Tag{profile: String, strategy: String}
19
20# What a run holds besides the settings it reads each tick.
21type Live is Data:
22  Live{flags: Cfg.Flags, over: Maybe<&2, String>, tag: Maybe<&2, Tag>}
23
24def raw_of(+text: String) -> Json.Json:
25  Setload.raw.parsed(Json.parse(text))
26
27def laid(+c: Cfg.Cfg, +res: P.Res) -> Cfg.Cfg:
28  match res:
29    case P.ROk{p}:
30      P.with_prof(c, p)
31    case o:
32      c
33
34def cfg_res(+text: String, +fl: Cfg.Flags, +res: P.Res) -> Cfg.Cfg:
35  Cfg.merge(laid(Cfg.from_text(text), res), fl)
36
37def tag_of(+res: P.Res, +c: Cfg.Cfg) -> Maybe<&2, Tag>:
38  match res:
39    case P.ROk{p}:
40      Some{Tag{P.name_of(p), Cfg.cfg_strategy(c)}}
41    case o:
42      None{}
43
44# The effective settings of a tick.
45def cfg_for(+text: String, +fl: Cfg.Flags, +over: Maybe<&2, String>) -> Cfg.Cfg:
46  cfg_res(text, fl, P.resolve(raw_of(text), over))
47
48def tag_for(+text: String, +fl: Cfg.Flags, +over: Maybe<&2, String>) -> Maybe<&2, Tag>:
49  tag_of(P.resolve(raw_of(text), over), cfg_for(text, fl, over))
50
51# A name given with `--profile` that matches no profile.
52def unknown2(+res: P.Res, +over: Maybe<&2, String>) -> Maybe<&2, String>:
53  match res over:
54    case P.RUnknown{n} Some{o}:
55      Some{n}
56    case _ _:
57      None{}
58
59def unknown(+text: String, +over: Maybe<&2, String>) -> Maybe<&2, String>:
60  unknown2(P.resolve(raw_of(text), over), over)
61
62def live_for(+text: String, +fl: Cfg.Flags, +over: Maybe<&2, String>) -> Live:
63  Live{fl, over, tag_for(text, fl, over)}
64
65def live_flags(+l: Live) -> Cfg.Flags:
66  match l:
67    case Live{fl, over, tag}:
68      fl
69
70def live_over(+l: Live) -> Maybe<&2, String>:
71  match l:
72    case Live{fl, over, tag}:
73      over
74
75def live_tag(+l: Live) -> Maybe<&2, Tag>:
76  match l:
77    case Live{fl, over, tag}:
78      tag
79
80def text_of(r: Fs.Res(String)) -> String:
81  match r:
82    case Fail{e}:
83      ""
84    case Done{t}:
85      t
86
87# settings.json as text; "" when it cannot be read.
88def read_text(+root: String) -> IO(String):
89  IO.bind(Fs.Res(String), String, Fs.read_file(root ++ "/settings.json"), r => IO.pure(String, text_of(r)))
90
91# ---- events
92
93def kind_is(+k: Maybe<&2, Json.Json>, +name: String) -> Bool:
94  match k:
95    case Some{Json.JStr{s}}:
96      String.eq(s, name)
97    case _:
98      False{}
99
100def decision_event(+e: Json.Json) -> Bool:
101  kind_is(Json.get("event", e), "switch") || kind_is(Json.get("event", e), "no-switch") || kind_is(Json.get("event", e), "all-exhausted")
102
103def tag_one2(+decision: Bool, +t: Tag, +e: Json.Json) -> Json.Json:
104  match decision t:
105    case True{} Tag{p, s}:
106      Json.set("strategy", Json.JStr{s}, Json.set("profile", Json.JStr{p}, e))
107    case False{} _:
108      e
109
110# A decision event with the profile and strategy it was made under added at the end.
111def tag_one(+t: Tag, +e: Json.Json) -> Json.Json:
112  tag_one2(decision_event(e), t, e)
113
114def tag_events2(+t: Tag, +es: List<&2, Json.Json>) -> List<&2, Json.Json>:
115  match es:
116    case Nil{}:
117      []
118    case Con{e, rest}:
119      tag_one(t, e) <> tag_events2(t, rest)
120
121def tag_events(+m: Maybe<&2, Tag>, +es: List<&2, Json.Json>) -> List<&2, Json.Json>:
122  match m:
123    case Some{t}:
124      tag_events2(t, es)
125    case None{}:
126      es
127
128# The log line with the profile and strategy added, when a profile is in force.
129def tag_log(+m: Maybe<&2, Tag>, +j: Json.Json) -> Json.Json:
130  match m:
131    case Some{Tag{p, s}}:
132      Json.set("strategy", Json.JStr{s}, Json.set("profile", Json.JStr{p}, j))
133    case None{}:
134      j