profile.bend
cswap-bend-cli@0.1.0.0 · 424 lines · 66 definitions
1import Base
2import cswap-bend-cli@0.1.0.0/json.bend as Json
3import cswap-bend-cli@0.1.0.0/policy/safe.bend as SF
4import cswap-bend-cli@0.1.0.0/daemon/config.bend as Cfg
5import cswap-bend-cli@0.1.0.0/setload.bend as Setload
6import cswap-bend-cli@0.1.0.0/setwrite.bend as Setwrite
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23type Prof is Data:
24 Prof{name: String, algo: SF.Algo, threshold: Maybe<&2, String>, models: Maybe<&2, String>, cooldown: Maybe<&2, String>, interval: Maybe<&2, String>}
25
26
27
28type Res is Data:
29 RNone{}
30 ROk{prof: Prof}
31 RUnknown{name: String}
32
33type Upd is Data:
34 UBad{msg: String}
35 UOk{prof: Prof}
36
37def keys() -> List<&2, String>:
38 ["strategy", "threshold", "models", "cooldown_s", "interval_s"]
39
40def builtin_names() -> List<&2, String>:
41 ["default", "best", "round-robin", "soonest-reset", "sticky", "drain-first"]
42
43def bare(+name: String, +algo: SF.Algo) -> Prof:
44 Prof{name, algo, None{}, None{}, None{}, None{}}
45
46def builtin.of(+name: String, +m: Maybe<&2, SF.Algo>) -> Maybe<&2, Prof>:
47 match m:
48 case Some{a}:
49 Some{bare(name, a)}
50 case None{}:
51 None{}
52
53def builtin.pick(+is_default: Bool, +name: String) -> Maybe<&2, Prof>:
54 match is_default:
55 case True{}:
56 Some{bare(name, SF.ABest{})}
57 case False{}:
58 builtin.of(name, SF.parse(name))
59
60def builtin(+name: String) -> Maybe<&2, Prof>:
61 builtin.pick(String.eq(name, "default"), name)
62
63def is_builtin(+name: String) -> Bool:
64 Setload.has_str(builtin_names(), name)
65
66
67
68def num_text(+m: Maybe<&2, Json.Json>) -> Maybe<&2, String>:
69 match m:
70 case Some{Json.JNum{t}}:
71 Some{t}
72 case _:
73 None{}
74
75def join_strs(+xs: List<&2, Json.Json>) -> List<&2, String>:
76 match xs:
77 case Nil{}:
78 []
79 case Con{Json.JStr{s}, t}:
80 s <> join_strs(t)
81 case Con{h, t}:
82 join_strs(t)
83
84def models_text(+m: Maybe<&2, Json.Json>) -> Maybe<&2, String>:
85 match m:
86 case Some{Json.JStr{s}}:
87 Some{s}
88 case Some{Json.JArr{items}}:
89 Some{String.join(join_strs(Json.to_list(Json.JArr{items})), ",")}
90 case _:
91 None{}
92
93def algo_of(+m: Maybe<&2, Json.Json>, +dflt: SF.Algo) -> SF.Algo:
94 match m:
95 case Some{Json.JStr{s}}:
96 Maybe.default(&2, SF.Algo, SF.parse(s), dflt)
97 case _:
98 dflt
99
100def algo_base(+p: Prof) -> SF.Algo:
101 match p:
102 case Prof{n, a, th, ms, cd, iv}:
103 a
104
105
106def of_json(+name: String, +j: Json.Json, +base: SF.Algo) -> Prof:
107 Prof{name, algo_of(Json.get("strategy", j), base), num_text(Json.get("threshold", j)), models_text(Json.get("models", j)),
108 num_text(Json.get("cooldown_s", j)), num_text(Json.get("interval_s", j))}
109
110def stored_in(+m: Maybe<&2, Json.Json>, +name: String) -> Maybe<&2, Json.Json>:
111 match m:
112 case Some{ps}:
113 Json.get(name, ps)
114 case None{}:
115 None{}
116
117def stored(+raw: Json.Json, +name: String) -> Maybe<&2, Json.Json>:
118 stored_in(Json.get("profiles", raw), name)
119
120def found(+s: Maybe<&2, Json.Json>, +b: Maybe<&2, Prof>, +name: String) -> Maybe<&2, Prof>:
121 match s:
122 case Some{j}:
123 Some{of_json(name, j, algo_base(Maybe.default(&2, Prof, b, bare(name, SF.ABest{}))))}
124 case None{}:
125 b
126
127def find.pick(+is_default: Bool, +raw: Json.Json, +name: String) -> Maybe<&2, Prof>:
128 match is_default:
129 case True{}:
130 builtin(name)
131 case False{}:
132 found(stored(raw, name), builtin(name), name)
133
134def find(+raw: Json.Json, +name: String) -> Maybe<&2, Prof>:
135 find.pick(String.eq(name, "default"), raw, name)
136
137def extra_keep(+skip: Bool, +k: String, +rest: List<&2, String>) -> List<&2, String>:
138 match skip:
139 case True{}:
140 rest
141 case False{}:
142 k <> rest
143
144def extra_names(+ks: List<&2, String>) -> List<&2, String>:
145 match ks:
146 case Nil{}:
147 []
148 case Con{k, t}:
149 extra_keep(is_builtin(k), k, extra_names(t))
150
151def profiles_keys(+m: Maybe<&2, Json.Json>) -> List<&2, String>:
152 match m:
153 case Some{ps}:
154 Json.keys(ps)
155 case None{}:
156 []
157
158
159def names(+raw: Json.Json) -> List<&2, String>:
160 List.append(&2, String, builtin_names(), extra_names(profiles_keys(Json.get("profiles", raw))))
161
162def active.pick(+empty: Bool, +s: String) -> Maybe<&2, String>:
163 match empty:
164 case True{}:
165 None{}
166 case False{}:
167 Some{s}
168
169def active.of(+m: Maybe<&2, Json.Json>) -> Maybe<&2, String>:
170 match m:
171 case Some{Json.JStr{s}}:
172 active.pick(String.is_empty(s), s)
173 case _:
174 None{}
175
176def active(+raw: Json.Json) -> Maybe<&2, String>:
177 active.of(Json.get("active_profile", raw))
178
179def resolve.found(+m: Maybe<&2, Prof>, +name: String) -> Res:
180 match m:
181 case Some{p}:
182 ROk{p}
183 case None{}:
184 RUnknown{name}
185
186def resolve.name(+raw: Json.Json, +n: Maybe<&2, String>) -> Res:
187 match n:
188 case None{}:
189 RNone{}
190 case Some{name}:
191 resolve.found(find(raw, name), name)
192
193def pick_name(+over: Maybe<&2, String>, +act: Maybe<&2, String>) -> Maybe<&2, String>:
194 match over:
195 case Some{o}:
196 over
197 case None{}:
198 act
199
200
201def resolve(+raw: Json.Json, +over: Maybe<&2, String>) -> Res:
202 resolve.name(raw, pick_name(over, active(raw)))
203
204
205
206def over_thr(+m: Maybe<&2, String>, +d: U32) -> U32:
207 match m:
208 case Some{t}:
209 Cfg.field_centi2(Cfg.centi(t), d, 5000, 9990)
210 case None{}:
211 d
212
213def over_secs(+m: Maybe<&2, String>, +d: U32, +lo: U32, +hi: U32) -> U32:
214 match m:
215 case Some{t}:
216 (Cfg.field_centi2(Cfg.centi(t), (d * 100 : U32), (lo * 100 : U32), (hi * 100 : U32)) / 100 : U32)
217 case None{}:
218 d
219
220
221
222def strat_of(+algo: SF.Algo, +st: String) -> String:
223 match algo:
224 case SF.ABest{}:
225 st
226 case o:
227 SF.name(o)
228
229
230def with_prof(+c: Cfg.Cfg, +p: Prof) -> Cfg.Cfg:
231 match c p:
232 case Cfg.Cfg{th, iv, cd, hy, st, ia, ut, ms} Prof{n, algo, pth, pms, pcd, piv}:
233 Cfg.Cfg{over_thr(pth, th), over_secs(piv, iv, 15, 3600), over_secs(pcd, cd, 0, 86400), hy, strat_of(algo, st), ia, ut, Cfg.over_models(pms, ms)}
234
235
236
237def num_json(+m: Maybe<&2, String>) -> Maybe<&2, Json.Json>:
238 match m:
239 case Some{t}:
240 Some{Json.JNum{t}}
241 case None{}:
242 None{}
243
244def strs_json(+xs: List<&2, String>) -> List<&2, Json.Json>:
245 match xs:
246 case Nil{}:
247 []
248 case Con{h, t}:
249 Json.JStr{h} <> strs_json(t)
250
251def models_json(+m: Maybe<&2, String>) -> Maybe<&2, Json.Json>:
252 match m:
253 case Some{t}:
254 Some{Json.arr(strs_json(Cfg.parse_models(t)))}
255 case None{}:
256 None{}
257
258def opt_set(+key: String, +m: Maybe<&2, Json.Json>, +o: Json.Json) -> Json.Json:
259 match m:
260 case Some{v}:
261 Json.set(key, v, o)
262 case None{}:
263 Json.del(key, o)
264
265
266
267def to_json(+p: Prof, +base: Json.Json) -> Json.Json:
268 match p:
269 case Prof{n, algo, th, ms, cd, iv}:
270 opt_set("interval_s", num_json(iv), opt_set("cooldown_s", num_json(cd), opt_set("models", models_json(ms), opt_set("threshold", num_json(th), Json.set("strategy", Json.JStr{SF.name(algo)}, base)))))
271
272def name_of(+p: Prof) -> String:
273 match p:
274 case Prof{n, a, th, ms, cd, iv}:
275 n
276
277def obj_of(+m: Maybe<&2, Json.Json>) -> Json.Json:
278 match m:
279 case Some{Json.JObj{f}}:
280 Json.JObj{f}
281 case _:
282 Setload.empty()
283
284def store.at(+p: Prof, +ps: Json.Json) -> Json.Json:
285 Json.set(name_of(p), to_json(p, obj_of(Json.get(name_of(p), ps))), ps)
286
287
288def store(+raw: Json.Json, +p: Prof) -> Json.Json:
289 Json.set("profiles", store.at(p, obj_of(Json.get("profiles", raw))), Setload.with_version(raw))
290
291def drop.empty(+ps: Json.Json, +raw: Json.Json) -> Json.Json:
292 match ps:
293 case Json.JObj{Json.JNil{}}:
294 Json.del("profiles", raw)
295 case o:
296 Json.set("profiles", ps, raw)
297
298def drop.active(+is_active: Bool, +raw: Json.Json) -> Json.Json:
299 match is_active:
300 case True{}:
301 Json.del("active_profile", raw)
302 case False{}:
303 raw
304
305
306
307def remove(+raw: Json.Json, +name: String) -> Json.Json:
308 drop.active(String.eq(Maybe.default(&2, String, active(raw), ""), name), drop.empty(Json.del(name, obj_of(Json.get("profiles", raw))), raw))
309
310def use(+raw: Json.Json, +name: String) -> Json.Json:
311 Json.set("active_profile", Json.JStr{name}, Setload.with_version(raw))
312
313def has_stored(+raw: Json.Json, +name: String) -> Bool:
314 Setload.has_str(profiles_keys(Json.get("profiles", raw)), name)
315
316
317
318type NumRes is Data:
319 NBad{msg: String}
320 NSet{t: Maybe<&2, String>}
321
322def num.good(+p: Setwrite.Parsed) -> NumRes:
323 match p:
324 case Setwrite.Bad{m}:
325 NBad{m}
326 case Setwrite.Good{Json.JNum{t}}:
327 NSet{Some{t}}
328 case Setwrite.Good{j}:
329 NBad{"expected a number"}
330
331def num.pick(+inherit: Bool, +key: String, +raw: String, +lo: String, +hi: String) -> NumRes:
332 match inherit:
333 case True{}:
334 NSet{None{}}
335 case False{}:
336 num.good(Setwrite.parse.number(False{}, key, raw, lo, hi))
337
338
339
340def num.of(+key: String, +raw: String, +lo: String, +hi: String) -> NumRes:
341 num.pick(String.eq(raw, "inherit"), key, raw, lo, hi)
342
343def upd.th(+p: Prof, +n: NumRes) -> Upd:
344 match p n:
345 case Prof{nm, a, th, ms, cd, iv} NBad{m}:
346 UBad{m}
347 case Prof{nm, a, th, ms, cd, iv} NSet{t}:
348 UOk{Prof{nm, a, t, ms, cd, iv}}
349
350def upd.cd(+p: Prof, +n: NumRes) -> Upd:
351 match p n:
352 case Prof{nm, a, th, ms, cd, iv} NBad{m}:
353 UBad{m}
354 case Prof{nm, a, th, ms, cd, iv} NSet{t}:
355 UOk{Prof{nm, a, th, ms, t, iv}}
356
357def upd.iv(+p: Prof, +n: NumRes) -> Upd:
358 match p n:
359 case Prof{nm, a, th, ms, cd, iv} NBad{m}:
360 UBad{m}
361 case Prof{nm, a, th, ms, cd, iv} NSet{t}:
362 UOk{Prof{nm, a, th, ms, cd, t}}
363
364def upd.algo(+p: Prof, +m: Maybe<&2, SF.Algo>) -> Upd:
365 match p m:
366 case Prof{nm, a, th, ms, cd, iv} Some{x}:
367 UOk{Prof{nm, x, th, ms, cd, iv}}
368 case Prof{nm, a, th, ms, cd, iv} None{}:
369 UBad{"strategy must be one of: " ++ String.join(SF.names(), ", ")}
370
371
372def models.pick(+inherit: Bool, +raw: String) -> Maybe<&2, String>:
373 match inherit:
374 case True{}:
375 None{}
376 case False{}:
377 Some{String.join(Setload.model_names(raw), ",")}
378
379def models.of(+raw: String) -> Maybe<&2, String>:
380 models.pick(String.eq(raw, "inherit"), raw)
381
382def upd.models(+p: Prof, +raw: String) -> Upd:
383 match p:
384 case Prof{nm, a, th, ms, cd, iv}:
385 UOk{Prof{nm, a, th, models.of(raw), cd, iv}}
386
387def upd.key(+k_strat: Bool, +k_thr: Bool, +k_mod: Bool, +k_cd: Bool, +k_iv: Bool, +key: String, +p: Prof, +raw: String) -> Upd:
388 match k_strat k_thr k_mod k_cd k_iv:
389 case True{} _ _ _ _:
390 upd.algo(p, SF.parse(raw))
391 case False{} True{} _ _ _:
392 upd.th(p, num.of("threshold", raw, "50", "99.9"))
393 case False{} False{} True{} _ _:
394 upd.models(p, raw)
395 case False{} False{} False{} True{} _:
396 upd.cd(p, num.of("cooldown_s", raw, "0", "86400"))
397 case False{} False{} False{} False{} True{}:
398 upd.iv(p, num.of("interval_s", raw, "15", "3600"))
399 case _ _ _ _ _:
400 UBad{"unknown profile key '" ++ key ++ "'; valid keys: " ++ String.join(keys(), ", ")}
401
402
403def update(+p: Prof, +key: String, +raw: String) -> Upd:
404 upd.key(String.eq(key, "strategy"), String.eq(key, "threshold"), String.eq(key, "models"), String.eq(key, "cooldown_s"), String.eq(key, "interval_s"), key, p, raw)
405
406
407
408def chars_ok(+s: String) -> Bool:
409 match s:
410 case SNil{}:
411 True{}
412 case SCon{c, t}:
413 (Char.is_alpha(c) || Char.is_digit(c) || Char.is_eq(c, '-') || Char.is_eq(c, '_')) && chars_ok(t)
414
415def first_ok(+s: String) -> Bool:
416 match s:
417 case SNil{}:
418 False{}
419 case SCon{c, t}:
420 Char.is_alpha(c) || Char.is_digit(c)
421
422
423def valid_name(+s: String) -> Bool:
424 first_ok(s) && chars_ok(s)