profcmd.bend
cswap-bend-cli@0.1.0.0 · 349 lines · 55 definitions
1import Base
2import cswap-bend-cli@0.1.0.0/json.bend as Json
3import cswap-bend-cli@0.1.0.0/act.bend as Act
4import cswap-bend-cli@0.1.0.0/cmd.bend as Cmd
5import cswap-bend-cli@0.1.0.0/fs.bend as Fs
6import cswap-bend-cli@0.1.0.0/setload.bend as Setload
7import cswap-bend-cli@0.1.0.0/setwrite.bend as Setwrite
8import cswap-bend-cli@0.1.0.0/setcmd.bend as Setcmd
9import cswap-bend-cli@0.1.0.0/profile.bend as P
10import cswap-bend-cli@0.1.0.0/policy/safe.bend as SF
11import cswap-bend-cli@0.1.0.0/daemon/config.bend as Cfg
12import cswap-bend-cli@0.1.0.0/profview.bend as PV
13
14
15
16
17
18def ActM(-A: Type) -> Type:
19 Act.of(A)
20
21def ActM.pure(-A: Type, x: A) -> Act.of(A):
22 Act.pure(A, x)
23
24def ActM.bind(-A: Type, -B: Type, m: Act.of(A), f: A -> Act.of(B)) -> Act.of(B):
25 Act.bind(A, B, m, f)
26
27
28
29
30
31type Plan2 is Data:
32 Answer{out: Cmd.Out}
33 Wr{raw: Json.Json, out: Cmd.Out}
34
35def doc(pairs: List<(String & Json.Json)>) -> Cmd.Out:
36 Cmd.doc(Json.of_pairs(Con{("schemaVersion", Json.JNum{"1"}), pairs}))
37
38def err(+json: Bool, +msg: String) -> Cmd.Out:
39 Cmd.failed(json, "ConfigError", msg)
40
41def unknown_msg(+name: String) -> String:
42 "unknown profile '" ++ name ++ "'; `cswap profile list` shows the profiles"
43
44def active_json(+m: Maybe<&2, String>) -> Maybe<&2, Json.Json>:
45 match m:
46 case Some{s}:
47 Some{Json.JStr{s}}
48 case None{}:
49 None{}
50
51def list_doc(+raw: Json.Json) -> Cmd.Out:
52 doc([("active", PV.opt_json(active_json(P.active(raw)))), ("profiles", Json.arr(PV.prof_jsons(raw, Cfg.from_settings(raw), P.names(raw))))])
53
54def list_out(+json: Bool, +raw: Json.Json) -> Cmd.Out:
55 match json:
56 case True{}:
57 list_doc(raw)
58 case False{}:
59 Cmd.ok(PV.table(PV.rows_of(raw, Cfg.from_settings(raw), P.names(raw))))
60
61def yes_no(+b: Bool) -> String:
62 match b:
63 case True{}:
64 "yes"
65 case False{}:
66 "no"
67
68def show_text2(+r: PV.Row) -> String:
69 match r:
70 case PV.Row{m, n, s, t, ms, c, i, src}:
71 "name: " ++ n ++ "\nstrategy: " ++ s ++ "\nthreshold: " ++ t ++ "\nmodels: " ++ ms ++ "\ncooldown_s: " ++ c ++ "\ninterval_s: " ++ i ++ "\nsource: " ++ src ++ "\nactive: " ++ yes_no(String.eq(m, "*"))
72
73def show_text(+raw: Json.Json, +cfg: Cfg.Cfg, +p: P.Prof) -> String:
74 show_text2(PV.row_of(raw, cfg, p))
75
76def show_out(+json: Bool, +raw: Json.Json, +p: P.Prof) -> Cmd.Out:
77 match json:
78 case True{}:
79 doc([("profile", PV.prof_json(raw, Cfg.from_settings(raw), p))])
80 case False{}:
81 Cmd.ok(show_text(raw, Cfg.from_settings(raw), p))
82
83def show_found(+json: Bool, +raw: Json.Json, +name: String, +m: Maybe<&2, P.Prof>) -> Cmd.Out:
84 match m:
85 case None{}:
86 err(json, unknown_msg(name))
87 case Some{p}:
88 show_out(json, raw, p)
89
90def show_name(+raw: Json.Json, +n: Maybe<&2, String>) -> String:
91 match n:
92 case Some{x}:
93 x
94 case None{}:
95 Maybe.default(&2, String, P.active(raw), "default")
96
97def show_out2(+json: Bool, +raw: Json.Json, +name: String) -> Cmd.Out:
98 show_found(json, raw, name, P.find(raw, name))
99
100def run.list(+root: String, +json: Bool) -> IO(Cmd.Out):
101 IO.bind(Json.Json, Cmd.Out, Setload.load_raw(root), raw => IO.pure(Cmd.Out, list_out(json, raw)))
102
103def show_run(+json: Bool, +n: Maybe<&2, String>, +raw: Json.Json) -> IO(Cmd.Out):
104 IO.pure(Cmd.Out, show_out2(json, raw, show_name(raw, n)))
105
106def run.show(+root: String, +json: Bool, +n: Maybe<&2, String>) -> IO(Cmd.Out):
107 IO.bind(Json.Json, Cmd.Out, Setload.load_raw(root), raw => show_run(json, n, raw))
108
109
110
111def use_ok(+json: Bool, +raw: Json.Json, +name: String, +p: P.Prof) -> Cmd.Out:
112 match json:
113 case True{}:
114 doc([("activeProfile", Json.JStr{name}), ("profile", PV.prof_json(raw, Cfg.from_settings(raw), p))])
115 case False{}:
116 Cmd.ok("active profile: " ++ name ++ " (" ++ SF.name(P.algo_base(p)) ++ ")")
117
118def use_plan2(+m: Maybe<&2, P.Prof>, +json: Bool, +raw: Json.Json, +name: String) -> Plan2:
119 match m:
120 case None{}:
121 Answer{err(json, unknown_msg(name))}
122 case Some{p}:
123 Wr{P.use(raw, name), use_ok(json, P.use(raw, name), name, p)}
124
125def use_plan(+json: Bool, +raw: Json.Json, +name: String) -> Plan2:
126 use_plan2(P.find(raw, name), json, raw, name)
127
128
129
130def set_ok(+json: Bool, +raw: Json.Json, +name: String, +key: String, +value: String, +p: P.Prof) -> Cmd.Out:
131 match json:
132 case True{}:
133 doc([("profile", PV.prof_json(raw, Cfg.from_settings(raw), p))])
134 case False{}:
135 Cmd.ok("profile " ++ name ++ ": " ++ key ++ " = " ++ value)
136
137def set_plan3(+u: P.Upd, +json: Bool, +raw: Json.Json, +name: String, +key: String, +value: String) -> Plan2:
138 match u:
139 case P.UBad{m}:
140 Answer{err(json, m)}
141 case P.UOk{p}:
142 Wr{P.store(raw, p), set_ok(json, P.store(raw, p), name, key, value, p)}
143
144def set_base(+m: Maybe<&2, P.Prof>, +name: String) -> P.Prof:
145 Maybe.default(&2, P.Prof, m, P.bare(name, SF.ABest{}))
146
147def set_plan2(+valid: Bool, +json: Bool, +raw: Json.Json, +name: String, +key: String, +value: String) -> Plan2:
148 match valid:
149 case False{}:
150 Answer{err(json, "invalid profile name '" ++ name ++ "': use letters, digits, '-' and '_', starting with a letter or digit")}
151 case True{}:
152 set_plan3(P.update(set_base(P.find(raw, name), name), key, value), json, raw, name, key, value)
153
154def set_plan1(+is_default: Bool, +json: Bool, +raw: Json.Json, +name: String, +key: String, +value: String) -> Plan2:
155 match is_default:
156 case True{}:
157 Answer{err(json, "profile 'default' follows the autoswitch settings and cannot be changed; create another profile, or use `cswap config set`")}
158 case False{}:
159 set_plan2(P.valid_name(name), json, raw, name, key, value)
160
161def set_plan(+json: Bool, +raw: Json.Json, +name: String, +key: String, +value: String) -> Plan2:
162 set_plan1(String.eq(name, "default"), json, raw, name, key, value)
163
164
165
166def gone_note(+was_active: Bool) -> String:
167 match was_active:
168 case True{}:
169 "\nactive profile cleared (back to default)"
170 case False{}:
171 ""
172
173def gone_ok(+json: Bool, +raw: Json.Json, +name: String, +was_active: Bool) -> Cmd.Out:
174 match json:
175 case True{}:
176 doc([("removed", Json.JStr{name}), ("activeProfile", PV.opt_json(active_json(P.active(raw))))])
177 case False{}:
178 Cmd.ok("removed profile " ++ name ++ gone_note(was_active))
179
180def gone_missing(+builtin: Bool, +name: String) -> String:
181 match builtin:
182 case True{}:
183 "profile '" ++ name ++ "' is built in and has no stored override to remove"
184 case False{}:
185 unknown_msg(name)
186
187def gone_plan3(+stored: Bool, +json: Bool, +raw: Json.Json, +name: String) -> Plan2:
188 match stored:
189 case True{}:
190 Wr{P.remove(raw, name), gone_ok(json, P.remove(raw, name), name, String.eq(Maybe.default(&2, String, P.active(raw), ""), name))}
191 case False{}:
192 Answer{err(json, gone_missing(P.is_builtin(name), name))}
193
194def gone_plan1(+is_default: Bool, +json: Bool, +raw: Json.Json, +name: String) -> Plan2:
195 match is_default:
196 case True{}:
197 Answer{err(json, "profile 'default' is built in and cannot be removed")}
198 case False{}:
199 gone_plan3(P.has_stored(raw, name), json, raw, name)
200
201def gone_plan(+json: Bool, +raw: Json.Json, +name: String) -> Plan2:
202 gone_plan1(String.eq(name, "default"), json, raw, name)
203
204
205
206def finish(+root: String, +w: Plan2) -> Act.of(Cmd.Out):
207 match w:
208 case Answer{o}:
209 Act.pure(Cmd.Out, o)
210 case Wr{raw, o}:
211 do ActM<Cmd.Out>:
212 u : Unit <- Setcmd.write_settings(root, raw)
213 Act.pure(Cmd.Out, o)
214
215def edit(+root: String, +plan: Plan2) -> IO(Cmd.Out):
216 IO.bind(Fs.Res(Cmd.Out), Cmd.Out, finish(root, plan), r => IO.pure(Cmd.Out, Setcmd.fin(r)))
217
218def use.act(+root: String, +json: Bool, +name: String) -> Act.of(Plan2):
219 do ActM<Plan2>:
220 +raw : Json.Json <- Setcmd.load_raw_w(root)
221 Act.pure(Plan2, use_plan(json, raw, name))
222
223def set.act(+root: String, +json: Bool, +name: String, +key: String, +value: String) -> Act.of(Plan2):
224 do ActM<Plan2>:
225 +raw : Json.Json <- Setcmd.load_raw_w(root)
226 Act.pure(Plan2, set_plan(json, raw, name, key, value))
227
228def gone.act(+root: String, +json: Bool, +name: String) -> Act.of(Plan2):
229 do ActM<Plan2>:
230 +raw : Json.Json <- Setcmd.load_raw_w(root)
231 Act.pure(Plan2, gone_plan(json, raw, name))
232
233
234def planned(+root: String, r: Fs.Res(Plan2)) -> IO(Cmd.Out):
235 match r:
236 case Fail{e}:
237 IO.pure(Cmd.Out, Cmd.fail(Setcmd.err.msg(e)))
238 case Done{w}:
239 edit(root, w)
240
241def run.use(+root: String, +json: Bool, +name: String) -> IO(Cmd.Out):
242 IO.bind(Fs.Res(Plan2), Cmd.Out, use.act(root, json, name), r => planned(root, r))
243
244def run.set(+root: String, +json: Bool, +name: String, +key: String, +value: String) -> IO(Cmd.Out):
245 IO.bind(Fs.Res(Plan2), Cmd.Out, set.act(root, json, name, key, value), r => planned(root, r))
246
247def run.gone(+root: String, +json: Bool, +name: String) -> IO(Cmd.Out):
248 IO.bind(Fs.Res(Plan2), Cmd.Out, gone.act(root, json, name), r => planned(root, r))
249
250
251
252type Plan is Data:
253 PList{}
254 PShow{name: Maybe<&2, String>}
255 PUse{name: String}
256 PSet{name: String, key: String, value: String}
257 PRemove{name: String}
258 PBad{msg: String}
259
260def join_sp(xs: List<String>) -> String:
261 match xs:
262 case Nil{}:
263 ""
264 case Con{h, Nil{}}:
265 h
266 case Con{h, t}:
267 h ++ " " ++ join_sp(t)
268
269def plan.none(rest: List<String>, +p: Plan) -> Plan:
270 match rest:
271 case Nil{}:
272 p
273 case o:
274 PBad{"unrecognized arguments: " ++ join_sp(rest)}
275
276def plan.show(rest: List<String>) -> Plan:
277 match rest:
278 case Nil{}:
279 PShow{None{}}
280 case Con{n, t}:
281 plan.none(t, PShow{Some{n}})
282
283def plan.use(rest: List<String>) -> Plan:
284 match rest:
285 case Nil{}:
286 PBad{"the following arguments are required: NAME"}
287 case Con{n, t}:
288 plan.none(t, PUse{n})
289
290def plan.gone(rest: List<String>) -> Plan:
291 match rest:
292 case Nil{}:
293 PBad{"the following arguments are required: NAME"}
294 case Con{n, t}:
295 plan.none(t, PRemove{n})
296
297def plan.set(rest: List<String>) -> Plan:
298 match rest:
299 case Nil{}:
300 PBad{"the following arguments are required: NAME, KEY, VALUE"}
301 case Con{n, Nil{}}:
302 PBad{"the following arguments are required: KEY, VALUE"}
303 case Con{n, Con{k, Nil{}}}:
304 PBad{"the following arguments are required: VALUE"}
305 case Con{n, Con{k, Con{v, t}}}:
306 plan.none(t, PSet{n, k, v})
307
308def plan.verb(+l: Bool, +sh: Bool, +u: Bool, +st: Bool, +dl: Bool, +a: String, rest: List<String>) -> Plan:
309 match l sh u st dl:
310 case True{} _ _ _ _:
311 plan.none(rest, PList{})
312 case False{} True{} _ _ _:
313 plan.show(rest)
314 case False{} False{} True{} _ _:
315 plan.use(rest)
316 case False{} False{} False{} True{} _:
317 plan.set(rest)
318 case False{} False{} False{} False{} True{}:
319 plan.gone(rest)
320 case _ _ _ _ _:
321 PBad{"argument action: invalid choice: '" ++ a ++ "' (choose from 'list', 'show', 'use', 'set', 'remove')"}
322
323
324def plan.of(args: List<String>) -> Plan:
325 match args:
326 case Nil{}:
327 PList{}
328 case Con{+a, rest}:
329 plan.verb(String.eq(a, "list") || String.eq(a, "ls"), String.eq(a, "show"), String.eq(a, "use"), String.eq(a, "set"), String.eq(a, "remove"), a, rest)
330
331def run(+root: String, +p: Plan, +json: Bool) -> IO(Cmd.Out):
332 match p:
333 case PList{}:
334 run.list(root, json)
335 case PShow{n}:
336 run.show(root, json, n)
337 case PUse{n}:
338 run.use(root, json, n)
339 case PSet{n, k, v}:
340 run.set(root, json, n, k, v)
341 case PRemove{n}:
342 run.gone(root, json, n)
343 case PBad{msg}:
344 IO.pure(Cmd.Out, Cmd.Out{2, "", "cswap profile: error: " ++ msg ++ "\n"})
345
346
347
348def profile(+root: String, args: List<String>, +json: Bool) -> IO(Cmd.Out):
349 run(root, plan.of(args), json)