lib/vstr.bend

near-v-framework@0.1.0.0 · 190 lines · 0 definitions

1import Base
2import near-v-framework@0.1.0.0/lib/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
6def 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
13def 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
20def words(s: String) -> List<&2, String>:
21  nonempty(String.split(s, ' '))
22
23def 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
34def 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
45def count(xs: List<&2, String>) -> Nat:
46  List.length(&2, String, xs)
47
48def 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
55def 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
62def digits(s: String) -> String:
63  digits_of(String.to_list(s))
64
65def 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
72def 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
79def append(a: List<&2, String>, b: List<&2, String>) -> List<&2, String>:
80  List.append(&2, String, a, b)
81
82def quoted(xs: List<&2, String>) -> List<&2, String>:
83  match xs:
84    case []:
85      []
86    case x <> rest:
87      ("\"" ++ x ++ "\"") <> quoted(rest)
88
89def quote_all(xs: List<&2, String>) -> String:
90  String.join(quoted(xs), ", ")
91
92def join_lines(xs: List<&2, String>) -> String:
93  String.join(xs, "\n")
94
95# ---- escaping ----
96def pick(c: Bool, a: String, b: String) -> String:
97  match c:
98    case True{}:
99      a
100    case False{}:
101      b
102
103def 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
114def esc_char(+c: Char) -> String:
115  pick_esc(Char.is_eq(c, '\\'), Char.is_eq(c, '"'), c)
116
117def 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.
125def esc(s: String) -> String:
126  esc_chars(String.to_list(s))
127
128def 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
131def 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.
139def nix_str(s: String) -> String:
140  "\"" ++ nix_esc_chars(String.to_list(s)) ++ "\""
141
142def 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.
150def nix_list(xs: List<&2, String>) -> String:
151  "[" ++ nix_strs(xs) ++ " ]"
152
153def setting_line(name: String, k: String, v: String) -> String:
154  "    T.Setting{\"" ++ esc(name) ++ "\", T." ++ k ++ "{}, \"" ++ esc(v) ++ "\"}"
155
156def 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`) ----
165def 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
172def 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
179def xml_unescape(+s: String) -> String:
180  nth(String.split(s, '&'), 0n) ++ xml_entities(List.drop(&2, String, String.split(s, '&'), 1n))
181
182# "<true/>" -> "1", "<false/>" -> "0", "<integer>3</integer>" -> "3", "<string>a &amp; b</string>" -> "a & b"; "" when unset.
183def plist_scalar(+v: String) -> String:
184  pick(String.starts_with(String.trim(v), "<true/>"), "1",
185  pick(String.starts_with(String.trim(v), "<false/>"), "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 <args> export`-style output (args like "export -g", "-currentHost export com.apple.x").
189def export_key(+args: String, +key: String) -> String:
190  "defaults " ++ args ++ " - 2>/dev/null | awk -v k='<key>" ++ key ++ "</key>' 'f{s=s $0 \"\\n\"; if ($0 ~ /<\\/string>|<\\/integer>|<\\/real>|\\/>/) {printf \"%s\", s; exit}} $0==\"\\t\"k{f=1}'"