src/ez/target.bend
ezx@1.11.0.0 · 456 lines · 58 definitions
1
2
3
4
5
6
7import Base
8import ezx@1.11.0.0/src/pkg/pkg.bend as K
9
10
11type Place is Data:
12 Bad{why: String}
13 Local{path: String}
14 Remote{url: String, slug: String}
15
16
17
18def plain.at(+ch: Char) -> Bool:
19 Bool.or(Char.is_digit(ch), Bool.or(Char.is_alpha(ch),
20 Bool.or(Char.is_eq(ch, '-'), Char.is_eq(ch, '_'))))
21
22
23
24
25
26type Mark is Data:
27 Slash{}
28 Dot{}
29 Tilde{}
30 Word{}
31 Other{}
32
33def kind.word(word: Bool) -> Mark:
34 match word:
35 case True{}:
36 Word{}
37 case False{}:
38 Other{}
39
40def kind.tilde(tilde: Bool, word: Bool) -> Mark:
41 match tilde:
42 case True{}:
43 Tilde{}
44 case False{}:
45 kind.word(word)
46
47def kind.dot(dot: Bool, tilde: Bool, word: Bool) -> Mark:
48 match dot:
49 case True{}:
50 Dot{}
51 case False{}:
52 kind.tilde(tilde, word)
53
54def kind.slash(slash: Bool, dot: Bool, tilde: Bool, word: Bool) -> Mark:
55 match slash:
56 case True{}:
57 Slash{}
58 case False{}:
59 kind.dot(dot, tilde, word)
60
61def kind.colon(colon: Bool, slash: Bool, dot: Bool, tilde: Bool, word: Bool) -> Mark:
62 match colon:
63 case True{}:
64 Other{}
65 case False{}:
66 kind.slash(slash, dot, tilde, word)
67
68
69def kind(+ch: Char) -> Mark:
70 kind.colon(Char.is_eq(ch, ':'), Char.is_eq(ch, '/'), Char.is_eq(ch, '.'),
71 Char.is_eq(ch, '~'), plain.at(ch))
72
73
74
75def name.keep(mark: Mark, rest: Unit -> Bool) -> Bool:
76 match mark:
77 case Word{}:
78 rest(Unit{})
79 case Dot{}:
80 rest(Unit{})
81 case Slash{}:
82 False{}
83 case Tilde{}:
84 False{}
85 case Other{}:
86 False{}
87
88
89def name.all(text: String) -> Bool:
90 match text:
91 case SNil{}:
92 True{}
93 case SCon{+h, t}:
94 name.keep(kind(h), _u => name.all(t))
95
96
97def seg.dots(text: String) -> Bool:
98 match text:
99 case SNil{}:
100 False{}
101 case SCon{+h, t}:
102 name.keep(kind(h), _u => name.all(t))
103
104
105def seg.dot2(mark: Mark, rest: String) -> Bool:
106 match mark:
107 case Word{}:
108 name.all(rest)
109 case Dot{}:
110 seg.dots(rest)
111 case Slash{}:
112 False{}
113 case Tilde{}:
114 False{}
115 case Other{}:
116 False{}
117
118
119def seg.dot(text: String) -> Bool:
120 match text:
121 case SNil{}:
122 False{}
123 case SCon{+h, t}:
124 seg.dot2(kind(h), t)
125
126
127def seg.first(mark: Mark, rest: String) -> Bool:
128 match mark:
129 case Word{}:
130 name.all(rest)
131 case Dot{}:
132 seg.dot(rest)
133 case Slash{}:
134 False{}
135 case Tilde{}:
136 False{}
137 case Other{}:
138 False{}
139
140
141
142def seg(text: String) -> Bool:
143 match text:
144 case SNil{}:
145 False{}
146 case SCon{+h, t}:
147 seg.first(kind(h), t)
148
149
150
151type Halves is Data:
152 Uncut{}
153 Two{left: String, right: String}
154
155
156def halve.put(ch: Char, halves: Halves) -> Halves:
157 match halves:
158 case Uncut{}:
159 Uncut{}
160 case Two{left, right}:
161 Two{SCon{ch, left}, right}
162
163
164
165def halve.at(mark: Mark, ch: Char, +tail: String, rest: Unit -> Halves) -> Halves:
166 match mark:
167 case Slash{}:
168 Two{"", tail}
169 case Word{}:
170 halve.put(ch, rest(Unit{}))
171 case Dot{}:
172 halve.put(ch, rest(Unit{}))
173 case Tilde{}:
174 Uncut{}
175 case Other{}:
176 Uncut{}
177
178
179def halve(text: String) -> Halves:
180 match text:
181 case SNil{}:
182 Uncut{}
183 case SCon{+h, +t}:
184 halve.at(kind(h), h, t, _u => halve(t))
185
186
187
188def bend.file(+text: String) -> Bool:
189 String.ends_with(text, ".bend")
190
191
192def repo.seg(+text: String) -> Bool:
193 Bool.and(seg(text), Bool.not(bend.file(text)))
194
195
196def github.halves(halves: Halves) -> Bool:
197 match halves:
198 case Uncut{}:
199 False{}
200 case Two{left, right}:
201 Bool.and(seg(left), repo.seg(right))
202
203
204
205
206def github.ok(text: String) -> Bool:
207 github.halves(halve(text))
208
209
210def climb.step(here: Bool, rest: Unit -> Bool) -> Bool:
211 match here:
212 case True{}:
213 True{}
214 case False{}:
215 rest(Unit{})
216
217
218def climb(ps: List<&2, String>) -> Bool:
219 match ps:
220 case []:
221 False{}
222 case +h <> t:
223 climb.step(String.eq(h, ".."), _u => climb(t))
224
225
226def slug.ok(+text: String) -> Bool:
227 Bool.and(Bool.not(String.is_empty(text)),
228 Bool.and(Bool.not(String.starts_with(text, "/")),
229 Bool.not(climb(String.split(text, '/')))))
230
231
232def remote(+url: String, +slug: String) -> Place:
233 Bool.pick(Place, slug.ok(slug), Remote{url, slug}, Bad{"not a repository"})
234
235
236
237def cut.end(+text: String, +suf: String, len: Nat) -> String:
238 Bool.pick(String, String.ends_with(text, suf),
239 String.take(text, Nat.sub(String.length(text), len)), text)
240
241
242def unslash(+text: String) -> String:
243 cut.end(text, "/", 1n)
244
245
246def ungit(+text: String) -> String:
247 cut.end(text, ".git", 4n)
248
249
250
251def body(ps: List<&2, String>) -> String:
252 match ps:
253 case Nil{}:
254 ""
255 case _h <> t:
256 String.join(t, ":")
257
258
259def drop.slashes(+text: String) -> String:
260 Bool.pick(String, String.starts_with(text, "//"), String.drop(text, 2n), text)
261
262
263
264def hostless(+text: String) -> String:
265 Bool.pick(String, String.starts_with(text, "/"), "localhost" ++ text, text)
266
267
268def slug.url(+text: String) -> String:
269 ungit(unslash(hostless(drop.slashes(body(String.split(text, ':'))))))
270
271
272def slug.scp(+text: String) -> String:
273 ungit(unslash(String.join(String.split(String.drop(text, 4n), ':'), "/")))
274
275
276def looks.slash.at(mark: Mark) -> Bool:
277 match mark:
278 case Slash{}:
279 True{}
280 case Dot{}:
281 False{}
282 case Tilde{}:
283 False{}
284 case Word{}:
285 False{}
286 case Other{}:
287 False{}
288
289def looks.slash(text: String) -> Bool:
290 match text:
291 case SNil{}:
292 False{}
293 case SCon{+h, _t}:
294 looks.slash.at(kind(h))
295
296
297def looks.dot.at(mark: Mark, rest: String) -> Bool:
298 match mark:
299 case Slash{}:
300 True{}
301 case Dot{}:
302 looks.slash(rest)
303 case Tilde{}:
304 False{}
305 case Word{}:
306 False{}
307 case Other{}:
308 False{}
309
310def looks.dot(text: String) -> Bool:
311 match text:
312 case SNil{}:
313 False{}
314 case SCon{+h, t}:
315 looks.dot.at(kind(h), t)
316
317
318def looks.first(mark: Mark, rest: String) -> Bool:
319 match mark:
320 case Slash{}:
321 True{}
322 case Dot{}:
323 looks.dot(rest)
324 case Tilde{}:
325 looks.slash(rest)
326 case Word{}:
327 False{}
328 case Other{}:
329 False{}
330
331
332def looks.path(text: String) -> Bool:
333 match text:
334 case SNil{}:
335 False{}
336 case SCon{+h, t}:
337 looks.first(kind(h), t)
338
339
340def classify.scp(scp: Bool, +text: String) -> Place:
341 match scp:
342 case True{}:
343 remote(text, slug.scp(text))
344 case False{}:
345 Local{text}
346
347
348
349
350def classify.hub(ok: Bool, +text: String) -> Place:
351 match ok:
352 case True{}:
353 Remote{"https://github.com/" ++ text, "github.com/" ++ ungit(text)}
354 case False{}:
355 classify.scp(String.starts_with(text, "git@"), text)
356
357
358def classify.path(local: Bool, +text: String) -> Place:
359 match local:
360 case True{}:
361 Local{text}
362 case False{}:
363 classify.hub(github.ok(text), text)
364
365
366def classify.url(url: Bool, +text: String) -> Place:
367 match url:
368 case True{}:
369 remote(text, slug.url(text))
370 case False{}:
371 classify.path(looks.path(text), text)
372
373
374def classify.empty(empty: Bool, +text: String) -> Place:
375 match empty:
376 case True{}:
377 Bad{"a target is needed"}
378 case False{}:
379 classify.url(String.contains(text, "://"), text)
380
381
382
383
384
385
386def classify(+text: String) -> Place:
387 classify.empty(String.is_empty(text), text)
388
389
390
391type Aim is Data:
392 Named{nv: String}
393 Placed{place: Place}
394
395
396
397def aim.local(named: Bool, +text: String, +path: String) -> Aim:
398 match named:
399 case True{}:
400 Named{text}
401 case False{}:
402 Placed{Local{path}}
403
404
405
406
407def aim.place(place: Place, +text: String) -> Aim:
408 match place:
409 case Bad{why}:
410 Placed{Bad{why}}
411 case Local{path}:
412 aim.local(K.name.ok(text), text, path)
413 case Remote{url, slug}:
414 Placed{Remote{url, slug}}
415
416
417
418
419def aim(+text: String) -> Aim:
420 aim.place(classify(text), text)
421
422
423def aim.named(aim: Aim) -> Bool:
424 match aim:
425 case Named{_nv}:
426 True{}
427 case Placed{_place}:
428 False{}
429
430
431def expand(+home: String, +path: String) -> String:
432 Bool.pick(String, String.eq(path, "~"), home,
433 Bool.pick(String, String.starts_with(path, "~/"),
434 home ++ String.drop(path, 1n), path))
435
436
437def entry.or(+path: String) -> String:
438 Bool.pick(String, String.is_empty(path), "main.bend", path)
439
440
441def file.of(+bin: String, +entry: String) -> String:
442 Bool.pick(String, String.is_empty(bin), entry.or(entry), bin)
443
444
445def out.name(+name: String) -> String:
446 Bool.pick(String, String.is_empty(name), "app", name)
447
448
449def show(place: Place) -> String:
450 match place:
451 case Bad{why}:
452 "bad " ++ why
453 case Local{path}:
454 "local " ++ path
455 case Remote{url, slug}:
456 "remote " ++ url ++ " " ++ slug