cliops.bend
cswap-bend-cli@0.1.0.0 · 334 lines · 56 definitions
1import Base
2import cswap-bend-cli@0.1.0.0/urec.bend as Urec
3import cswap-bend-cli@0.1.0.0/store/ops.bend as SOps
4import cswap-bend-cli@0.1.0.0/store/io.bend as SIo
5import cswap-bend-cli@0.1.0.0/store/base.bend as SBase
6import cswap-bend-cli@0.1.0.0/store/seq.bend as SSeq
7import cswap-bend-cli@0.1.0.0/json.bend as Json
8import cswap-bend-cli@0.1.0.0/clock.bend as Clock
9import cswap-bend-cli@0.1.0.0/fs.bend as Fs
10import cswap-bend-cli@0.1.0.0/act.bend as Act
11import cswap-bend-cli@0.1.0.0/cmd.bend as Cmd
12import cswap-bend-cli@0.1.0.0/ustore.bend as Ustore
13import cswap-bend-cli@0.1.0.0/store.bend as Store
14import cswap-bend-cli@0.1.0.0/importu.bend as Importu
15import cswap-bend-cli@0.1.0.0/clibase.bend as Clibase
16
17
18
19type Ni is Data:
20 Ni{noin: Bool, yes: Bool}
21
22def ni_noin(+ni: Ni) -> Bool:
23 match ni:
24 case Ni{noin, yes}:
25 noin
26
27def ni_yes(+ni: Ni) -> Bool:
28 match ni:
29 case Ni{noin, yes}:
30 yes
31
32def slot_in(+m: String) -> String:
33 Clibase.first_line(String.split(String.drop(m, 5n), ' '))
34
35def answer(yes: Bool, +prefix: String, retry: Act.of(String)) -> Act.of(String):
36 match yes:
37 case True{}:
38 Act.bind(String, String, retry, t => Act.pure(String, prefix ++ "\n" ++ t))
39 case False{}:
40 Act.pure(String, prefix ++ "\nCancelled")
41
42def ask(+m: String, retry: Act.of(String)) -> Act.of(String):
43 Act.bind(String, String, Clibase.read_line(), a => answer(Clibase.is_yes(a), m ++ "\nOverwrite slot " ++ slot_in(m) ++ "? [y/N] ", retry))
44
45def ow.pick(occupied: Bool, +c: U32, +m: String, retry: Act.of(String)) -> Act.of(String):
46 match occupied:
47 case True{}:
48 ask(m, retry)
49 case False{}:
50 Act.fail(String, c, m)
51
52def ow.err(ce: U32 & String, retry: Act.of(String), +noin: Bool) -> Act.of(String):
53 (+c, m) = ce
54 ow.pick(U32.is_eq(c, 10) && Bool.not(noin), c, m, retry)
55
56def ow.handle(r: Fs.Res(String), retry: Act.of(String), +noin: Bool) -> Act.of(String):
57 match r:
58 case Done{t}:
59 Act.pure(String, t)
60 case Fail{e}:
61 ow.err(e, retry, noin)
62
63
64
65
66def with_overwrite(first: Act.of(String), retry: Act.of(String), +noin: Bool) -> Act.of(String):
67 IO.bind(Fs.Res(String), Fs.Res(String), first, r => ow.handle(r, retry, noin))
68
69def digits.pick(d: Bool, +c: Char, rest: String) -> String:
70 match d:
71 case True{}:
72 SCon{c, rest}
73 case False{}:
74 ""
75
76def digits_of(s: String) -> String:
77 match s:
78 case SNil{}:
79 ""
80 case SCon{+c, t}:
81 digits.pick(Char.is_digit(c), c, digits_of(t))
82
83def nonempty.pick(empty: Bool, +s: String) -> Maybe<&2, String>:
84 match empty:
85 case True{}:
86 None{}
87 case False{}:
88 Some{s}
89
90def nonempty_str(+s: String) -> Maybe<&2, String>:
91 nonempty.pick(String.is_empty(s), s)
92
93def touched.pick(added: Bool, updated: Bool, +l: String) -> Maybe<&2, String>:
94 match added updated:
95 case True{} _:
96 nonempty_str(digits_of(String.drop(l, 14n)))
97 case False{} True{}:
98 nonempty_str(digits_of(String.drop(l, 32n)))
99 case False{} False{}:
100 None{}
101
102def touched.line(+l: String) -> Maybe<&2, String>:
103 touched.pick(String.starts_with(l, "Added Account "), String.starts_with(l, "Updated credentials for Account "), l)
104
105def first_some(a: Maybe<&2, String>, b: Maybe<&2, String>) -> Maybe<&2, String>:
106 match a:
107 case Some{x}:
108 Some{x}
109 case None{}:
110 b
111
112
113def touched(ls: List<&2, String>) -> Maybe<&2, String>:
114 match ls:
115 case Nil{}:
116 None{}
117 case Con{h, t}:
118 first_some(touched.line(h), touched(t))
119
120def clear_rec(m: Maybe<&2, Json.Json>, +root: String, +slot: String) -> Act.of(Unit):
121 match m:
122 case None{}:
123 Act.pure(Unit, Unit{})
124 case Some{+rec}:
125 Urec.clear_dead(root, [Ustore.Id{slot, SSeq.field_str(rec, "email"), SSeq.field_str(rec, "organizationUuid")}])
126
127def clear_seq(m: Maybe<&2, Json.Json>, +root: String, +slot: String) -> Act.of(Unit):
128 match m:
129 case None{}:
130 Act.pure(Unit, Unit{})
131 case Some{seq}:
132 clear_rec(SSeq.slot_record(seq, slot), root, slot)
133
134def clear_slot(m: Maybe<&2, String>, +root: String) -> Act.of(Unit):
135 match m:
136 case None{}:
137 Act.pure(Unit, Unit{})
138 case Some{slot}:
139 Act.bind(Maybe<&2, Json.Json>, Unit, SIo.read_seq(root), sq => clear_seq(sq, root, slot))
140
141
142
143def after_write(+root: String, +text: String) -> Act.of(Cmd.Out):
144 Act.bind(Unit, Cmd.Out, clear_slot(touched(String.lines(text)), root), u => Act.pure(Cmd.Out, Clibase.text_out(text)))
145
146def add_cmd(+ctx: SBase.Ctx, +alias: Maybe<&2, String>, +slot: Maybe<&2, U32>, +ni: Ni) -> Act.of(Cmd.Out):
147 Act.bind(String, Cmd.Out, with_overwrite(Store.add_current(ctx, alias, slot, False{}, t0 => Store.profile_http(t0)), Store.add_current(ctx, alias, slot, True{}, t1 => Store.profile_http(t1)), ni_noin(ni)),
148 t => after_write(SBase.root_of(ctx), t))
149
150
151def line_or_stop(noin: Bool) -> Act.of(String):
152 match noin:
153 case True{}:
154 Act.fail(String, 2, "the token must be on the command line (standard input is not available)")
155 case False{}:
156 Clibase.read_line()
157
158def token_src2(dash: Bool, +t: String, +noin: Bool) -> Act.of(String):
159 match dash:
160 case True{}:
161 line_or_stop(noin)
162 case False{}:
163 Act.pure(String, t)
164
165def token_src(m: Maybe<&2, String>, +noin: Bool) -> Act.of(String):
166 match m:
167 case Some{+t}:
168 token_src2(String.eq(t, "-"), t, noin)
169 case None{}:
170 line_or_stop(noin)
171
172def token_run2(empty: Bool, +ctx: SBase.Ctx, +t: String, +email: Maybe<&2, String>, +slot: Maybe<&2, U32>, +noin: Bool) -> Act.of(Cmd.Out):
173 match empty:
174 case True{}:
175 Act.fail(Cmd.Out, 2, "Token cannot be empty")
176 case False{}:
177 Act.bind(String, Cmd.Out, with_overwrite(SOps.add_token(ctx, t, email, slot, False{}), SOps.add_token(ctx, t, email, slot, True{}), noin),
178 out => after_write(SBase.root_of(ctx), out))
179
180def token_run(+ctx: SBase.Ctx, +token: String, email: Maybe<&2, String>, slot: Maybe<&2, U32>, +noin: Bool) -> Act.of(Cmd.Out):
181 token_run2(String.is_empty(String.trim(token)), ctx, String.trim(token), email, slot, noin)
182
183def add_token_cmd(+ctx: SBase.Ctx, src: Maybe<&2, String>, email: Maybe<&2, String>, slot: Maybe<&2, U32>, +ni: Ni) -> Act.of(Cmd.Out):
184 Act.bind(String, Cmd.Out, token_src(src, ni_noin(ni)), t => token_run(ctx, t, email, slot, ni_noin(ni)))
185
186def remove_answer(yes: Bool, +prompt: String, +ctx: SBase.Ctx, +num: String) -> Act.of(Cmd.Out):
187 match yes:
188 case True{}:
189 Act.bind(String, Cmd.Out, SOps.remove(ctx, num), t => Act.pure(Cmd.Out, Cmd.Out{0, prompt ++ t ++ "\n", ""}))
190 case False{}:
191 Act.pure(Cmd.Out, Cmd.Out{0, prompt ++ "Cancelled\n", ""})
192
193def remove_ask(+ctx: SBase.Ctx, +num: String, +email: String) -> Act.of(Cmd.Out):
194 Act.bind(String, Cmd.Out, Clibase.read_line(), a => remove_answer(Clibase.is_yes(a), "Are you sure you want to permanently remove Account-" ++ num ++ " (" ++ email ++ ")? [y/N] ", ctx, num))
195
196def remove_now(+ctx: SBase.Ctx, +num: String) -> Act.of(Cmd.Out):
197 Act.bind(String, Cmd.Out, SOps.remove(ctx, num), t => Act.pure(Cmd.Out, Cmd.ok(t)))
198
199def remove_gate(noin: Bool, +ctx: SBase.Ctx, +num: String, +email: String) -> Act.of(Cmd.Out):
200 match noin:
201 case True{}:
202 Act.fail(Cmd.Out, 2, "removing Account-" ++ num ++ " needs confirmation; pass --yes")
203 case False{}:
204 remove_ask(ctx, num, email)
205
206def remove_confirm(yes: Bool, noin: Bool, +ctx: SBase.Ctx, +num: String, +email: String) -> Act.of(Cmd.Out):
207 match yes:
208 case True{}:
209 remove_now(ctx, num)
210 case False{}:
211 remove_gate(noin, ctx, num, email)
212
213def remove_rec(m: Maybe<&2, Json.Json>, +ctx: SBase.Ctx, +num: String, +ni: Ni) -> Act.of(Cmd.Out):
214 match m:
215 case None{}:
216 Act.fail(Cmd.Out, 3, "Account-" ++ num ++ " does not exist")
217 case Some{rec}:
218 remove_confirm(ni_yes(ni), ni_noin(ni), ctx, num, SSeq.field_str(rec, "email"))
219
220def remove_res(r: SSeq.Resolved, +ctx: SBase.Ctx, +seq: Json.Json, +ident: String, +ni: Ni) -> Act.of(Cmd.Out):
221 match r:
222 case SSeq.RNone{}:
223 Act.fail(Cmd.Out, 3, "No account found with identifier: " ++ ident)
224 case SSeq.RAmb{msg}:
225 Act.fail(Cmd.Out, 1, msg)
226 case SSeq.RSlot{+n}:
227 remove_rec(SSeq.slot_record(seq, n), ctx, n, ni)
228
229def remove_seq(m: Maybe<&2, Json.Json>, +ctx: SBase.Ctx, +ident: String, +ni: Ni) -> Act.of(Cmd.Out):
230 match m:
231 case None{}:
232 Act.fail(Cmd.Out, 1, "No accounts are managed yet")
233 case Some{+seq}:
234 remove_res(SSeq.resolve(seq, ident), ctx, seq, ident, ni)
235
236def remove_cmd(+ctx: SBase.Ctx, +ident: String, +ni: Ni) -> Act.of(Cmd.Out):
237 Act.bind(Maybe<&2, Json.Json>, Cmd.Out, SIo.read_seq(SBase.root_of(ctx)), m => remove_seq(m, ctx, ident, ni))
238
239def source_pick(ex: Bool, +path: String) -> Act.of(String):
240 match ex:
241 case True{}:
242 Fs.read_file(path)
243 case False{}:
244 Act.fail(String, 4, "usage file not found: " ++ path)
245
246def source_text(+path: String) -> Act.of(String):
247 Act.bind(Bool, String, Act.lift(Bool, Fs.exists(path)), ex => source_pick(ex, path))
248
249def source_dash(dash: Bool, +path: String) -> Act.of(String):
250 match dash:
251 case True{}:
252 IO.bind(Fs.Res(String), Fs.Res(String), Fs.read_stdin(), r => IO.pure(Fs.Res(String), r))
253 case False{}:
254 source_text(path)
255
256def source_of(+path: String) -> Act.of(String):
257 source_dash(String.eq(path, "-"), path)
258
259def slot_of_reading(+r: Urec.Reading) -> String:
260 match r:
261 case Urec.Reading{id, usage, age}:
262 Ustore.slot_of(id)
263
264def email_of_reading(+r: Urec.Reading) -> String:
265 match r:
266 case Urec.Reading{Ustore.Id{slot, email, org}, usage, age}:
267 email
268
269def adopt_line(yes: Bool, +r: Urec.Reading) -> String:
270 match yes:
271 case True{}:
272 "Adopted usage for " ++ email_of_reading(r) ++ " \u{2192} slot " ++ slot_of_reading(r) ++ "\n"
273 case False{}:
274 "Kept slot " ++ slot_of_reading(r) ++ "'s own reading for " ++ email_of_reading(r) ++ ": it is newer\n"
275
276def adopt_lines(rs: List<&2, Urec.Reading>, +adopted: List<&2, String>) -> String:
277 match rs:
278 case Nil{}:
279 ""
280 case Con{+r, t}:
281 adopt_line(Importu.has_slot(slot_of_reading(r), adopted), r) ++ adopt_lines(t, adopted)
282
283def hold_text(any: Bool, +h: U32) -> String:
284 match any:
285 case True{}:
286 Clock.sel.s(U32.is_gt(h, 0), "; fetching held for up to " ++ U32.show(h) ++ "s", "; holds lifted")
287 case False{}:
288 ""
289
290def hold_note(hold: Maybe<&2, U32>, +any: Bool) -> String:
291 match hold:
292 case Some{h}:
293 hold_text(any, h)
294 case None{}:
295 ""
296
297def summary(+adopted: Nat, +kept: Nat, +skipped: U32, hold: Maybe<&2, U32>, +any: Bool) -> String:
298 "Done: " ++ Nat.show(adopted) ++ " adopted, " ++ Nat.show(kept) ++ " kept, " ++ U32.show(skipped) ++ " skipped" ++ hold_note(hold, any)
299
300def import_report(+rs: List<&2, Urec.Reading>, +skipped: U32, hold: Maybe<&2, U32>, ar: Urec.AR) -> Cmd.Out:
301 match ar:
302 case Urec.AR{rows, +adopted, kept}:
303 Cmd.Out{0, "", adopt_lines(rs, adopted) ++ summary(List.length(&2, String, adopted), List.length(&2, String, kept), skipped, hold, Bool.not(List.is_empty(&2, Urec.Reading, rs))) ++ "\n"}
304
305def adopt_run(+root: String, +rs: List<&2, Urec.Reading>, +skipped: U32, +hold: Maybe<&2, U32>, +now: U32) -> Act.of(Cmd.Out):
306 Act.bind(Urec.AR, Cmd.Out, Urec.adopt(root, rs, hold, now), ar => Act.pure(Cmd.Out, import_report(rs, skipped, hold, ar)))
307
308def import_plan_run(r: Result<&2, &2, String, Importu.Plan>, +root: String, hold: Maybe<&2, U32>, +now: U32) -> Act.of(Cmd.Out):
309 match r:
310 case Fail{e}:
311 Act.fail(Cmd.Out, 4, e)
312 case Done{Importu.Plan{rs, sk}}:
313 adopt_run(root, rs, sk, hold, now)
314
315def import_seq(m: Maybe<&2, Json.Json>) -> Json.Json:
316 match m:
317 case Some{j}:
318 j
319 case None{}:
320 Json.of_pairs([])
321
322def import_with(+ctx: SBase.Ctx, +text: String, hold: Maybe<&2, U32>, tm: Clibase.Tm, m: Maybe<&2, Json.Json>) -> Act.of(Cmd.Out):
323 match tm:
324 case Clibase.Tm{+now, off, tick}:
325 import_plan_run(Importu.import_plan(text, import_seq(m), now, off), SBase.root_of(ctx), hold, now)
326
327def import_run(+ctx: SBase.Ctx, +text: String, hold: Maybe<&2, U32>) -> Act.of(Cmd.Out):
328 do Clibase.ActM<Cmd.Out>:
329 tm : Clibase.Tm <- Clibase.times()
330 m : Maybe<&2, Json.Json> <- SIo.read_seq(SBase.root_of(ctx))
331 import_with(ctx, text, hold, tm, m)
332
333def import_cmd(+ctx: SBase.Ctx, +path: String, hold: Maybe<&2, U32>) -> Act.of(Cmd.Out):
334 Act.bind(String, Cmd.Out, source_of(path), text => import_run(ctx, text, hold))