src/ws/workspace.bend
ezx@1.11.0.0 · 565 lines · 67 definitions
1
2
3
4
5
6
7
8
9
10
11
12
13
14import Base
15import emerging-eztoml@0.9.0.0/main.bend as E
16import ezx@1.11.0.0/src/toml/toml.bend as T
17
18
19def segs.of(+path: String) -> List<&2, String>:
20 String.split(path, '/')
21
22
23def segs.text(ss: List<&2, String>) -> String:
24 String.join(ss, "/")
25
26
27def cut.cons(+head: String, rest: Maybe<&2, List<&2, String>>) -> Maybe<&2, List<&2, String>>:
28 match rest:
29 case None{}:
30 None{}
31 case Some{ds}:
32 Some{head <> ds}
33
34
35def cut.last(hit: Bool) -> Maybe<&2, List<&2, String>>:
36 match hit:
37 case True{}:
38 Some{[]}
39 case False{}:
40 None{}
41
42
43
44
45def cut(ss: List<&2, String>, +word: String) -> Maybe<&2, List<&2, String>>:
46 match ss:
47 case []:
48 None{}
49 case +hd <> []:
50 cut.last(String.eq(hd, word))
51 case +hd <> tl:
52 cut.cons(hd, cut(tl, word))
53
54
55
56
57
58
59type Entry is Data:
60 Plain{dir: List<&2, String>}
61 Under{dir: List<&2, String>}
62
63
64def entry.go(under: Maybe<&2, List<&2, String>>, +ss: List<&2, String>) -> Entry:
65 match under:
66 case Some{ds}:
67 Under{ds}
68 case None{}:
69 Plain{ss}
70
71
72
73def entry.of(+ss: List<&2, String>) -> Entry:
74 entry.go(cut(ss, "*"), ss)
75
76
77def entry.dir(entry: Entry) -> List<&2, String>:
78 match entry:
79 case Plain{ds}:
80 ds
81 case Under{ds}:
82 ds
83
84
85def entry.text(entry: Entry) -> String:
86 match entry:
87 case Plain{ds}:
88 segs.text(ds)
89 case Under{ds}:
90 segs.text(ds) ++ "/*"
91
92
93
94
95
96def seg.ok(+seg: String) -> Bool:
97 Bool.and(Bool.not(String.is_empty(seg)),
98 Bool.and(Bool.not(String.eq(seg, ".")),
99 Bool.and(Bool.not(String.eq(seg, "..")), Bool.not(String.eq(seg, "*")))))
100
101
102def segs.ok(ss: List<&2, String>) -> Bool:
103 match ss:
104 case []:
105 True{}
106 case +hd <> tl:
107 rest = segs.ok(tl)
108 Bool.and(seg.ok(hd), rest)
109
110
111
112def dir.ok(+ss: List<&2, String>) -> Bool:
113 Bool.and(Bool.not(List.is_empty(&2, String, ss)), segs.ok(ss))
114
115
116def entries.ok(es: List<&2, Entry>) -> Bool:
117 match es:
118 case []:
119 True{}
120 case +hd <> tl:
121 rest = entries.ok(tl)
122 Bool.and(dir.ok(entry.dir(hd)), rest)
123
124
125def entries.bad(es: List<&2, Entry>) -> String:
126 match es:
127 case []:
128 ""
129 case +hd <> tl:
130 rest = entries.bad(tl)
131 Bool.pick(String, dir.ok(entry.dir(hd)), rest, entry.text(hd))
132
133
134
135
136
137def segs.eq(as: List<&2, String>, bs: List<&2, String>) -> Bool:
138 match as:
139 case []:
140 List.is_empty(&2, String, bs)
141 case +ah <> at:
142 match bs:
143 case []:
144 False{}
145 case +bh <> bt:
146 rest = segs.eq(at, bt)
147 Bool.and(String.eq(ah, bh), rest)
148
149
150def one(ds: List<&2, String>) -> Bool:
151 match ds:
152 case []:
153 False{}
154 case _hd <> tl:
155 List.is_empty(&2, String, tl)
156
157
158
159def under(ps: List<&2, String>, ds: List<&2, String>) -> Bool:
160 match ps:
161 case []:
162 one(ds)
163 case +ph <> pt:
164 match ds:
165 case []:
166 False{}
167 case +dh <> dt:
168 rest = under(pt, dt)
169 Bool.and(String.eq(ph, dh), rest)
170
171
172def hit(entry: Entry, +ds: List<&2, String>) -> Bool:
173 match entry:
174 case Plain{ps}:
175 segs.eq(ps, ds)
176 case Under{ps}:
177 under(ps, ds)
178
179
180def listed(es: List<&2, Entry>, +ds: List<&2, String>) -> Bool:
181 match es:
182 case []:
183 False{}
184 case +hd <> tl:
185 rest = listed(tl, ds)
186 Bool.or(hit(hd, ds), rest)
187
188
189def has(xs: List<&2, List<&2, String>>, +ds: List<&2, String>) -> Bool:
190 match xs:
191 case []:
192 False{}
193 case +hd <> tl:
194 rest = has(tl, ds)
195 Bool.or(segs.eq(hd, ds), rest)
196
197
198
199
200
201type Spec is Data:
202 Spec{members: List<&2, Entry>, exclude: List<&2, List<&2, String>>}
203
204
205
206
207type Root is Data:
208 Virtual{}
209 Package{name: String}
210
211
212
213
214type Ledger is Data:
215 Ledger{dir: List<&2, String>, name: String, nested: Bool}
216
217
218
219type Members is Data:
220 Refused{why: String}
221 Members{root: Bool, dirs: List<&2, List<&2, String>>}
222
223
224def ledger.dir(ledger: Ledger) -> List<&2, String>:
225 Ledger{ds, _n, _w} = ledger
226 ds
227
228
229def ledger.name(ledger: Ledger) -> String:
230 Ledger{_ds, nn, _w} = ledger
231 nn
232
233
234def ledger.nested(ledger: Ledger) -> Bool:
235 Ledger{_ds, _n, ww} = ledger
236 ww
237
238
239def dirs.of(ls: List<&2, Ledger>) -> List<&2, List<&2, String>>:
240 match ls:
241 case []:
242 []
243 case +hd <> tl:
244 ledger.dir(hd) <> dirs.of(tl)
245
246
247def chosen(+spec: Spec, +ds: List<&2, String>) -> Bool:
248 Spec{es, ex} = spec
249 Bool.and(listed(es, ds), Bool.not(has(ex, ds)))
250
251
252def picks.put(keep: Bool, +ledger: Ledger, rest: List<&2, Ledger>) -> List<&2, Ledger>:
253 match keep:
254 case True{}:
255 ledger <> rest
256 case False{}:
257 rest
258
259
260def picks(+spec: Spec, ls: List<&2, Ledger>) -> List<&2, Ledger>:
261 match ls:
262 case []:
263 []
264 case +hd <> tl:
265 picks.put(chosen(spec, ledger.dir(hd)), hd, picks(spec, tl))
266
267
268
269def present.one(entry: Entry, +found: List<&2, List<&2, String>>) -> Bool:
270 match entry:
271 case Plain{ps}:
272 has(found, ps)
273 case Under{_ps}:
274 True{}
275
276
277def present(es: List<&2, Entry>, +found: List<&2, List<&2, String>>) -> Bool:
278 match es:
279 case []:
280 True{}
281 case +hd <> tl:
282 rest = present(tl, found)
283 Bool.and(present.one(hd, found), rest)
284
285
286
287def absent(es: List<&2, Entry>, +found: List<&2, List<&2, String>>) -> String:
288 match es:
289 case []:
290 ""
291 case +hd <> tl:
292 rest = absent(tl, found)
293 Bool.pick(String, present.one(hd, found), rest, entry.text(hd))
294
295
296def unnested(ls: List<&2, Ledger>) -> Bool:
297 match ls:
298 case []:
299 True{}
300 case +hd <> tl:
301 rest = unnested(tl)
302 Bool.and(Bool.not(ledger.nested(hd)), rest)
303
304
305def nested(ls: List<&2, Ledger>) -> String:
306 match ls:
307 case []:
308 ""
309 case +hd <> tl:
310 rest = nested(tl)
311 Bool.pick(String, ledger.nested(hd), segs.text(ledger.dir(hd)), rest)
312
313
314def names.of(ls: List<&2, Ledger>) -> List<&2, String>:
315 match ls:
316 case []:
317 []
318 case +hd <> tl:
319 ledger.name(hd) <> names.of(tl)
320
321
322def names(root: Root, ls: List<&2, Ledger>) -> List<&2, String>:
323 match root:
324 case Virtual{}:
325 names.of(ls)
326 case Package{nn}:
327 nn <> names.of(ls)
328
329
330def named(ns: List<&2, String>, +nn: String) -> Bool:
331 match ns:
332 case []:
333 False{}
334 case +hd <> tl:
335 rest = named(tl, nn)
336 Bool.or(String.eq(hd, nn), rest)
337
338
339def distinct(ns: List<&2, String>) -> Bool:
340 match ns:
341 case []:
342 True{}
343 case +hd <> +tl:
344 rest = distinct(tl)
345 Bool.and(Bool.not(named(tl, hd)), rest)
346
347
348def twice(ns: List<&2, String>) -> String:
349 match ns:
350 case []:
351 ""
352 case +hd <> +tl:
353 rest = twice(tl)
354 Bool.pick(String, named(tl, hd), hd, rest)
355
356
357def root.member(root: Root) -> Bool:
358 match root:
359 case Virtual{}:
360 False{}
361 case Package{_nn}:
362 True{}
363
364
365
366
367
368def ok(+spec: Spec, +root: Root, +ls: List<&2, Ledger>) -> Bool:
369 Spec{es, _ex} = spec
370 Bool.and(entries.ok(es),
371 Bool.and(present(es, dirs.of(ls)),
372 Bool.and(unnested(picks(spec, ls)), distinct(names(root, picks(spec, ls))))))
373
374
375def why(+spec: Spec, +root: Root, +ls: List<&2, Ledger>) -> String:
376 Spec{es, _ex} = spec
377 +bad = entries.bad(es)
378 +gone = absent(es, dirs.of(ls))
379 +deep = nested(picks(spec, ls))
380 +dup = twice(names(root, picks(spec, ls)))
381 Bool.pick(String, String.is_empty(bad),
382 Bool.pick(String, String.is_empty(gone),
383 Bool.pick(String, String.is_empty(deep),
384 "ez: two workspace members are both named " ++ dup,
385 "ez: workspace member " ++ deep ++ " has a [workspace] table of its own"),
386 "ez: workspace member " ++ gone ++ " has no committed ez.toml"),
387 "ez: workspace member " ++ bad ++ " is not a directory under the root")
388
389
390def members.go(fine: Bool, +spec: Spec, +root: Root, +ls: List<&2, Ledger>) -> Members:
391 match fine:
392 case True{}:
393 Members{root.member(root), dirs.of(picks(spec, ls))}
394 case False{}:
395 Refused{why(spec, root, ls)}
396
397
398
399
400def members(+spec: Spec, +root: Root, +ls: List<&2, Ledger>) -> Members:
401 members.go(ok(spec, root, ls), spec, root, ls)
402
403
404
405
406
407
408type Read is Data:
409 Lone{}
410 Bad{why: String}
411 Root{spec: Spec}
412
413
414def strs(items: List<&2, E.Val>) -> Maybe<&2, List<&2, String>>:
415 match items:
416 case []:
417 Some{[]}
418 case E.VStr{text} <> tl:
419 cut.cons(text, strs(tl))
420 case E.VSpan{src, nn} <> tl:
421 cut.cons(E.string(E.VSpan{src, nn}), strs(tl))
422 case _hd <> tl:
423 None{}
424
425
426def array.val(val: E.Val) -> Maybe<&2, List<&2, String>>:
427 match val:
428 case E.VArr{items}:
429 strs(items)
430 case _:
431 None{}
432
433
434def array.at(hit: Bool, val: E.Val, rest: Maybe<&2, List<&2, String>>) -> Maybe<&2, List<&2, String>>:
435 match hit:
436 case True{}:
437 array.val(val)
438 case False{}:
439 rest
440
441
442
443def array(rows: List<&2, E.Val>, +key: String) -> Maybe<&2, List<&2, String>>:
444 match rows:
445 case []:
446 Some{[]}
447 case E.VPair{+name, val} <> tl:
448 array.at(String.eq(name, key), val, array(tl, key))
449 case _hd <> tl:
450 array(tl, key)
451
452
453def entries.of(ts: List<&2, String>) -> List<&2, Entry>:
454 match ts:
455 case []:
456 []
457 case +hd <> tl:
458 entry.of(segs.of(hd)) <> entries.of(tl)
459
460
461def paths.of(ts: List<&2, String>) -> List<&2, List<&2, String>>:
462 match ts:
463 case []:
464 []
465 case +hd <> tl:
466 segs.of(hd) <> paths.of(tl)
467
468
469def table.go(ms: Maybe<&2, List<&2, String>>, ex: Maybe<&2, List<&2, String>>) -> Read:
470 match ms:
471 case None{}:
472 Bad{"ez: [workspace] members is not a list of strings"}
473 case Some{mm}:
474 match ex:
475 case None{}:
476 Bad{"ez: [workspace] exclude is not a list of strings"}
477 case Some{xx}:
478 Root{Spec{entries.of(mm), paths.of(xx)}}
479
480
481def table(+rows: List<&2, E.Val>) -> Read:
482 table.go(array(rows, "members"), array(rows, "exclude"))
483
484
485def spec.at(hit: Bool, +sub: List<&2, E.Val>, rest: Read) -> Read:
486 match hit:
487 case True{}:
488 table(sub)
489 case False{}:
490 rest
491
492
493def spec.rows(rows: List<&2, E.Val>) -> Read:
494 match rows:
495 case []:
496 Lone{}
497 case E.VPair{+name, E.VHead{_path, +sub}} <> tl:
498 spec.at(String.eq(name, "workspace"), sub, spec.rows(tl))
499 case _hd <> tl:
500 spec.rows(tl)
501
502
503
504def spec.of(text: String) -> Read:
505 spec.rows(E.root(E.parse(text)))
506
507
508def sect.name(sect: T.Sect) -> String:
509 T.Sect{nn, _ps} = sect
510 nn
511
512
513def sect.has(ss: List<&2, T.Sect>, +name: String) -> Bool:
514 match ss:
515 case []:
516 False{}
517 case +hd <> tl:
518 rest = sect.has(tl, name)
519 Bool.or(String.eq(sect.name(hd), name), rest)
520
521
522def pairs.at(sect: T.Sect, rest: List<&2, T.Kv>, +name: String) -> List<&2, T.Kv>:
523 T.Sect{nn, ps} = sect
524 Bool.pick(List<&2, T.Kv>, String.eq(nn, name), ps, rest)
525
526
527def pairs.of(ss: List<&2, T.Sect>, +name: String) -> List<&2, T.Kv>:
528 match ss:
529 case []:
530 []
531 case hd <> tl:
532 pairs.at(hd, pairs.of(tl, name), name)
533
534
535
536def root.of(+text: String) -> Root:
537 +ss = T.sects(T.parse(text))
538 +nn = T.value(pairs.of(ss, "package"), "name")
539 Bool.pick(Root, sect.has(ss, "package"), Package{nn}, Virtual{})
540
541
542def ledger.some(ds: List<&2, String>, nn: String, ww: Bool) -> Maybe<&2, Ledger>:
543 match ds:
544 case []:
545 None{}
546 case +hd <> tl:
547 Some{Ledger{hd <> tl, nn, ww}}
548
549
550
551def ledger.go(dir: Maybe<&2, List<&2, String>>, nn: String, ww: Bool) -> Maybe<&2, Ledger>:
552 match dir:
553 case None{}:
554 None{}
555 case Some{ds}:
556 ledger.some(ds, nn, ww)
557
558
559
560
561
562def ledger.of(+path: String, +text: String) -> Maybe<&2, Ledger>:
563 +ss = T.sects(T.parse(text))
564 ledger.go(cut(segs.of(path), "ez.toml"), T.value(pairs.of(ss, "package"), "name"),
565 sect.has(ss, "workspace"))