setwrite.bend
cswap-bend-cli@0.1.0.0 · 289 lines · 47 definitions
1import Base
2import cswap-bend-cli@0.1.0.0/setload.bend as Setload
3import cswap-bend-cli@0.1.0.0/fs/write.bend as FWrite
4import cswap-bend-cli@0.1.0.0/json.bend as Json
5import cswap-bend-cli@0.1.0.0/fs.bend as Fs
6import cswap-bend-cli@0.1.0.0/act.bend as Act
7import cswap-bend-cli@0.1.0.0/cmd.bend as Cmd
8import cswap-bend-cli@0.1.0.0/settings.bend as Settings
9
10type Parsed is Data:
11 Bad{msg: String}
12 Good{value: Json.Json}
13
14def strip_sign(+t: String) -> String:
15 Settings.sel.s(String.starts_with(t, "-") || String.starts_with(t, "+"), String.drop(t, 1n), t)
16
17def special.w(+w: String) -> Bool:
18 String.eq(w, "inf") || String.eq(w, "infinity") || String.eq(w, "nan")
19
20
21def special(t: String) -> Bool:
22 special.w(String.to_lower(strip_sign(t)))
23
24def int.pick(ok: Bool, t: String) -> Maybe<&2, Settings.Dec>:
25 match ok:
26 case True{}:
27 Settings.num(t)
28 case False{}:
29 None{}
30
31
32def int.cand(+t: String) -> Maybe<&2, Settings.Dec>:
33 int.pick(Settings.all_digits(strip_sign(t)) && Bool.not(String.is_empty(strip_sign(t))), t)
34
35def cand.pick(isint: Bool, t: String) -> Maybe<&2, Settings.Dec>:
36 match isint:
37 case True{}:
38 int.cand(t)
39 case False{}:
40 Settings.num(t)
41
42def range_msg(dot: String, lo: String, hi: String) -> String:
43 dot ++ " must be between " ++ lo ++ " and " ++ hi
44
45def parse.chk(bad: Bool, isint: Bool, msg: String, d: Settings.Dec) -> Parsed:
46 match bad:
47 case True{}:
48 Bad{msg}
49 case False{}:
50 Good{Setload.norm.show(isint, d)}
51
52def parse.dec(m: Maybe<&2, Settings.Dec>, isint: Bool, +dot: String, raw: String, +lo: String, +hi: String) -> Parsed:
53 match m:
54 case Some{+d}:
55 parse.chk(Settings.below(d, Settings.lit(lo)) || Settings.above(d, Settings.lit(hi)), isint, range_msg(dot, lo, hi), d)
56 case None{}:
57 Bad{dot ++ " expects " ++ Settings.sel.s(isint, "an integer", "a number") ++ ", got '" ++ raw ++ "'"}
58
59def parse.number2(inf: Bool, +isint: Bool, +dot: String, raw: String, +lo: String, +hi: String, t: String) -> Parsed:
60 match inf:
61 case True{}:
62 Bad{range_msg(dot, lo, hi)}
63 case False{}:
64 parse.dec(cand.pick(isint, t), isint, dot, raw, lo, hi)
65
66def parse.number(+isint: Bool, dot: String, +raw: String, lo: String, hi: String) -> Parsed:
67 parse.number2(Bool.not(isint) && special(String.trim(raw)), isint, dot, raw, lo, hi, String.trim(raw))
68
69def bool.word2(t: Bool, f: Bool) -> Maybe<&2, Bool>:
70 match t f:
71 case True{} _:
72 Some{True{}}
73 case False{} True{}:
74 Some{False{}}
75 case False{} False{}:
76 None{}
77
78def bool.word(+w: String) -> Maybe<&2, Bool>:
79 bool.word2(String.eq(w, "true") || String.eq(w, "1") || String.eq(w, "yes"),
80 String.eq(w, "false") || String.eq(w, "0") || String.eq(w, "no"))
81
82def parse.bool2(m: Maybe<&2, Bool>, dot: String, raw: String) -> Parsed:
83 match m:
84 case Some{b}:
85 Good{Json.JBool{b}}
86 case None{}:
87 Bad{dot ++ " expects true or false (or 1/0, yes/no), got '" ++ raw ++ "'"}
88
89def parse.choice2(ok: Bool, dot: String, choices: String, raw: String) -> Parsed:
90 match ok:
91 case True{}:
92 Good{Json.JStr{raw}}
93 case False{}:
94 Bad{dot ++ " must be one of: " ++ String.join(String.split(choices, ','), ", ")}
95
96def parse.str2(Setload.empty: Bool, +dot: String, v: String) -> Parsed:
97 match Setload.empty:
98 case True{}:
99 Bad{dot ++ " expects a non-empty value; use 'cswap config unset " ++ dot ++ "' to clear it"}
100 case False{}:
101 Good{Json.JStr{v}}
102
103def parse.kind(kind: Settings.Sort, +dot: String, +raw: String, lo: String, hi: String, +choices: String) -> Parsed:
104 match kind:
105 case Settings.KFloat{}:
106 parse.number(False{}, dot, raw, lo, hi)
107 case Settings.KInt{}:
108 parse.number(True{}, dot, raw, lo, hi)
109 case Settings.KBool{}:
110 parse.bool2(bool.word(String.to_lower(String.trim(raw))), dot, raw)
111 case Settings.KChoice{}:
112 parse.choice2(Setload.has_str(String.split(choices, ','), raw), dot, choices, raw)
113 case Settings.KString{}:
114 parse.str2(String.is_empty(String.trim(raw)), dot, String.trim(raw))
115
116
117
118def parse_value(sp: Settings.Spec, raw: String) -> Parsed:
119 match sp:
120 case Settings.Spec{section, key, kind, lo, hi, choices, dflt}:
121 parse.kind(kind, section ++ "." ++ key, raw, lo, hi, choices)
122
123def is_float.kind(k: Settings.Sort) -> Bool:
124 match k:
125 case Settings.KFloat{}:
126 True{}
127 case _:
128 False{}
129
130def is_float(sp: Settings.Spec) -> Bool:
131 match sp:
132 case Settings.Spec{section, key, kind, lo, hi, choices, dflt}:
133 is_float.kind(kind)
134
135def has.in(sec: Maybe<&2, Json.Json>, key: String) -> Bool:
136 match sec:
137 case Some{j}:
138 Maybe.is_some(&2, Json.Json, Json.get(key, j))
139 case None{}:
140 False{}
141
142
143def is_set(raw: Json.Json, sp: Settings.Spec) -> Bool:
144 match sp:
145 case Settings.Spec{section, key, kind, lo, hi, choices, dflt}:
146 has.in(Json.get(section, raw), key)
147
148
149def set_in(+raw: Json.Json, sp: Settings.Spec, v: Json.Json) -> Json.Json:
150 match sp:
151 case Settings.Spec{+section, key, kind, lo, hi, choices, dflt}:
152 Json.set(section, Json.set(key, v, Setload.section_of(raw, section)), Setload.with_version(raw))
153
154def obj_empty(j: Json.Json) -> Bool:
155 match j:
156 case Json.JObj{Json.JNil{}}:
157 True{}
158 case _:
159 False{}
160
161def unset.fin(gone: Bool, +section: String, +sect: Json.Json, r2: Json.Json) -> Json.Json:
162 match gone:
163 case True{}:
164 Json.del(section, r2)
165 case False{}:
166 Json.set(section, sect, r2)
167
168def unset.fin2(+sect: Json.Json, +section: String, r2: Json.Json) -> Json.Json:
169 unset.fin(obj_empty(sect), section, sect, r2)
170
171
172def unset_in(+raw: Json.Json, sp: Settings.Spec) -> Json.Json:
173 match sp:
174 case Settings.Spec{+section, +key, kind, lo, hi, choices, dflt}:
175 unset.fin2(Json.del(key, Setload.section_of(raw, section)), section, Setload.with_version(raw))
176
177type Row is Data:
178 Row{sp: Settings.Spec, value: Json.Json, set: Bool}
179
180def value_of(+norm: Json.Json, sp: Settings.Spec) -> Json.Json:
181 match sp:
182 case Settings.Spec{+section, key, kind, lo, hi, choices, dflt}:
183 Setload.get_or_null(key, Setload.get_or_null(section, norm))
184
185def rows(+raw: Json.Json, +norm: Json.Json, xs: List<&2, Settings.Spec>) -> List<&2, Row>:
186 match xs:
187 case Nil{}:
188 []
189 case Con{+h, t}:
190 Row{h, value_of(norm, h), is_set(raw, h)} <> rows(raw, norm, t)
191
192
193def effective(+raw: Json.Json) -> List<&2, Row>:
194 rows(raw, Setload.normalize(raw), Settings.specs())
195
196def fmt.num(isfloat: Bool, +t: String) -> String:
197 Settings.sel.s(isfloat, Settings.fmt(Settings.lit(t)), t)
198
199
200def fmt.val(isfloat: Bool, j: Json.Json) -> String:
201 match j:
202 case Json.JNull{}:
203 "(none)"
204 case Json.JBool{b}:
205 Settings.sel.s(b, "true", "false")
206 case Json.JNum{t}:
207 fmt.num(isfloat, t)
208 case Json.JStr{s}:
209 s
210 case _:
211 ""
212
213def row.key(r: Row) -> String:
214 match r:
215 case Row{sp, value, set}:
216 Settings.dotted(sp)
217
218def row.val(r: Row) -> String:
219 match r:
220 case Row{+sp, value, set}:
221 fmt.val(is_float(sp), value)
222
223def row.json(r: Row) -> Json.Json:
224 match r:
225 case Row{sp, value, set}:
226 Json.of_pairs([("key", Json.JStr{Settings.dotted(sp)}), ("value", value), ("isSet", Json.JBool{set})])
227
228def rows.json(rs: List<&2, Row>) -> List<&2, Json.Json>:
229 match rs:
230 case Nil{}:
231 []
232 case Con{+h, t}:
233 row.json(h) <> rows.json(t)
234
235def row.find.pick(hit: Bool, r: Row, rest: Maybe<&2, Row>) -> Maybe<&2, Row>:
236 match hit:
237 case True{}:
238 Some{r}
239 case False{}:
240 rest
241
242def row.find(+key: String, rs: List<&2, Row>) -> Maybe<&2, Row>:
243 match rs:
244 case Nil{}:
245 None{}
246 case Con{+h, t}:
247 row.find.pick(String.eq(row.key(h), key), h, row.find(key, t))
248
249def keys.of(rs: List<&2, Row>) -> List<&2, String>:
250 match rs:
251 case Nil{}:
252 []
253 case Con{+h, t}:
254 row.key(h) <> keys.of(t)
255
256def vals.of(rs: List<&2, Row>) -> List<&2, String>:
257 match rs:
258 case Nil{}:
259 []
260 case Con{+h, t}:
261 row.val(h) <> vals.of(t)
262
263def width(xs: List<&2, String>, acc: Nat) -> Nat:
264 match xs:
265 case Nil{}:
266 acc
267 case Con{h, t}:
268 width(t, Nat.max(acc, String.length(h)))
269
270def pad(+s: String, n: Nat) -> String:
271 s ++ String.repeat(" ", Nat.sub(n, String.length(s)))
272
273def line(kw: Nat, vw: Nat, +r: Row) -> String:
274 match r:
275 case Row{sp, value, +set}:
276 pad(row.key(Row{sp, value, set}), kw) ++ " " ++ pad(row.val(Row{sp, value, set}), vw) ++ Settings.sel.s(set, "", " (default)")
277
278def lines(+kw: Nat, +vw: Nat, rs: List<&2, Row>) -> List<&2, String>:
279 match rs:
280 case Nil{}:
281 []
282 case Con{h, t}:
283 line(kw, vw, h) <> lines(kw, vw, t)
284
285
286
287def list.text(+rs: List<&2, Row>) -> String:
288 String.join(lines(width(keys.of(rs), 0n), width(vals.of(rs), 0n), rs), "\n")
289