lib/mac_types.bend
near-v-framework@0.2.0.0 · 255 lines · 0 definitions
1import Base
2
3# One declared or observed setting of the Mac: its nix-darwin option, how to write its value, and the value.
4# KNix values are whole Nix expressions (lists, attribute sets, paths), written as they are.
5# Values are kept exactly as nix-darwin would write them, so declared and observed compare directly.
6type VKind is Data:
7 KBool{}
8 KInt{}
9 KFloat{}
10 KStr{}
11 KNix{}
12
13type Setting is Data:
14 Setting{option: String, kind: VKind, value: String}
15
16type Change is Data:
17 Change{option: String, now: String, want: String}
18
19def cmp_eq(c: Cmp) -> Bool:
20 match c:
21 case EQ{}:
22 True{}
23 case _:
24 False{}
25
26def snd_eq(r: (String & String) & Cmp) -> Bool:
27 match r:
28 case (_, c):
29 cmp_eq(c)
30
31def str_eq(a: String, b: String) -> Bool:
32 snd_eq(String.cmp(a, b))
33
34def option_of(s: Setting) -> String:
35 match s:
36 case Setting{o, _, _}:
37 o
38
39def value_of(s: Setting) -> String:
40 match s:
41 case Setting{_, _, v}:
42 v
43
44# The observed value of `option`, or "(unset)".
45
46def pick_value(hit: Bool, s: Setting, other: String) -> String:
47 match hit:
48 case True{}:
49 value_of(s)
50 case False{}:
51 other
52
53
54def lookup(+option: String, now: List<&2, Setting>) -> String:
55 match now:
56 case []:
57 "(unset)"
58 case +s <> rest:
59 pick_value(str_eq(option, option_of(s)), s, lookup(option, rest))
60
61def change_if(same: Bool, option: String, now: String, want: String, rest: List<&2, Change>) -> List<&2, Change>:
62 match same:
63 case True{}:
64 rest
65 case False{}:
66 Change{option, now, want} <> rest
67
68# Dry run: every declared setting whose observed value differs.
69
70def change_if_str(+observed: String, option: String, +value: String, rest: List<&2, Change>) -> List<&2, Change>:
71 change_if(str_eq(observed, value), option, observed, value, rest)
72
73# The nix literal for a value.
74
75def diff_one(s: Setting, +now: List<&2, Setting>, rest: List<&2, Change>) -> List<&2, Change>:
76 match s:
77 case Setting{+option, _, +value}:
78 change_if_str(lookup(option, now), option, value, rest)
79
80def diff(want: List<&2, Setting>, +now: List<&2, Setting>) -> List<&2, Change>:
81 match want:
82 case []:
83 []
84 case s <> rest:
85 diff_one(s, now, diff(rest, now))
86
87def literal(k: VKind, v: String) -> String:
88 match k:
89 case KStr{}:
90 "\"" ++ v ++ "\""
91 case _:
92 v
93
94def line(s: Setting) -> String:
95 match s:
96 case Setting{option, k, v}:
97 " " ++ option ++ " = " ++ literal(k, v) ++ ";\n"
98
99def lines(xs: List<&2, Setting>) -> String:
100 match xs:
101 case []:
102 ""
103 case s <> rest:
104 line(s) ++ lines(rest)
105
106# A Homebrew entry: kind is "tap", "brew", "cask", "mas" (name is the Nix attribute `"App" = id`) or "other" (a raw Brewfile line).
107type Brew is Data:
108 Brew{kind: String, name: String}
109
110def brew_key(b: Brew) -> String:
111 match b:
112 case Brew{k, n}:
113 k ++ " " ++ n
114
115def keys(xs: List<&2, Brew>) -> List<&2, String>:
116 match xs:
117 case []:
118 []
119 case b <> rest:
120 brew_key(b) <> keys(rest)
121
122def has(+s: String, xs: List<&2, String>) -> Bool:
123 match xs:
124 case []:
125 False{}
126 case x <> rest:
127 str_eq(s, x) || has(s, rest)
128
129def keep_if(missing: Bool, k: String, rest: List<&2, String>) -> List<&2, String>:
130 match missing:
131 case True{}:
132 k <> rest
133 case False{}:
134 rest
135
136# Entries of `a` that are not in `b`.
137def minus(a: List<&2, String>, +b: List<&2, String>) -> List<&2, String>:
138 match a:
139 case []:
140 []
141 case +k <> rest:
142 keep_if(Bool.not(has(k, b)), k, minus(rest, b))
143
144# Nix for the Homebrew section: one list per kind; entries nix-darwin has no option for are raw Brewfile lines.
145def kind_of(b: Brew) -> String:
146 match b:
147 case Brew{k, _}:
148 k
149
150def name_of(b: Brew) -> String:
151 match b:
152 case Brew{_, n}:
153 n
154
155def quoted_if(hit: Bool, n: String) -> String:
156 match hit:
157 case True{}:
158 " \"" ++ n ++ "\""
159 case False{}:
160 ""
161
162def names(+k: String, xs: List<&2, Brew>) -> String:
163 match xs:
164 case []:
165 ""
166 case +b <> rest:
167 quoted_if(str_eq(k, kind_of(b)), name_of(b)) ++ names(k, rest)
168
169def raw_if(hit: Bool, n: String) -> String:
170 match hit:
171 case True{}:
172 " " ++ n ++ "\n"
173 case False{}:
174 ""
175
176def raws(xs: List<&2, Brew>) -> String:
177 match xs:
178 case []:
179 ""
180 case +b <> rest:
181 raw_if(str_eq("other", kind_of(b)), name_of(b)) ++ raws(rest)
182
183def mas_if(hit: Bool, n: String) -> String:
184 match hit:
185 case True{}:
186 " " ++ n ++ ";"
187 case False{}:
188 ""
189
190def mas(xs: List<&2, Brew>) -> String:
191 match xs:
192 case []:
193 ""
194 case +b <> rest:
195 mas_if(str_eq("mas", kind_of(b)), name_of(b)) ++ mas(rest)
196
197def brew_section(+xs: List<&2, Brew>) -> String:
198 " homebrew.taps = [" ++ names("tap", xs) ++ " ];\n" ++
199 " homebrew.masApps = {" ++ mas(xs) ++ " };\n" ++
200 " homebrew.brews = [" ++ names("brew", xs) ++ " ];\n" ++
201 " homebrew.casks = [" ++ names("cask", xs) ++ " ];\n" ++
202 " homebrew.extraConfig = ''\n" ++ raws(xs) ++ " '';\n"
203
204# The macOS application firewall. Its options exist only while it is on: `socketfilterfw` turns the
205# firewall on whenever one of them is set, so "off, with options" cannot be written.
206type Firewall is Data:
207 FirewallOff{}
208 FirewallOn{allowSigned: Bool, allowSignedApp: Bool, blockAllIncoming: Bool, stealth: Bool}
209
210def nix_bool(b: Bool) -> String:
211 match b:
212 case True{}:
213 "true"
214 case False{}:
215 "false"
216
217def firewall_nix(f: Firewall) -> String:
218 match f:
219 case FirewallOff{}:
220 " networking.applicationFirewall.enable = false;\n"
221 case FirewallOn{a, b, c, d}:
222 " networking.applicationFirewall.enable = true;\n" ++
223 " networking.applicationFirewall.allowSigned = " ++ nix_bool(a) ++ ";\n" ++
224 " networking.applicationFirewall.allowSignedApp = " ++ nix_bool(b) ++ ";\n" ++
225 " networking.applicationFirewall.blockAllIncoming = " ++ nix_bool(c) ++ ";\n" ++
226 " networking.applicationFirewall.enableStealthMode = " ++ nix_bool(d) ++ ";\n"
227
228def firewall_show(f: Firewall) -> String:
229 match f:
230 case FirewallOff{}:
231 "off"
232 case FirewallOn{a, b, c, d}:
233 "on (allowSigned " ++ nix_bool(a) ++ ", allowSignedApp " ++ nix_bool(b) ++ ", blockAllIncoming " ++ nix_bool(c) ++ ", stealth " ++ nix_bool(d) ++ ")"
234
235def firewall_change_if(same: Bool, now: String, want: String) -> String:
236 match same:
237 case True{}:
238 ""
239 case False{}:
240 "change networking.applicationFirewall: " ++ now ++ " -> " ++ want ++ "\n"
241
242def firewall_change_str(+want: String, +now: String) -> String:
243 firewall_change_if(str_eq(want, now), now, want)
244
245# Dry run line for the firewall ("" when unchanged).
246def firewall_change(want: Firewall, now: Firewall) -> String:
247 firewall_change_str(firewall_show(want), firewall_show(now))
248
249# True when no setting's option starts with `p` (areas V types, like the firewall, stay out of the flat list).
250def none_prefixed(+p: String, xs: List<&2, Setting>) -> Bool:
251 match xs:
252 case []:
253 True{}
254 case s <> rest:
255 Bool.not(String.starts_with(option_of(s), p)) && none_prefixed(p, rest)