domain/architecture/system/deployment/nix_darwin/setting/ops.bend
near-v-framework@0.4.0.0 · 697 lines · 120 definitions
1import Base
2import bend-net-url@0.4.0.0/url.bend as Url
3import near-v-framework@0.4.0.0/domain/architecture/system/plan/type.bend as P
4import near-v-framework@0.4.0.0/domain/architecture/system/deployment/nix_darwin/brew/type.bend as Br
5import near-v-framework@0.4.0.0/domain/architecture/system/deployment/nix_darwin/brew/ops.bend as BrO
6import near-v-framework@0.4.0.0/domain/architecture/system/deployment/nix_darwin/setting/type.bend as St
7
8
9def literal(k: St.ValueKind, v: String) -> String:
10 match k:
11 case St.KStr{}:
12 "\"" ++ v ++ "\""
13 case _:
14 v
15
16def line(s: St.Setting) -> String:
17 match s:
18 case St.Setting{o, k, v}:
19 " " ++ o ++ " = " ++ literal(k, v) ++ ";\n"
20
21def lines(xs: List<&2, St.Setting>) -> String:
22 match xs:
23 case []:
24 ""
25 case s <> rest:
26 line(s) ++ lines(rest)
27
28def nix_bool(b: Bool) -> String:
29 Bool.pick(String, b, "true", "false")
30
31def firewall_nix(f: St.Firewall) -> String:
32 match f:
33 case St.FirewallOff{}:
34 " networking.applicationFirewall.enable = false;\n"
35 case St.FirewallOn{a, b, c, d}:
36 " networking.applicationFirewall.enable = true;\n" ++
37 " networking.applicationFirewall.allowSigned = " ++ nix_bool(a) ++ ";\n" ++
38 " networking.applicationFirewall.allowSignedApp = " ++ nix_bool(b) ++ ";\n" ++
39 " networking.applicationFirewall.blockAllIncoming = " ++ nix_bool(c) ++ ";\n" ++
40 " networking.applicationFirewall.enableStealthMode = " ++ nix_bool(d) ++ ";\n"
41
42
43
44def mac_nix(settings: List<&2, St.Setting>, fw: St.Firewall, brews: List<&2, Br.Brew>) -> String:
45 "# Generated by V from the system's nix-darwin settings. Do not edit.\n{ ... }: {\n" ++
46 lines(settings) ++
47 firewall_nix(fw) ++
48 " homebrew.enable = true;\n homebrew.onActivation.cleanup = \"uninstall\";\n homebrew.onActivation.autoUpdate = false;\n homebrew.onActivation.upgrade = false;\n" ++
49 BrO.nix_section(brews) ++ "}\n"
50
51def option_of(s: St.Setting) -> String:
52 match s:
53 case St.Setting{o, _, _}:
54 o
55
56def none_prefixed(xs: List<&2, St.Setting>, +p: String) -> Bool:
57 match xs:
58 case []:
59 True{}
60 case s <> rest:
61 Bool.not(String.starts_with(option_of(s), p)) && none_prefixed(rest, p)
62
63
64def firewall_typed(settings: List<&2, St.Setting>) -> Bool:
65 none_prefixed(settings, "networking.applicationFirewall")
66
67
68def value_if(hit: Bool, s: St.Setting, other: String) -> String:
69 match s:
70 case St.Setting{_, _, v}:
71 Bool.pick(String, hit, v, other)
72
73
74def lookup(+option: String, now: List<&2, St.Setting>) -> String:
75 match now:
76 case []:
77 "(unset)"
78 case +s <> rest:
79 value_if(String.eq(option, option_of(s)), s, lookup(option, rest))
80
81def drop_zeros(cs: List<&2, Char>) -> List<&2, Char>:
82 match cs:
83 case []:
84 []
85 case +c <> +rest:
86 Bool.pick(List<&2, Char>, Char.is_eq(c, '0'), drop_zeros(rest), c <> rest)
87
88
89def trim_num(+s: String) -> String:
90 +t = String.reverse(String.from_list(drop_zeros(String.to_list(String.reverse(s)))))
91 Bool.pick(String, String.contains(s, "."), Bool.pick(String, String.ends_with(t, "."), String.take(t, Nat.sub(String.length(t), 1n)), t), s)
92
93def same(+now: String, +want: String) -> Bool:
94 String.eq(now, want) || String.eq(trim_num(now), trim_num(want))
95
96def change_if(+now: String, +option: String, +want: String) -> List<&2, P.Step>:
97 Bool.pick(List<&2, P.Step>, same(now, want), [], [P.Change{option, now, want}])
98
99def diff_one(s: St.Setting, +now: List<&2, St.Setting>) -> List<&2, P.Step>:
100 match s:
101 case St.Setting{+o, _, v}:
102 change_if(lookup(o, now), o, v)
103
104
105def diff(want: List<&2, St.Setting>, +now: List<&2, St.Setting>) -> List<&2, P.Step>:
106 match want:
107 case []:
108 []
109 case s <> rest:
110 List.append(&2, P.Step, diff_one(s, now), diff(rest, now))
111
112def firewall_show(f: St.Firewall) -> String:
113 match f:
114 case St.FirewallOff{}:
115 "off"
116 case St.FirewallOn{a, b, c, d}:
117 "on (allowSigned " ++ nix_bool(a) ++ ", allowSignedApp " ++ nix_bool(b) ++ ", blockAllIncoming " ++ nix_bool(c) ++ ", stealth " ++ nix_bool(d) ++ ")"
118
119def firewall_change(want: St.Firewall, now: St.Firewall) -> List<&2, P.Step>:
120 +w = firewall_show(want)
121 +n = firewall_show(now)
122 Bool.pick(List<&2, P.Step>, String.eq(w, n), [], [P.Change{"networking.applicationFirewall", n, w}])
123
124
125def keep_if(keep: Bool, x: String, rest: List<&2, String>) -> List<&2, String>:
126 match keep:
127 case True{}:
128 x <> rest
129 case False{}:
130 rest
131
132def blank(cs: List<&2, Char>) -> List<&2, Char>:
133 match cs:
134 case []:
135 []
136 case +c <> rest:
137 Bool.pick(Char, Char.is_space(c), ' ', c) <> blank(rest)
138
139def words(s: String) -> List<&2, String>:
140 List.filter(~String, ~(w => Bool.not(String.is_empty(w))), String.split(String.from_list(blank(String.to_list(s))), ' '))
141
142def word(+s: String, n: Nat) -> String:
143 Maybe.default(&2, String, List.get(&2, String, words(s), n), "")
144
145def last_word(+s: String) -> String:
146 Maybe.default(&2, String, List.last(&2, String, words(s)), "")
147
148def filled_go(ls: List<&2, String>) -> List<&2, String>:
149 match ls:
150 case []:
151 []
152 case l <> rest:
153 +t = String.trim(l)
154 keep_if(Bool.not(String.is_empty(t)), t, filled_go(rest))
155
156
157def filled(s: String) -> List<&2, String>:
158 filled_go(String.lines(s))
159
160def distinct(xs: List<&2, String>) -> List<&2, String>:
161 match xs:
162 case []:
163 []
164 case +x <> +rest:
165 keep_if(Bool.not(List.contains(~String, ~String.eq, rest, x)), x, distinct(rest))
166
167def digits_of(cs: List<&2, Char>) -> String:
168 match cs:
169 case []:
170 ""
171 case +c <> rest:
172 Bool.pick(String, Char.is_digit(c), String.from_list([c]) ++ digits_of(rest), "")
173
174def digits(s: String) -> String:
175 digits_of(String.to_list(s))
176
177def nat_of(+s: String) -> Nat:
178 Maybe.default(&2, Nat, Nat.read(digits(String.trim(s))), 0n)
179
180def unquote(+s: String) -> String:
181 Bool.pick(String, String.starts_with(s, "\"") && String.ends_with(s, "\"") && Nat.is_gt(String.length(s), 1n),
182 String.take(String.drop(s, 1n), Nat.sub(String.length(s), 2n)), s)
183
184def nix_strs(xs: List<&2, String>) -> String:
185 match xs:
186 case []:
187 ""
188 case x <> rest:
189 " " ++ BrO.nix_str(x) ++ nix_strs(rest)
190
191
192def nix_list(xs: List<&2, String>) -> String:
193 "[" ++ nix_strs(xs) ++ " ]"
194
195def nix_setting(o: String, v: String) -> St.Setting:
196 St.Setting{o, St.KNix{}, v}
197
198def bool_setting(o: String, b: Bool) -> St.Setting:
199 St.Setting{o, St.KBool{}, nix_bool(b)}
200
201def str_setting(o: String, v: String) -> St.Setting:
202 St.Setting{o, St.KStr{}, BrO.nix_esc(v)}
203
204def int_setting(o: String, v: String) -> St.Setting:
205 St.Setting{o, St.KInt{}, v}
206
207
208def when_filled(+v: String, s: St.Setting) -> List<&2, St.Setting>:
209 Bool.pick(List<&2, St.Setting>, String.is_empty(String.trim(v)), [], [s])
210
211
212
213def scalar_kind(+t: String) -> St.ValueKind:
214 Bool.pick(St.ValueKind, String.eq(t, "boolean"), St.KBool{},
215 Bool.pick(St.ValueKind, String.starts_with(t, "floating point"), St.KFloat{},
216 Bool.pick(St.ValueKind, String.starts_with(t, "signed integer") || String.starts_with(t, "(positive integer") || String.starts_with(t, "integer between") || String.starts_with(t, "one of 0") || String.starts_with(t, "one of 1") || String.starts_with(t, "value 1"), St.KInt{},
217 Bool.pick(St.ValueKind, String.eq(t, "string") || String.eq(t, "absolute path") || String.starts_with(t, "one of \"") || String.starts_with(t, "value \""), St.KStr{}, St.KNix{}))))
218
219def kind_of(+t: String) -> St.ValueKind:
220 Bool.pick(St.ValueKind, String.starts_with(t, "null or "), scalar_kind(String.drop(t, 8n)), St.KNix{})
221
222def option_line(+l: String) -> List<&2, St.Setting>:
223 +cols = String.split(l, '\t')
224 +o = Maybe.default(&2, String, List.head(&2, String, cols), "")
225 Bool.pick(List<&2, St.Setting>, String.is_empty(o), [],
226 [St.Setting{o, kind_of(Maybe.default(&2, String, List.get(&2, String, cols, 1n), "")), ""}])
227
228def option_lines(ls: List<&2, String>) -> List<&2, St.Setting>:
229 match ls:
230 case []:
231 []
232 case l <> rest:
233 List.append(&2, St.Setting, option_line(l), option_lines(rest))
234
235
236def options(tsv: String) -> List<&2, St.Setting>:
237 option_lines(String.lines(tsv))
238
239def after_defaults(+o: String) -> String:
240 Bool.pick(String, String.starts_with(o, "system.defaults."), String.drop(o, 16n), "")
241
242
243def group_of(+o: String) -> String:
244 +r = after_defaults(o)
245 Bool.pick(String, String.starts_with(r, "\""),
246 Maybe.default(&2, String, List.get(&2, String, String.split(r, '"'), 1n), ""),
247 Maybe.default(&2, String, List.head(&2, String, String.split(r, '.')), ""))
248
249def key_of(+o: String) -> String:
250 +r = after_defaults(o)
251 unquote(Bool.pick(String, String.starts_with(r, "\""),
252 String.drop(String.join(List.drop(&2, String, String.split(r, '"'), 2n), "\""), 1n),
253 String.join(List.drop(&2, String, String.split(r, '.'), 1n), ".")))
254
255
256def domain(+g: String) -> String:
257 Bool.pick(String, String.eq(g, ".GlobalPreferences") || String.eq(g, "NSGlobalDomain"), "-g",
258 Bool.pick(String, String.eq(g, "LaunchServices"), "com.apple.LaunchServices",
259 Bool.pick(String, String.eq(g, "menuExtraClock"), "com.apple.menuextra.clock",
260 Bool.pick(String, String.eq(g, "dock"), "com.apple.dock",
261 Bool.pick(String, String.eq(g, "finder"), "com.apple.finder",
262 Bool.pick(String, String.eq(g, "hitoolbox"), "com.apple.HIToolbox",
263 Bool.pick(String, String.eq(g, "iCal"), "com.apple.iCal",
264 Bool.pick(String, String.eq(g, "magicmouse"), "com.apple.AppleMultitouchMouse",
265 Bool.pick(String, String.eq(g, "screencapture"), "com.apple.screencapture",
266 Bool.pick(String, String.eq(g, "screensaver"), "com.apple.screensaver",
267 Bool.pick(String, String.eq(g, "spaces"), "com.apple.spaces",
268 Bool.pick(String, String.eq(g, "trackpad"), "com.apple.AppleMultitouchTrackpad",
269 Bool.pick(String, String.eq(g, "universalaccess"), "com.apple.universalaccess",
270 Bool.pick(String, String.eq(g, "ActivityMonitor"), "com.apple.ActivityMonitor",
271 Bool.pick(String, String.eq(g, "WindowManager"), "com.apple.WindowManager",
272 Bool.pick(String, String.eq(g, "controlcenter"), "-currentHost com.apple.controlcenter",
273 Bool.pick(String, String.eq(g, "loginwindow"), "/Library/Preferences/com.apple.loginwindow",
274 Bool.pick(String, String.eq(g, "smb"), "/Library/Preferences/SystemConfiguration/com.apple.smb.server",
275 Bool.pick(String, String.eq(g, "SoftwareUpdate"), "/Library/Preferences/com.apple.SoftwareUpdate", "")))))))))))))))))))
276
277
278def export_args(+d: String) -> List<&2, String>:
279 Bool.pick(List<&2, String>, String.starts_with(d, "-currentHost "), ["-currentHost", "export", String.drop(d, 13n), "-"], ["export", d, "-"])
280
281def is_nix(k: St.ValueKind) -> Bool:
282 match k:
283 case St.KNix{}:
284 True{}
285 case _:
286 False{}
287
288
289def domain_of(s: St.Setting) -> String:
290 match s:
291 case St.Setting{+o, k, _}:
292 Bool.pick(String, is_nix(k), "", domain(group_of(o)))
293
294def domains_go(xs: List<&2, St.Setting>) -> List<&2, String>:
295 match xs:
296 case []:
297 []
298 case s <> rest:
299 +d = domain_of(s)
300 keep_if(Bool.not(String.is_empty(d)), d, domains_go(rest))
301
302
303def domains(xs: List<&2, St.Setting>) -> List<&2, String>:
304 distinct(domains_go(xs))
305
306
307def text_lines_go(s: String, +cur: List<&2, Char>, +acc: List<&2, String>) -> List<&2, String>:
308 match s:
309 case SNil{}:
310 List.reverse(&2, String, String.from_list(List.reverse(&2, Char, cur)) <> acc)
311 case SCon{+h, t}:
312 +nl = Char.is_eq(h, '\n')
313 text_lines_go(t, Bool.pick(List<&2, Char>, nl, [], h <> cur), Bool.pick(List<&2, String>, nl, String.from_list(List.reverse(&2, Char, cur)) <> acc, acc))
314
315
316def text_lines(s: String) -> List<&2, String>:
317 text_lines_go(s, [], [])
318
319def xml_entity(+seg: String) -> String:
320 Bool.pick(String, String.starts_with(seg, "amp;"), "&" ++ String.drop(seg, 4n),
321 Bool.pick(String, String.starts_with(seg, "lt;"), "<" ++ String.drop(seg, 3n),
322 Bool.pick(String, String.starts_with(seg, "gt;"), ">" ++ String.drop(seg, 3n),
323 Bool.pick(String, String.starts_with(seg, "quot;"), "\"" ++ String.drop(seg, 5n),
324 Bool.pick(String, String.starts_with(seg, "apos;"), "'" ++ String.drop(seg, 5n), "&" ++ seg)))))
325
326def xml_entities(segs: List<&2, String>) -> String:
327 match segs:
328 case []:
329 ""
330 case s <> rest:
331 xml_entity(s) ++ xml_entities(rest)
332
333def xml_unescape(+s: String) -> String:
334 +segs = String.split(s, '&')
335 Maybe.default(&2, String, List.head(&2, String, segs), "") ++ xml_entities(List.drop(&2, String, segs, 1n))
336
337def xml_escape_char(+c: Char) -> String:
338 Bool.pick(String, Char.is_eq(c, '&'), "&", Bool.pick(String, Char.is_eq(c, '<'), "<", Bool.pick(String, Char.is_eq(c, '>'), ">", String.from_list([c]))))
339
340def xml_escape(cs: List<&2, Char>) -> String:
341 match cs:
342 case []:
343 ""
344 case c <> rest:
345 xml_escape_char(c) ++ xml_escape(rest)
346
347
348
349def plist_scalar(+v: String) -> String:
350 +t = String.trim(v)
351 Bool.pick(String, String.starts_with(t, "<true/>"), "1",
352 Bool.pick(String, String.starts_with(t, "<false/>"), "0",
353 Bool.pick(String, String.starts_with(t, "<dict") || String.starts_with(t, "<array") || String.starts_with(t, "<data"), "",
354 xml_unescape(Maybe.default(&2, String, List.head(&2, String, String.split(Maybe.default(&2, String, List.get(&2, String, String.split(t, '>'), 1n), ""), '<')), "")))))
355
356def ends_scalar(+l: String) -> Bool:
357 String.contains(l, "</string>") || String.contains(l, "</integer>") || String.contains(l, "</real>") || String.contains(l, "/>") || String.contains(l, "<dict>") || String.contains(l, "<array>") || String.contains(l, "<data>")
358
359
360def value_lines(ls: List<&2, String>, done: Bool) -> String:
361 match ls done:
362 case [] _:
363 ""
364 case _ True{}:
365 ""
366 case +l <> rest False{}:
367 l ++ "\n" ++ value_lines(rest, ends_scalar(l))
368
369def after_key(ls: List<&2, String>, +target: String, hit: Bool) -> String:
370 match ls hit:
371 case [] _:
372 ""
373 case xs True{}:
374 value_lines(xs, False{})
375 case +l <> rest False{}:
376 after_key(rest, target, String.eq(l, target))
377
378
379def plist_value(+key: String, ls: List<&2, String>) -> String:
380 plist_scalar(after_key(ls, "\t<key>" ++ xml_escape(String.to_list(key)) ++ "</key>", False{}))
381
382
383def normalize_kind(k: St.ValueKind, +v: String) -> String:
384 match k:
385 case St.KBool{}:
386 Bool.pick(String, String.eq(v, "1"), "true", Bool.pick(String, String.eq(v, "0"), "false", ""))
387 case St.KFloat{}:
388 Bool.pick(String, String.contains(v, "."), v, v ++ ".0")
389 case St.KStr{}:
390 BrO.nix_esc(v)
391 case _:
392 v
393
394def normalize(k: St.ValueKind, +v: String) -> String:
395 Bool.pick(String, String.is_empty(v), "", normalize_kind(k, v))
396
397def index_label(+raw: String, labels: List<&2, String>) -> String:
398 Maybe.default(&2, String, List.get(&2, String, labels, Maybe.default(&2, Nat, Nat.read(raw), 99n)), "")
399
400def finder_label(+c: String) -> String:
401 Bool.pick(String, String.eq(c, "PfCm"), "Computer", Bool.pick(String, String.eq(c, "PfVo"), "OS volume",
402 Bool.pick(String, String.eq(c, "PfHm"), "Home", Bool.pick(String, String.eq(c, "PfDe"), "Desktop",
403 Bool.pick(String, String.eq(c, "PfDo"), "Documents", Bool.pick(String, String.eq(c, "PfAF"), "Recents",
404 Bool.pick(String, String.eq(c, "PfID"), "iCloud Drive", Bool.pick(String, String.eq(c, "PfLo"), "Other", ""))))))))
405
406
407def value_for(+g: String, +key: String, k: St.ValueKind, +raw: String) -> String:
408 Bool.pick(String, String.eq(g, "hitoolbox") && String.eq(key, "AppleFnUsageType"), index_label(raw, ["Do Nothing", "Change Input Source", "Show Emoji & Symbols", "Start Dictation"]),
409 Bool.pick(String, String.eq(g, "iCal") && String.eq(key, "first day of week"), index_label(raw, ["System Setting", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]),
410 Bool.pick(String, String.eq(g, "finder") && String.eq(key, "NewWindowTarget"), finder_label(raw),
411 Bool.pick(String, String.eq(g, "controlcenter"), Bool.pick(String, String.eq(raw, "18"), "true", Bool.pick(String, String.eq(raw, "24"), "false", "")),
412 normalize(k, raw)))))
413
414def read_one(s: St.Setting, +ls: List<&2, String>) -> List<&2, St.Setting>:
415 match s:
416 case St.Setting{+o, +k, _}:
417 +v = value_for(group_of(o), key_of(o), k, plist_value(key_of(o), ls))
418 Bool.pick(List<&2, St.Setting>, String.is_empty(v), [], [St.Setting{o, k, v}])
419
420
421def read_domain(xs: List<&2, St.Setting>, +d: String, +ls: List<&2, String>) -> List<&2, St.Setting>:
422 match xs:
423 case []:
424 []
425 case +s <> rest:
426 List.append(&2, St.Setting, Bool.pick(List<&2, St.Setting>, String.eq(domain_of(s), d), read_one(s, ls), []), read_domain(rest, d, ls))
427
428
429def pm_hit(+ws: List<&2, String>, +key: String) -> String:
430 Bool.pick(String, Nat.is_eq(List.length(&2, String, ws), 2n) && String.eq(Maybe.default(&2, String, List.head(&2, String, ws), ""), key),
431 Maybe.default(&2, String, List.get(&2, String, ws, 1n), ""), "")
432
433def pm_get(ls: List<&2, String>, +key: String) -> String:
434 match ls:
435 case []:
436 ""
437 case l <> rest:
438 +hit = pm_hit(words(l), key)
439 Bool.pick(String, String.is_empty(hit), pm_get(rest, key), hit)
440
441def minutes(+v: String) -> String:
442 Bool.pick(String, String.eq(v, "0"), "\"never\"", v)
443
444def power_settings(+pm: String) -> List<&2, St.Setting>:
445 +ls = String.lines(pm)
446 List.concat(&2, St.Setting, [
447 when_filled(pm_get(ls, "sleep"), int_setting("power.sleep.computer", minutes(pm_get(ls, "sleep")))),
448 when_filled(pm_get(ls, "displaysleep"), int_setting("power.sleep.display", minutes(pm_get(ls, "displaysleep")))),
449 when_filled(pm_get(ls, "disksleep"), int_setting("power.sleep.harddisk", minutes(pm_get(ls, "disksleep")))),
450 when_filled(pm_get(ls, "autorestart"), bool_setting("power.restartAfterPowerFailure", String.eq(pm_get(ls, "autorestart"), "1"))),
451 when_filled(pm_get(ls, "womp"), bool_setting("networking.wakeOnLan.enable", String.eq(pm_get(ls, "womp"), "1")))])
452
453def button_line(ls: List<&2, String>) -> String:
454 match ls:
455 case []:
456 ""
457 case +l <> rest:
458 Bool.pick(String, String.contains(l, "Sleep On Power Button"), last_word(l), button_line(rest))
459
460
461def button_setting(+pm: String) -> List<&2, St.Setting>:
462 +b = button_line(String.lines(pm))
463 when_filled(b, bool_setting("power.sleep.allowSleepByPowerButton", String.eq(b, "1")))
464
465
466def firewall_on(on: Bool, +f: String) -> St.Firewall:
467 match on:
468 case True{}:
469 St.FirewallOn{String.contains(f, "built-in signed software ENABLED"), String.contains(f, "downloaded signed software ENABLED"),
470 String.contains(f, "block all state set to enabled"), String.contains(f, "stealth mode is on")}
471 case False{}:
472 St.FirewallOff{}
473
474def firewall_of(+f: String) -> St.Firewall:
475 firewall_on(String.contains(f, "Firewall is enabled"), f)
476
477
478
479def url_path(+u: String) -> String:
480 +a = Bool.pick(String, String.starts_with(u, "file://"), String.drop(u, 7n), u)
481 +b = Bool.pick(String, String.ends_with(a, "/") && Nat.is_gt(String.length(a), 1n), String.take(a, Nat.sub(String.length(a), 1n)), a)
482 Maybe.default(&2, String, Url.pct.decode(b), b)
483
484def arrangement(+n: String) -> String:
485 Bool.pick(String, String.eq(n, "2"), "date-added", Bool.pick(String, String.eq(n, "3"), "date-modified",
486 Bool.pick(String, String.eq(n, "4"), "date-created", Bool.pick(String, String.eq(n, "5"), "kind", "name"))))
487
488def displayas(+n: String) -> String:
489 Bool.pick(String, String.eq(n, "1"), "folder", "stack")
490
491def showas(+n: String) -> String:
492 Bool.pick(String, String.eq(n, "1"), "fan", Bool.pick(String, String.eq(n, "2"), "grid", Bool.pick(String, String.eq(n, "3"), "list", "automatic")))
493
494def app_tile(+t: String, +p: String) -> String:
495 Bool.pick(String, String.eq(t, "spacer-tile"), "{ spacer = { small = false; }; }",
496 Bool.pick(String, String.eq(t, "small-spacer-tile"), "{ spacer = { small = true; }; }",
497 Bool.pick(String, String.eq(t, "directory-tile"), "{ folder = " ++ BrO.nix_str(p) ++ "; }",
498 Bool.pick(String, String.ends_with(p, ".app"), "{ app = " ++ BrO.nix_str(p) ++ "; }", "{ file = " ++ BrO.nix_str(p) ++ "; }"))))
499
500
501def tile_nix(+key: String, +t: String, +url: String, +arr: String, +disp: String, +show: String) -> String:
502 Bool.pick(String, String.eq(key, "persistent-apps"), app_tile(t, url_path(url)),
503 Bool.pick(String, String.eq(t, "directory-tile"),
504 "{ folder = { path = " ++ BrO.nix_str(url_path(url)) ++ "; arrangement = \"" ++ arrangement(arr) ++ "\"; displayas = \"" ++ displayas(disp) ++ "\"; showas = \"" ++ showas(show) ++ "\"; }; }",
505 "{ file = " ++ BrO.nix_str(url_path(url)) ++ "; }"))
506
507def spaced(xs: List<&2, String>) -> String:
508 match xs:
509 case []:
510 ""
511 case x <> rest:
512 " " ++ x ++ spaced(rest)
513
514def dock_setting(+key: String, +count: String, tiles: List<&2, String>) -> List<&2, St.Setting>:
515 when_filled(count, nix_setting("system.defaults.dock." ++ key, "[" ++ spaced(tiles) ++ " ]"))
516
517
518
519
520def services(+out: String) -> List<&2, String>:
521 List.filter(~String, ~(s => Bool.not(String.starts_with(s, "*"))), List.drop(&2, String, filled(out), 1n))
522
523
524def per_service(+out: String) -> String:
525 Bool.pick(String, String.contains(out, "aren't any"), "", String.join(words(out), " "))
526
527def same_for_all(+what: String, +outs: List<&2, String>) -> String:
528 Bool.pick(String, Nat.is_gt(List.length(&2, String, distinct(outs)), 1n), "throw \"V: " ++ what ++ " differ per network service\"",
529 nix_list(words(Maybe.default(&2, String, List.head(&2, String, outs), ""))))
530
531
532def is_nix_link(+target: String) -> Bool:
533 String.starts_with(String.trim(target), "/etc/static/")
534
535def sudo_keep(+l: String) -> Bool:
536 Bool.not(String.is_empty(String.trim(l)) || String.starts_with(l, "#") || String.contains(l, "env_keep+=TERMINFO"))
537
538def sudo_line(l: String) -> Bool:
539 sudo_keep(l)
540
541
542def sudo_extra(+sudo: String) -> String:
543 String.join(List.filter(~String, ~sudo_line, String.lines(sudo)), "\n")
544
545
546
547def after_word(ws: List<&2, String>, +w: String) -> List<&2, String>:
548 match ws:
549 case []:
550 []
551 case +x <> +rest:
552 List.append(&2, String, Bool.pick(List<&2, String>, String.eq(x, w), [digits(Maybe.default(&2, String, List.get(&2, String, rest, 1n), ""))], []), after_word(rest, w))
553
554def pairs(src: List<&2, String>, dst: List<&2, String>) -> String:
555 match src:
556 case []:
557 ""
558 case s <> srest:
559 match dst:
560 case []:
561 ""
562 case d <> drest:
563 " { HIDKeyboardModifierMappingSrc = " ++ s ++ "; HIDKeyboardModifierMappingDst = " ++ d ++ "; }" ++ pairs(srest, drest)
564
565def keyboard_settings(+km: String) -> List<&2, St.Setting>:
566 +none = String.eq(String.trim(km), "(null)") || String.eq(String.trim(km), "(\n)") || String.is_empty(String.trim(km))
567 [bool_setting("system.keyboard.enableKeyMapping", Bool.not(none)),
568 nix_setting("system.keyboard.userKeyMapping", "[" ++ pairs(after_word(words(km), "HIDKeyboardModifierMappingSrc"), after_word(words(km), "HIDKeyboardModifierMappingDst")) ++ " ]"),
569 bool_setting("system.keyboard.remapCapsLockToControl", False{}),
570 bool_setting("system.keyboard.remapCapsLockToEscape", False{}),
571 bool_setting("system.keyboard.swapCapsLockAndEscape", False{}),
572 bool_setting("system.keyboard.swapLeftCommandAndLeftAlt", False{}),
573 bool_setting("system.keyboard.swapLeftCtrlAndFn", False{}),
574 bool_setting("system.keyboard.swapRightCommandAndRightOption", False{}),
575 bool_setting("system.keyboard.nonUS.remapTilde", False{})]
576
577
578def after_segment(segs: List<&2, String>, +s: String) -> String:
579 match segs:
580 case []:
581 ""
582 case +x <> +rest:
583 Bool.pick(String, String.eq(x, s), String.join(rest, "/"), after_segment(rest, s))
584
585
586def time_zone(+link: String) -> String:
587 after_segment(String.split(String.trim(link), '/'), "zoneinfo")
588
589def extra_shell_keep(+l: String) -> Bool:
590 Bool.not(String.starts_with(l, "#") || List.contains(~String, ~String.eq, ["/bin/bash", "/bin/csh", "/bin/dash", "/bin/ksh", "/bin/sh", "/bin/tcsh", "/bin/zsh"], l))
591
592def extra_shell(l: String) -> Bool:
593 extra_shell_keep(l)
594
595
596def extra_shells(+shells: String) -> List<&2, String>:
597 List.filter(~String, ~extra_shell, filled(shells))
598
599
600def chime_setting(+mute: String) -> List<&2, St.Setting>:
601 when_filled(mute, bool_setting("system.startup.chime", Bool.not(String.eq(word(mute, 1n), "%01"))))
602
603def fonts_setting(fonts: List<&2, String>) -> St.Setting:
604 nix_setting("fonts.packages", Bool.pick(String, List.is_empty(&2, String, fonts), "[ ]", "throw \"V: /Library/Fonts/Nix Fonts has fonts from no declared package\""))
605
606
607
608def account(+l: String) -> List<&2, String>:
609 +n = word(l, 0n)
610 Bool.pick(List<&2, String>, Nat.is_ge(nat_of(word(l, 1n)), 500n) && Bool.not(String.starts_with(n, "_")) && Bool.not(String.is_empty(n)), [n], [])
611
612def accounts_go(ls: List<&2, String>) -> List<&2, String>:
613 match ls:
614 case []:
615 []
616 case l <> rest:
617 List.append(&2, String, account(l), accounts_go(rest))
618
619def accounts(+out: String) -> List<&2, String>:
620 accounts_go(String.lines(out))
621
622
623def dscl_value(+attr: String, +out: String) -> String:
624 +t = String.trim(out)
625 String.trim(String.join(String.lines(Bool.pick(String, String.starts_with(t, attr ++ ":"), String.drop(t, Nat.add(String.length(attr), 1n)), t)), " "))
626
627def user_settings(+n: String, +uid: String, +gid: String, +dir: String, +real: String, +shell: String, +hidden: String) -> List<&2, St.Setting>:
628 +u = "users.users.\"" ++ n ++ "\"."
629 [int_setting(u ++ "uid", uid), int_setting(u ++ "gid", gid), str_setting(u ++ "home", dir), str_setting(u ++ "description", real),
630 nix_setting(u ++ "shell", shell), bool_setting(u ++ "isHidden", String.eq(hidden, "1"))]
631
632def group_settings(+n: String, +gid: String, +real: String, +members: String) -> List<&2, St.Setting>:
633 +g = "users.groups.\"" ++ n ++ "\"."
634 [int_setting(g ++ "gid", gid), str_setting(g ++ "description", real), nix_setting(g ++ "members", nix_list(words(members)))]
635
636
637
638def plist_of(labels: List<&2, String>, +f: String) -> Bool:
639 match labels:
640 case []:
641 False{}
642 case l <> rest:
643 String.eq(l ++ ".plist", f) || plist_of(rest, f)
644
645def launch_kept(+f: String, +labels: List<&2, String>) -> Bool:
646 Bool.not(String.starts_with(f, ".") || String.starts_with(f, "org.nixos.") || String.starts_with(f, "systems.determinate.") || plist_of(labels, f))
647
648
649def launch_setting(+opt: String, +f: String, +sum: String) -> St.Setting:
650 nix_setting(opt ++ ".\"" ++ f ++ "\".source", "./launch/" ++ String.take(String.trim(sum), 12n) ++ "-" ++ f)
651
652
653def kind_bend(k: St.ValueKind) -> String:
654 match k:
655 case St.KBool{}:
656 "St.KBool{}"
657 case St.KInt{}:
658 "St.KInt{}"
659 case St.KFloat{}:
660 "St.KFloat{}"
661 case St.KStr{}:
662 "St.KStr{}"
663 case St.KNix{}:
664 "St.KNix{}"
665
666def setting_bend(s: St.Setting) -> String:
667 match s:
668 case St.Setting{o, k, v}:
669 " St.Setting{" ++ BrO.bend_str(o) ++ ", " ++ kind_bend(k) ++ ", " ++ BrO.bend_str(v) ++ "}"
670
671def settings_bend(xs: List<&2, St.Setting>) -> List<&2, String>:
672 match xs:
673 case []:
674 []
675 case s <> rest:
676 setting_bend(s) <> settings_bend(rest)
677
678def bool_bend(b: Bool) -> String:
679 Bool.pick(String, b, "True{}", "False{}")
680
681def firewall_bend(f: St.Firewall) -> String:
682 match f:
683 case St.FirewallOff{}:
684 "St.FirewallOff{}"
685 case St.FirewallOn{a, b, c, d}:
686 "St.FirewallOn{" ++ bool_bend(a) ++ ", " ++ bool_bend(b) ++ ", " ++ bool_bend(c) ++ ", " ++ bool_bend(d) ++ "}"
687
688
689def v_package() -> String:
690 "near-v-framework@0.3.0.0/domain/architecture/system/deployment/nix-darwin"
691
692
693def import_text(settings: List<&2, St.Setting>, fw: St.Firewall, brews: List<&2, Br.Brew>) -> String:
694 "import Base\nimport " ++ v_package() ++ "/setting/type.bend as St\nimport " ++ v_package() ++ "/brew/type.bend as Br\n\n" ++
695 "# The Mac as V read it.\n\ndef settings() -> List<&2, St.Setting>:\n [\n" ++ String.join(settings_bend(settings), ",\n") ++ "\n ]\n\n" ++
696 "# The application firewall: its options exist only while it is on.\ndef firewall() -> St.Firewall:\n " ++ firewall_bend(fw) ++ "\n\n" ++
697 "def brews() -> List<&2, Br.Brew>:\n" ++ BrO.bend_list(brews) ++ "\n"