~/bend-docscommunity

lib/mac_types.bend source

lib/mac_types.bend on the hub · documented module

import Base# One declared or observed setting of the Mac: its nix-darwin option, how to write its value, and the value.# KNix values are whole Nix expressions (lists, attribute sets, paths), written as they are.# Values are kept exactly as nix-darwin would write them, so declared and observed compare directly.type VKind is Data:  KBool{}  KInt{}  KFloat{}  KStr{}  KNix{}type Setting is Data:  Setting{option: String, kind: VKind, value: String}type Change is Data:  Change{option: String, now: String, want: String}def cmp_eq(c: Cmp) -> Bool:  match c:    case EQ{}:      True{}    case _:      False{}def snd_eq(r: (String & String) & Cmp) -> Bool:  match r:    case (_, c):      cmp_eq(c)def str_eq(a: String, b: String) -> Bool:  snd_eq(String.cmp(a, b))def option_of(s: Setting) -> String:  match s:    case Setting{o, _, _}:      odef value_of(s: Setting) -> String:  match s:    case Setting{_, _, v}:      v# The observed value of `option`, or "(unset)".def pick_value(hit: Bool, s: Setting, other: String) -> String:  match hit:    case True{}:      value_of(s)    case False{}:      otherdef lookup(+option: String, now: List<&2, Setting>) -> String:  match now:    case []:      "(unset)"    case +s <> rest:      pick_value(str_eq(option, option_of(s)), s, lookup(option, rest))def change_if(same: Bool, option: String, now: String, want: String, rest: List<&2, Change>) -> List<&2, Change>:  match same:    case True{}:      rest    case False{}:      Change{option, now, want} <> rest# Dry run: every declared setting whose observed value differs.def change_if_str(+observed: String, option: String, +value: String, rest: List<&2, Change>) -> List<&2, Change>:  change_if(str_eq(observed, value), option, observed, value, rest)# The nix literal for a value.def diff_one(s: Setting, +now: List<&2, Setting>, rest: List<&2, Change>) -> List<&2, Change>:  match s:    case Setting{+option, _, +value}:      change_if_str(lookup(option, now), option, value, rest)def diff(want: List<&2, Setting>, +now: List<&2, Setting>) -> List<&2, Change>:  match want:    case []:      []    case s <> rest:      diff_one(s, now, diff(rest, now))def literal(k: VKind, v: String) -> String:  match k:    case KStr{}:      "\"" ++ v ++ "\""    case _:      vdef line(s: Setting) -> String:  match s:    case Setting{option, k, v}:      "  " ++ option ++ " = " ++ literal(k, v) ++ ";\n"def lines(xs: List<&2, Setting>) -> String:  match xs:    case []:      ""    case s <> rest:      line(s) ++ lines(rest)# A Homebrew entry: kind is "tap", "brew", "cask", "mas" (name is the Nix attribute `"App" = id`) or "other" (a raw Brewfile line).type Brew is Data:  Brew{kind: String, name: String}def brew_key(b: Brew) -> String:  match b:    case Brew{k, n}:      k ++ " " ++ ndef keys(xs: List<&2, Brew>) -> List<&2, String>:  match xs:    case []:      []    case b <> rest:      brew_key(b) <> keys(rest)def has(+s: String, xs: List<&2, String>) -> Bool:  match xs:    case []:      False{}    case x <> rest:      str_eq(s, x) || has(s, rest)def keep_if(missing: Bool, k: String, rest: List<&2, String>) -> List<&2, String>:  match missing:    case True{}:      k <> rest    case False{}:      rest# Entries of `a` that are not in `b`.def minus(a: List<&2, String>, +b: List<&2, String>) -> List<&2, String>:  match a:    case []:      []    case +k <> rest:      keep_if(Bool.not(has(k, b)), k, minus(rest, b))# Nix for the Homebrew section: one list per kind; entries nix-darwin has no option for are raw Brewfile lines.def kind_of(b: Brew) -> String:  match b:    case Brew{k, _}:      kdef name_of(b: Brew) -> String:  match b:    case Brew{_, n}:      ndef quoted_if(hit: Bool, n: String) -> String:  match hit:    case True{}:      " \"" ++ n ++ "\""    case False{}:      ""def names(+k: String, xs: List<&2, Brew>) -> String:  match xs:    case []:      ""    case +b <> rest:      quoted_if(str_eq(k, kind_of(b)), name_of(b)) ++ names(k, rest)def raw_if(hit: Bool, n: String) -> String:  match hit:    case True{}:      "    " ++ n ++ "\n"    case False{}:      ""def raws(xs: List<&2, Brew>) -> String:  match xs:    case []:      ""    case +b <> rest:      raw_if(str_eq("other", kind_of(b)), name_of(b)) ++ raws(rest)def mas_if(hit: Bool, n: String) -> String:  match hit:    case True{}:      " " ++ n ++ ";"    case False{}:      ""def mas(xs: List<&2, Brew>) -> String:  match xs:    case []:      ""    case +b <> rest:      mas_if(str_eq("mas", kind_of(b)), name_of(b)) ++ mas(rest)def brew_section(+xs: List<&2, Brew>) -> String:  "  homebrew.taps = [" ++ names("tap", xs) ++ " ];\n" ++  "  homebrew.masApps = {" ++ mas(xs) ++ " };\n" ++  "  homebrew.brews = [" ++ names("brew", xs) ++ " ];\n" ++  "  homebrew.casks = [" ++ names("cask", xs) ++ " ];\n" ++  "  homebrew.extraConfig = ''\n" ++ raws(xs) ++ "  '';\n"# The macOS application firewall. Its options exist only while it is on: `socketfilterfw` turns the# firewall on whenever one of them is set, so "off, with options" cannot be written.type Firewall is Data:  FirewallOff{}  FirewallOn{allowSigned: Bool, allowSignedApp: Bool, blockAllIncoming: Bool, stealth: Bool}def nix_bool(b: Bool) -> String:  match b:    case True{}:      "true"    case False{}:      "false"def firewall_nix(f: Firewall) -> String:  match f:    case FirewallOff{}:      "  networking.applicationFirewall.enable = false;\n"    case FirewallOn{a, b, c, d}:      "  networking.applicationFirewall.enable = true;\n" ++      "  networking.applicationFirewall.allowSigned = " ++ nix_bool(a) ++ ";\n" ++      "  networking.applicationFirewall.allowSignedApp = " ++ nix_bool(b) ++ ";\n" ++      "  networking.applicationFirewall.blockAllIncoming = " ++ nix_bool(c) ++ ";\n" ++      "  networking.applicationFirewall.enableStealthMode = " ++ nix_bool(d) ++ ";\n"def firewall_show(f: Firewall) -> String:  match f:    case FirewallOff{}:      "off"    case FirewallOn{a, b, c, d}:      "on (allowSigned " ++ nix_bool(a) ++ ", allowSignedApp " ++ nix_bool(b) ++ ", blockAllIncoming " ++ nix_bool(c) ++ ", stealth " ++ nix_bool(d) ++ ")"def firewall_change_if(same: Bool, now: String, want: String) -> String:  match same:    case True{}:      ""    case False{}:      "change  networking.applicationFirewall: " ++ now ++ " -> " ++ want ++ "\n"def firewall_change_str(+want: String, +now: String) -> String:  firewall_change_if(str_eq(want, now), now, want)# Dry run line for the firewall ("" when unchanged).def firewall_change(want: Firewall, now: Firewall) -> String:  firewall_change_str(firewall_show(want), firewall_show(now))# True when no setting's option starts with `p` (areas V types, like the firewall, stay out of the flat list).def none_prefixed(+p: String, xs: List<&2, Setting>) -> Bool:  match xs:    case []:      True{}    case s <> rest:      Bool.not(String.starts_with(option_of(s), p)) && none_prefixed(p, rest)