1 import Base 2 import ./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. 7 def 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 11 def 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. 19 def nix_esc(s: String) -> String: 20 quote_chars(String.to_list(s), '$') 21 22 def nix_str(s: String) -> String: 23 "\"" ++ nix_esc(s) ++ "\"" 24 25 def ruby_str(s: String) -> String: 26 "\"" ++ quote_chars(String.to_list(s), '#') ++ "\"" 27 28 def 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 ---- 33 def tap_nix(b: Br.Brew) -> String: 34 match b: 35 case Br.Tap{n}: 36 " " ++ nix_str(n) 37 case _: 38 "" 39 40 def formula_nix(b: Br.Brew) -> String: 41 match b: 42 case Br.Formula{n}: 43 " " ++ nix_str(n) 44 case _: 45 "" 46 47 def cask_nix(b: Br.Brew) -> String: 48 match b: 49 case Br.Cask{n}: 50 " " ++ nix_str(n) 51 case _: 52 "" 53 54 def other_nix(b: Br.Brew) -> String: 55 match b: 56 case Br.Other{l}: 57 " " ++ l ++ "\n" 58 case _: 59 "" 60 61 def mas_name(b: Br.Brew) -> List<&2, String>: 62 match b: 63 case Br.Mas{n, _}: 64 [n] 65 case _: 66 [] 67 68 def 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 75 def 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. 83 def 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. 91 def 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 101 def 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 ---- 109 def 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 122 def 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 ---- 131 def 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. 139 def 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 142 def 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 149 def first_word(+l: String) -> String: 150 Maybe.default(&2, String, List.head(&2, String, String.split(l, ' ')), "") 151 152 def quoted_name(+l: String) -> String: 153 Maybe.default(&2, String, List.get(&2, String, String.split(l, '"'), 1n), "") 154 155 def 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 163 def 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) ---- 167 def 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 180 def 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 187 def bend_list(xs: List<&2, Br.Brew>) -> String: 188 " [\n" ++ String.join(bend_exprs(xs), ",\n") ++ "\n ]"