domain/architecture/system/deployment/nix_darwin/brew/ops.bend

near-v-framework@0.4.0.0 · 188 lines · 28 definitions

1import Base
2import near-v-framework@0.4.0.0/domain/architecture/system/deployment/nix_darwin/brew/type.bend as Br
3
4# ---- string literals: Nix (mac.nix), Ruby (the Brewfile) and Bend (an imported settings file) ----
5# One character of a double-quoted literal: backslash, quote and the language's `x` (Nix's $, Ruby's #)
6# are escaped, and so is a newline.
7def quote_char(+c: Char, +x: Char) -> String:
8  Bool.pick(String, Char.is_eq(c, '\\') || Char.is_eq(c, '"') || Char.is_eq(c, x), "\\" ++ String.from_list([c]),
9    Bool.pick(String, Char.is_eq(c, '\n'), "\\n", String.from_list([c])))
10
11def quote_chars(cs: List<&2, Char>, +x: Char) -> String:
12  match cs:
13    case []:
14      ""
15    case c <> rest:
16      quote_char(c, x) ++ quote_chars(rest, x)
17
18# A string's characters as a Nix string literal writes them, without the quotes.
19def nix_esc(s: String) -> String:
20  quote_chars(String.to_list(s), '$')
21
22def nix_str(s: String) -> String:
23  "\"" ++ nix_esc(s) ++ "\""
24
25def ruby_str(s: String) -> String:
26  "\"" ++ quote_chars(String.to_list(s), '#') ++ "\""
27
28def bend_str(s: String) -> String:
29  "\"" ++ quote_chars(String.to_list(s), '"') ++ "\""
30
31# ---- the Homebrew section of mac.nix: one list per kind; entries nix-darwin has no option for are raw
32# Brewfile lines ----
33def tap_nix(b: Br.Brew) -> String:
34  match b:
35    case Br.Tap{n}:
36      " " ++ nix_str(n)
37    case _:
38      ""
39
40def formula_nix(b: Br.Brew) -> String:
41  match b:
42    case Br.Formula{n}:
43      " " ++ nix_str(n)
44    case _:
45      ""
46
47def cask_nix(b: Br.Brew) -> String:
48  match b:
49    case Br.Cask{n}:
50      " " ++ nix_str(n)
51    case _:
52      ""
53
54def other_nix(b: Br.Brew) -> String:
55  match b:
56    case Br.Other{l}:
57      "    " ++ l ++ "\n"
58    case _:
59      ""
60
61def mas_name(b: Br.Brew) -> List<&2, String>:
62  match b:
63    case Br.Mas{n, _}:
64      [n]
65    case _:
66      []
67
68def mas_names(xs: List<&2, Br.Brew>) -> List<&2, String>:
69  match xs:
70    case []:
71      []
72    case b <> rest:
73      List.append(&2, String, mas_name(b), mas_names(rest))
74
75def occurrences(xs: List<&2, String>, +x: String) -> Nat:
76  match xs:
77    case []:
78      0n
79    case y <> rest:
80      Nat.add(Bool.pick(Nat, String.eq(x, y), 1n, 0n), occurrences(rest, x))
81
82# masApps is an attribute set: an app name the list repeats gets its id added.
83def mas_nix(b: Br.Brew, +names: List<&2, String>) -> String:
84  match b:
85    case Br.Mas{+n, +id}:
86      " " ++ nix_str(Bool.pick(String, Nat.is_gt(occurrences(names, n), 1n), n ++ " (" ++ id ++ ")", n)) ++ " = " ++ id ++ ";"
87    case _:
88      ""
89
90# Part 0 taps, 1 masApps, 2 brews, 3 casks, 4 extraConfig.
91def all_nix(xs: List<&2, Br.Brew>, +part: Nat, +names: List<&2, String>) -> String:
92  match xs:
93    case []:
94      ""
95    case +b <> rest:
96      Bool.pick(String, Nat.is_eq(part, 0n), tap_nix(b),
97        Bool.pick(String, Nat.is_eq(part, 1n), mas_nix(b, names),
98          Bool.pick(String, Nat.is_eq(part, 2n), formula_nix(b),
99            Bool.pick(String, Nat.is_eq(part, 3n), cask_nix(b), other_nix(b))))) ++ all_nix(rest, part, names)
100
101def nix_section(+xs: List<&2, Br.Brew>) -> String:
102  "  homebrew.taps = [" ++ all_nix(xs, 0n, []) ++ " ];\n" ++
103  "  homebrew.masApps = {" ++ all_nix(xs, 1n, mas_names(xs)) ++ " };\n" ++
104  "  homebrew.brews = [" ++ all_nix(xs, 2n, []) ++ " ];\n" ++
105  "  homebrew.casks = [" ++ all_nix(xs, 3n, []) ++ " ];\n" ++
106  "  homebrew.extraConfig = ''\n" ++ all_nix(xs, 4n, []) ++ "  '';\n"
107
108# ---- the Brewfile, for brew bundle ----
109def brewfile_line(b: Br.Brew) -> String:
110  match b:
111    case Br.Tap{n}:
112      "tap " ++ ruby_str(n)
113    case Br.Formula{n}:
114      "brew " ++ ruby_str(n)
115    case Br.Cask{n}:
116      "cask " ++ ruby_str(n)
117    case Br.Mas{n, id}:
118      "mas " ++ ruby_str(n) ++ ", id: " ++ id
119    case Br.Other{l}:
120      l
121
122def brewfile(xs: List<&2, Br.Brew>) -> String:
123  match xs:
124    case []:
125      ""
126    case b <> rest:
127      brewfile_line(b) ++ "\n" ++ brewfile(rest)
128
129# ---- reading a Brewfile (as `brew bundle dump` writes it): a line's options after the name are dropped,
130# except a Mac App Store app's id ----
131def digits_of(cs: List<&2, Char>) -> String:
132  match cs:
133    case []:
134      ""
135    case +c <> rest:
136      Bool.pick(String, Char.is_digit(c), String.from_list([c]) ++ digits_of(rest), "")
137
138# The digits after "id:", "" when there are none.
139def mas_id(+l: String) -> String:
140  digits_of(String.to_list(String.trim(Maybe.default(&2, String, List.get(&2, String, String.split(String.drop(l, 4n), ':'), 1n), ""))))
141
142def entry_of(+kind: String, +name: String, +l: String) -> List<&2, Br.Brew>:
143  Bool.pick(List<&2, Br.Brew>, String.is_empty(l) || String.starts_with(l, "#"), [],
144    Bool.pick(List<&2, Br.Brew>, String.eq(kind, "tap"), [Br.Tap{name}],
145      Bool.pick(List<&2, Br.Brew>, String.eq(kind, "brew"), [Br.Formula{name}],
146        Bool.pick(List<&2, Br.Brew>, String.eq(kind, "cask"), [Br.Cask{name}],
147          Bool.pick(List<&2, Br.Brew>, String.eq(kind, "mas"), [Br.Mas{name, mas_id(l)}], [Br.Other{l}])))))
148
149def first_word(+l: String) -> String:
150  Maybe.default(&2, String, List.head(&2, String, String.split(l, ' ')), "")
151
152def quoted_name(+l: String) -> String:
153  Maybe.default(&2, String, List.get(&2, String, String.split(l, '"'), 1n), "")
154
155def of_lines(ls: List<&2, String>) -> List<&2, Br.Brew>:
156  match ls:
157    case []:
158      []
159    case l <> rest:
160      +t = String.trim(l)
161      List.append(&2, Br.Brew, entry_of(first_word(t), quoted_name(t), t), of_lines(rest))
162
163def of_brewfile(text: String) -> List<&2, Br.Brew>:
164  of_lines(String.lines(text))
165
166# ---- Bend source for a list of entries (an imported settings file names the types Br) ----
167def bend_expr(b: Br.Brew) -> String:
168  match b:
169    case Br.Tap{n}:
170      "Br.Tap{" ++ bend_str(n) ++ "}"
171    case Br.Formula{n}:
172      "Br.Formula{" ++ bend_str(n) ++ "}"
173    case Br.Cask{n}:
174      "Br.Cask{" ++ bend_str(n) ++ "}"
175    case Br.Mas{n, id}:
176      "Br.Mas{" ++ bend_str(n) ++ ", " ++ bend_str(id) ++ "}"
177    case Br.Other{l}:
178      "Br.Other{" ++ bend_str(l) ++ "}"
179
180def bend_exprs(xs: List<&2, Br.Brew>) -> List<&2, String>:
181  match xs:
182    case []:
183      []
184    case b <> rest:
185      ("    " ++ bend_expr(b)) <> bend_exprs(rest)
186
187def bend_list(xs: List<&2, Br.Brew>) -> String:
188  "  [\n" ++ String.join(bend_exprs(xs), ",\n") ++ "\n  ]"