1 import Base 2 import ./mac_types.bend as T 3 4 # String and list helpers for V's command line (explicit recursion; Bend's generic ones take compile-time functions). 5 6 def not_empty_keep(e: Bool, s: String, rest: List<&2, String>) -> List<&2, String>: 7 match e: 8 case True{}: 9 rest 10 case False{}: 11 s <> rest 12 13 def nonempty(xs: List<&2, String>) -> List<&2, String>: 14 match xs: 15 case []: 16 [] 17 case +s <> rest: 18 not_empty_keep(String.is_empty(s), s, nonempty(rest)) 19 20 def words(s: String) -> List<&2, String>: 21 nonempty(String.split(s, ' ')) 22 23 def nth(xs: List<&2, String>, n: Nat) -> String: 24 match xs: 25 case []: 26 "" 27 case x <> rest: 28 match n: 29 case 0n: 30 x 31 case 1n+m: 32 nth(rest, m) 33 34 def last(xs: List<&2, String>) -> String: 35 match xs: 36 case []: 37 "" 38 case x <> rest: 39 match rest: 40 case []: 41 x 42 case y <> more: 43 last(y <> more) 44 45 def count(xs: List<&2, String>) -> Nat: 46 List.length(&2, String, xs) 47 48 def digit_or_stop(d: Bool, c: Char, tail: String) -> String: 49 match d: 50 case True{}: 51 String.from_list([c]) ++ tail 52 case False{}: 53 "" 54 55 def digits_of(cs: List<&2, Char>) -> String: 56 match cs: 57 case []: 58 "" 59 case +c <> rest: 60 digit_or_stop(Char.is_digit(c), c, digits_of(rest)) 61 62 def digits(s: String) -> String: 63 digits_of(String.to_list(s)) 64 65 def keep_new(dup: Bool, x: String, rest: List<&2, String>) -> List<&2, String>: 66 match dup: 67 case True{}: 68 rest 69 case False{}: 70 x <> rest 71 72 def dedupe(xs: List<&2, String>) -> List<&2, String>: 73 match xs: 74 case []: 75 [] 76 case +x <> +rest: 77 keep_new(T.has(x, rest), x, dedupe(rest)) 78 79 def append(a: List<&2, String>, b: List<&2, String>) -> List<&2, String>: 80 List.append(&2, String, a, b) 81 82 def quoted(xs: List<&2, String>) -> List<&2, String>: 83 match xs: 84 case []: 85 [] 86 case x <> rest: 87 ("\"" ++ x ++ "\"") <> quoted(rest) 88 89 def quote_all(xs: List<&2, String>) -> String: 90 String.join(quoted(xs), ", ") 91 92 def join_lines(xs: List<&2, String>) -> String: 93 String.join(xs, "\n") 94 95 # ---- escaping ---- 96 def pick(c: Bool, a: String, b: String) -> String: 97 match c: 98 case True{}: 99 a 100 case False{}: 101 b 102 103 def pick_esc(bs: Bool, q: Bool, c: Char) -> String: 104 match bs: 105 case True{}: 106 "\\\\" 107 case False{}: 108 match q: 109 case True{}: 110 "\\\"" 111 case False{}: 112 String.from_list([c]) 113 114 def esc_char(+c: Char) -> String: 115 pick_esc(Char.is_eq(c, '\\'), Char.is_eq(c, '"'), c) 116 117 def esc_chars(cs: List<&2, Char>) -> String: 118 match cs: 119 case []: 120 "" 121 case c <> rest: 122 esc_char(c) ++ esc_chars(rest) 123 124 # Escape for a Bend string literal. 125 def esc(s: String) -> String: 126 esc_chars(String.to_list(s)) 127 128 def nix_esc_char(+c: Char) -> String: 129 pick(Char.is_eq(c, '\\'), "\\\\", pick(Char.is_eq(c, '"'), "\\\"", pick(Char.is_eq(c, '\n'), "\\n", String.from_list([c])))) 130 131 def nix_esc_chars(cs: List<&2, Char>) -> String: 132 match cs: 133 case []: 134 "" 135 case c <> rest: 136 nix_esc_char(c) ++ nix_esc_chars(rest) 137 138 # A string as a Nix string literal. 139 def nix_str(s: String) -> String: 140 "\"" ++ nix_esc_chars(String.to_list(s)) ++ "\"" 141 142 def nix_strs(xs: List<&2, String>) -> String: 143 match xs: 144 case []: 145 "" 146 case x <> rest: 147 " " ++ nix_str(x) ++ nix_strs(rest) 148 149 # A list of strings as a Nix list. 150 def nix_list(xs: List<&2, String>) -> String: 151 "[" ++ nix_strs(xs) ++ " ]" 152 153 def setting_line(name: String, k: String, v: String) -> String: 154 " T.Setting{\"" ++ esc(name) ++ "\", T." ++ k ++ "{}, \"" ++ esc(v) ++ "\"}" 155 156 def line_if(keep: Bool, l: String) -> List<&2, String>: 157 match keep: 158 case True{}: 159 [l] 160 case False{}: 161 [] 162 163 164 # ---- plist values, as `defaults export` writes them (real characters, unlike `defaults read`) ---- 165 def xml_entity(+seg: String) -> String: 166 pick(String.starts_with(seg, "amp;"), "&" ++ String.drop(seg, 4n), 167 pick(String.starts_with(seg, "lt;"), "<" ++ String.drop(seg, 3n), 168 pick(String.starts_with(seg, "gt;"), ">" ++ String.drop(seg, 3n), 169 pick(String.starts_with(seg, "quot;"), "\"" ++ String.drop(seg, 5n), 170 pick(String.starts_with(seg, "apos;"), "'" ++ String.drop(seg, 5n), "&" ++ seg))))) 171 172 def xml_entities(segs: List<&2, String>) -> String: 173 match segs: 174 case []: 175 "" 176 case s <> rest: 177 xml_entity(s) ++ xml_entities(rest) 178 179 def xml_unescape(+s: String) -> String: 180 nth(String.split(s, '&'), 0n) ++ xml_entities(List.drop(&2, String, String.split(s, '&'), 1n)) 181 182 # "" -> "1", "" -> "0", "3" -> "3", "a & b" -> "a & b"; "" when unset. 183 def plist_scalar(+v: String) -> String: 184 pick(String.starts_with(String.trim(v), ""), "1", 185 pick(String.starts_with(String.trim(v), ""), "0", 186 xml_unescape(nth(String.split(nth(String.split(v, '>'), 1n), '<'), 0n)))) 187 188 # Shell: the XML value of top-level `key` in `defaults export`-style output (args like "export -g", "-currentHost export com.apple.x"). 189 def export_key(+args: String, +key: String) -> String: 190 "defaults " ++ args ++ " - 2>/dev/null | awk -v k='" ++ key ++ "' 'f{s=s $0 \"\\n\"; if ($0 ~ /<\\/string>|<\\/integer>|<\\/real>|\\/>/) {printf \"%s\", s; exit}} $0==\"\\t\"k{f=1}'"