cfgio.bend
cswap-bend-cli@0.1.0.0 · 378 lines · 65 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/setcmd.bend as Setcmd
7import cswap-bend-cli@0.1.0.0/setload.bend as Setload
8import cswap-bend-cli@0.1.0.0/setimp.bend as Imp
9import cswap-bend-cli@0.1.0.0/profile.bend as P
10import cswap-bend-cli@0.1.0.0/profcmd.bend as Profcmd
11import cswap-bend-cli@0.1.0.0/clibase.bend as Clibase
12
13
14
15
16
17
18
19
20
21
22type Doc is Data:
23 Doc{fmt: String, keys: List<&2, String>, norm: Json.Json}
24
25type Parsed is Data:
26 Parsed{fmt: String, doc: Json.Json}
27
28
29
30type IA is Data:
31 IA{file: Maybe<&2, String>, fmt: Maybe<&2, String>, replace: Bool, err: String, want: String, data: Maybe<&2, String>}
32
33def ActM(-A: Type) -> Type:
34 Act.of(A)
35
36def ActM.pure(-A: Type, x: A) -> Act.of(A):
37 Act.pure(A, x)
38
39def ActM.bind(-A: Type, -B: Type, m: Act.of(A), f: A -> Act.of(B)) -> Act.of(B):
40 Act.bind(A, B, m, f)
41
42
43
44def ia.err(+a: IA, msg: String) -> IA:
45 match a:
46 case IA{file, fmt, replace, err, w, data}:
47 IA{file, fmt, replace, Bool.pick(String, String.is_empty(err), msg, err), "", data}
48
49def ia.val(+a: IA, +v: String) -> IA:
50 match a:
51 case IA{file, fmt, replace, err, w, data}:
52 Bool.pick(IA, String.eq(w, "--data"), IA{file, fmt, replace, err, "", Some{v}}, IA{file, Some{v}, replace, err, "", data})
53
54def ia.want(+a: IA, +flag: String) -> IA:
55 match a:
56 case IA{file, fmt, replace, err, w, data}:
57 IA{file, fmt, replace, err, flag, data}
58
59def ia.rep(+a: IA) -> IA:
60 match a:
61 case IA{file, fmt, replace, err, w, data}:
62 IA{file, fmt, True{}, err, w, data}
63
64def ia.file2(m: Maybe<&2, String>, +h: String, +a: IA) -> IA:
65 match m a:
66 case None{} IA{file, fmt, replace, err, w, data}:
67 IA{Some{h}, fmt, replace, err, w, data}
68 case Some{f} _:
69 ia.err(a, "unrecognized arguments: " ++ h)
70
71def ia.get_file(+a: IA) -> Maybe<&2, String>:
72 match a:
73 case IA{file, fmt, replace, err, w, data}:
74 file
75
76def ia.file(+h: String, +a: IA) -> IA:
77 ia.file2(ia.get_file(a), h, a)
78
79def ia.is_pending(+a: IA) -> Bool:
80 match a:
81 case IA{file, fmt, replace, err, w, data}:
82 Bool.not(String.is_empty(w))
83
84def sel.ia(c: Bool, +x: IA, +y: IA) -> IA:
85 match c:
86 case True{}:
87 x
88 case False{}:
89 y
90
91def ia.free(+h: String, +a: IA) -> IA:
92 sel.ia(String.eq(h, "--format") || String.eq(h, "--data"), ia.want(a, h),
93 sel.ia(String.starts_with(h, "--format="), ia.val(ia.want(a, "--format"), String.drop(h, 9n)),
94 sel.ia(String.starts_with(h, "--data="), ia.val(ia.want(a, "--data"), String.drop(h, 7n)),
95 sel.ia(String.eq(h, "--replace"), ia.rep(a),
96 sel.ia(String.starts_with(h, "-") && Bool.not(String.eq(h, "-")), ia.err(a, "unrecognized arguments: " ++ h),
97 ia.file(h, a))))))
98
99def ia.step(+h: String, +a: IA) -> IA:
100 sel.ia(ia.is_pending(a), ia.val(a, h), ia.free(h, a))
101
102def ia.go(args: List<&2, String>, +a: IA) -> IA:
103 match args:
104 case Nil{}:
105 a
106 case Con{h, t}:
107 ia.go(t, ia.step(h, a))
108
109def ia.need(file: Bool, data: Bool, +a: IA) -> IA:
110 match file data:
111 case False{} False{}:
112 ia.err(a, "the following arguments are required: FILE")
113 case True{} True{}:
114 ia.err(a, "give a FILE or --data, not both")
115 case _ _:
116 a
117
118def ia.end2(+a: IA) -> IA:
119 match a:
120 case IA{file, fmt, replace, err, w, data}:
121 ia.need(Maybe.is_some(&2, String, file), Maybe.is_some(&2, String, data), a)
122
123def ia.end(+a: IA) -> IA:
124 match a:
125 case IA{file, fmt, replace, err, w, data}:
126 ia.end2(Bool.pick(IA, String.is_empty(w), a, ia.err(a, "argument " ++ w ++ ": expected one argument")))
127
128def ia.parse(args: List<&2, String>) -> IA:
129 ia.end(ia.go(args, IA{None{}, None{}, False{}, "", "", None{}}))
130
131
132
133def failed(+code: U32, +msg: String) -> Act.of(String):
134 Act.fail(String, code, msg)
135
136def read_stdin(noin: Bool) -> Act.of(String):
137 match noin:
138 case True{}:
139 failed(2, "standard input is not available here; give a file")
140 case False{}:
141 Fs.read_stdin()
142
143def read_path(r: Fs.Res(String), +path: String) -> Act.of(String):
144 match r:
145 case Done{t}:
146 Act.pure(String, t)
147 case Fail{e}:
148 failed(1, "could not read " ++ path ++ ": " ++ Setcmd.err.msg(e))
149
150def read_input2(dash: Bool, +path: String, noin: Bool) -> Act.of(String):
151 match dash:
152 case True{}:
153 read_stdin(noin)
154 case False{}:
155 IO.bind(Fs.Res(String), Fs.Res(String), Fs.read_file(path), r => read_path(r, path))
156
157def lift_str(r: Result<&2, &2, String, String>) -> Act.of(String):
158 match r:
159 case Done{s}:
160 Act.pure(String, s)
161 case Fail{m}:
162 failed(1, m)
163
164def lift_json(r: Result<&2, &2, String, Json.Json>) -> Act.of(Json.Json):
165 match r:
166 case Done{j}:
167 Act.pure(Json.Json, j)
168 case Fail{m}:
169 Act.fail(Json.Json, 1, m)
170
171def lift_unit(r: Result<&2, &2, String, Unit>) -> Act.of(Unit):
172 match r:
173 case Done{u}:
174 Act.pure(Unit, Unit{})
175 case Fail{m}:
176 Act.fail(Unit, 1, m)
177
178def keys_check(only: Bool, +doc: Json.Json) -> Act.of(Unit):
179 match only:
180 case True{}:
181 lift_unit(Imp.profile_only(Json.keys(doc)))
182 case False{}:
183 Act.pure(Unit, Unit{})
184
185def active_found(m: Maybe<&2, P.Prof>, +name: String) -> Act.of(Unit):
186 match m:
187 case Some{p}:
188 Act.pure(Unit, Unit{})
189 case None{}:
190 Act.fail(Unit, 1, "active_profile '" ++ name ++ "' names no profile; `cswap profile list` shows the profiles")
191
192
193
194def active_check(m: Maybe<&2, String>, +merged: Json.Json) -> Act.of(Unit):
195 match m:
196 case None{}:
197 Act.pure(Unit, Unit{})
198 case Some{+name}:
199 active_found(P.find(merged, name), name)
200
201
202def base_of(only: Bool, replace: Bool, +raw: Json.Json) -> Json.Json:
203 Bool.pick(Json.Json, only && replace, Json.del("active_profile", Json.del("profiles", raw)), raw)
204
205def combine(+replace: Bool, +only: Bool, +norm: Json.Json, +raw: Json.Json) -> Json.Json:
206 Setload.with_version(Bool.pick(Json.Json, replace && Bool.not(only), Imp.strip(norm), Imp.merge(norm, base_of(only, replace, raw))))
207
208
209
210def key_names(ks: List<&2, String>) -> String:
211 String.join(ks, ", ")
212
213def strs(ks: List<&2, String>) -> List<&2, Json.Json>:
214 match ks:
215 case Nil{}:
216 []
217 case Con{h, t}:
218 Json.JStr{h} <> strs(t)
219
220def done_out(json: Bool, +path: String, +f: String, +keys: List<&2, String>) -> Cmd.Out:
221 match json:
222 case True{}:
223 Cmd.doc(Json.of_pairs([("schemaVersion", Json.JNum{"1"}), ("source", Json.JStr{path}), ("format", Json.JStr{f}), ("keys", Json.arr(strs(keys)))]))
224 case False{}:
225 Cmd.ok("Imported " ++ Nat.show(List.length(&2, String, keys)) ++ " key(s) from " ++ path ++ " as " ++ f ++ ": " ++ key_names(keys))
226
227def ia.get_fmt(+a: IA) -> Maybe<&2, String>:
228 match a:
229 case IA{file, fmt, replace, err, w, data}:
230 fmt
231
232def ia.get_replace(+a: IA) -> Bool:
233 match a:
234 case IA{file, fmt, replace, err, w, data}:
235 replace
236
237def ia.get_data(+a: IA) -> Maybe<&2, String>:
238 match a:
239 case IA{file, fmt, replace, err, w, data}:
240 data
241
242def read_src(data: Maybe<&2, String>, +path: String, noin: Bool) -> Act.of(String):
243 match data:
244 case Some{t}:
245 Act.pure(String, t)
246 case None{}:
247 read_input2(String.eq(path, "-"), path, noin)
248
249def parse_input(+path: String, fmt: Maybe<&2, String>, data: Maybe<&2, String>, noin: Bool) -> Act.of(Parsed):
250 do ActM<Parsed>:
251 +text : String <- read_src(data, path, noin)
252 +f : String <- lift_str(Imp.pick_format(fmt, path))
253 +doc : Json.Json <- lift_json(Imp.parse_doc(f, text))
254 Act.pure(Parsed, Parsed{f, doc})
255
256def check_doc(only: Bool, +doc: Json.Json) -> Act.of(Json.Json):
257 do ActM<Json.Json>:
258 keys_check(only, doc)
259 lift_json(Imp.normalize(doc))
260
261def loaded(+p: Parsed, only: Bool) -> Act.of(Doc):
262 match p:
263 case Parsed{f, doc}:
264 Act.bind(Json.Json, Doc, check_doc(only, doc), norm => Act.pure(Doc, Doc{f, Json.keys(doc), norm}))
265
266def load_doc(+path: String, fmt: Maybe<&2, String>, data: Maybe<&2, String>, noin: Bool, only: Bool) -> Act.of(Doc):
267 Act.bind(Parsed, Doc, parse_input(path, fmt, data, noin), p => loaded(p, only))
268
269def save_doc(+root: String, +path: String, +d: Doc, replace: Bool, json: Bool, only: Bool) -> Act.of(Cmd.Out):
270 match d:
271 case Doc{f, keys, norm}:
272 do ActM<Cmd.Out>:
273 +raw : Json.Json <- Setcmd.load_raw_w(root)
274 +merged : Json.Json = combine(replace, only, norm, raw)
275 active_check(P.active(merged), merged)
276 Setcmd.write_settings(root, merged)
277 Act.pure(Cmd.Out, done_out(json, path, f, keys))
278
279def import_run(+root: String, +path: String, fmt: Maybe<&2, String>, data: Maybe<&2, String>, replace: Bool, noin: Bool, json: Bool, +only: Bool) -> Act.of(Cmd.Out):
280 Act.bind(Doc, Cmd.Out, load_doc(path, fmt, data, noin, only), d => save_doc(root, path, d, replace, json, only))
281
282def import_act(+root: String, +a: IA, noin: Bool, json: Bool, only: Bool) -> Act.of(Cmd.Out):
283 import_run(root, Maybe.default(&2, String, ia.get_file(a), "(data)"), ia.get_fmt(a), ia.get_data(a), ia.get_replace(a), noin, json, only)
284
285def ia.err_of(+a: IA) -> String:
286 match a:
287 case IA{file, fmt, replace, err, w, data}:
288 err
289
290def import_ok(ok: Bool, +err: String, +a: IA, +root: String, noin: Bool, json: Bool, only: Bool) -> IO(Cmd.Out):
291 match ok:
292 case True{}:
293 IO.bind(Fs.Res(Cmd.Out), Cmd.Out, import_act(root, a, noin, json, only), r => IO.pure(Cmd.Out, Setcmd.fin(r)))
294 case False{}:
295 IO.pure(Cmd.Out, Cmd.Out{2, "", "cswap: error: " ++ err ++ "\n"})
296
297
298
299def import_gate(+a: IA, +root: String, noin: Bool, json: Bool, only: Bool) -> IO(Cmd.Out):
300 import_ok(String.is_empty(ia.err_of(a)), ia.err_of(a), a, root, noin, json, only)
301
302def import_cmd(+root: String, args: List<&2, String>, noin: Bool, json: Bool, only: Bool) -> IO(Cmd.Out):
303 import_gate(ia.parse(args), root, noin, json, only)
304
305def config_export(+root: String) -> IO(Cmd.Out):
306 IO.bind(Json.Json, Cmd.Out, Setload.load_raw(root), raw => IO.pure(Cmd.Out, Cmd.doc(raw)))
307
308def with_active(m: Maybe<&2, String>, +doc: Json.Json) -> Json.Json:
309 match m:
310 case Some{n}:
311 Json.set("active_profile", Json.JStr{n}, doc)
312 case None{}:
313 doc
314
315def all_profiles(+raw: Json.Json) -> Json.Json:
316 with_active(P.active(raw), Json.of_pairs([("profiles", P.obj_of(Json.get("profiles", raw)))]))
317
318def one_profile(stored: Bool, +raw: Json.Json, +name: String) -> Cmd.Out:
319 match stored:
320 case True{}:
321 Cmd.doc(Json.of_pairs([("profiles", Json.of_pairs([(name, P.obj_of(Json.get(name, P.obj_of(Json.get("profiles", raw)))))]))]))
322 case False{}:
323 Cmd.fail(Profcmd.gone_missing(P.is_builtin(name), name))
324
325def profile_export_out(m: Maybe<&2, String>, +raw: Json.Json) -> Cmd.Out:
326 match m:
327 case None{}:
328 Cmd.doc(all_profiles(raw))
329 case Some{+name}:
330 one_profile(P.has_stored(raw, name), raw, name)
331
332def profile_export(+root: String, n: Maybe<&2, String>) -> IO(Cmd.Out):
333 IO.bind(Json.Json, Cmd.Out, Setload.load_raw(root), raw => IO.pure(Cmd.Out, profile_export_out(n, raw)))
334
335
336
337def usage(+verb: String, +msg: String) -> Cmd.Out:
338 Cmd.Out{2, "", "cswap " ++ verb ++ ": error: " ++ msg ++ "\n"}
339
340def export_args(+verb: String, rest: List<&2, String>, run: IO(Cmd.Out)) -> IO(Cmd.Out):
341 match rest:
342 case Nil{}:
343 run
344 case Con{h, t}:
345 IO.pure(Cmd.Out, usage(verb, "unrecognized arguments: " ++ String.join(Con{h, t}, " ")))
346
347def profile_name_args(+root: String, rest: List<&2, String>) -> IO(Cmd.Out):
348 match rest:
349 case Nil{}:
350 profile_export(root, None{})
351 case Con{n, Nil{}}:
352 profile_export(root, Some{n})
353 case Con{n, Con{x, t}}:
354 IO.pure(Cmd.Out, usage("profile", "unrecognized arguments: " ++ String.join(Con{x, t}, " ")))
355
356def config2(+root: String, +word: String, +rest: List<&2, String>, args: List<&2, String>, +json: Bool, noin: Bool) -> IO(Cmd.Out):
357 Bool.pick(IO(Cmd.Out), String.eq(word, "export"), export_args("config", rest, config_export(root)),
358 Bool.pick(IO(Cmd.Out), String.eq(word, "import"), import_cmd(root, rest, noin, json, False{}), Setcmd.config(root, Clibase.affine(args), json)))
359
360
361def config(+root: String, args: List<&2, String>, json: Bool, noin: Bool) -> IO(Cmd.Out):
362 match args:
363 case Nil{}:
364 Setcmd.config(root, Clibase.affine(args), json)
365 case Con{+h, +t}:
366 config2(root, h, t, Con{h, t}, json, noin)
367
368def profile2(+root: String, +word: String, +rest: List<&2, String>, args: List<&2, String>, +json: Bool, noin: Bool) -> IO(Cmd.Out):
369 Bool.pick(IO(Cmd.Out), String.eq(word, "export"), profile_name_args(root, rest),
370 Bool.pick(IO(Cmd.Out), String.eq(word, "import"), import_cmd(root, rest, noin, json, True{}), Profcmd.profile(root, Clibase.affine(args), json)))
371
372
373def profile(+root: String, args: List<&2, String>, json: Bool, noin: Bool) -> IO(Cmd.Out):
374 match args:
375 case Nil{}:
376 Profcmd.profile(root, Clibase.affine(args), json)
377 case Con{+h, +t}:
378 profile2(root, h, t, Con{h, t}, json, noin)