src/git/git.bend
ezx@1.11.0.0 · 787 lines · 110 definitions
1
2
3
4
5
6
7
8
9
10
11
12
13import Base
14import ezx@1.11.0.0/src/io/file.bend as F
15import ezx@1.11.0.0/src/toml/toml.bend as T
16import ezx@1.11.0.0/src/ledger/manifest.bend as M
17import ezx@1.11.0.0/src/pkg/pkg.bend as K
18import ezx@1.11.0.0/src/pkg/path.bend as P
19
20
21type Row is Data:
22 Row{rev: String, name: String}
23
24
25
26type Ord is Data:
27 Lt{}
28 Eq{}
29 Gt{}
30
31
32type Id is Data:
33 IdNum{n: Nat}
34 IdWord{s: String}
35
36
37
38type Ver is Data:
39 No{}
40 Yes{nums: List<&2, Nat>, pre: List<&2, Id>}
41
42
43type Cut is Data:
44 Cut{left: String, right: String}
45
46
47
48
49
50
51def among.step(here: Bool, rest: Unit -> Bool) -> Bool:
52 match here:
53 case True{}:
54 True{}
55 case False{}:
56 rest(Unit{})
57
58
59
60
61def among(+ch: Char, cs: List<&2, Char>) -> Bool:
62 match cs:
63 case []:
64 False{}
65 case +h <> t:
66 among.step(Char.is_eq(h, ch), _u => among(ch, t))
67
68
69def hex.at(ch: Char) -> Bool:
70 among(ch, String.to_list("0123456789abcdef"))
71
72
73
74
75def hex.all.step(here: Bool, rest: Unit -> Bool) -> Bool:
76 match here:
77 case True{}:
78 rest(Unit{})
79 case False{}:
80 False{}
81
82
83
84
85def hex.all(cs: List<&2, Char>) -> Bool:
86 match cs:
87 case []:
88 True{}
89 case +h <> t:
90 hex.all.step(hex.at(h), _u => hex.all(t))
91
92
93
94
95def is_rev(+ref: String) -> Bool:
96 Bool.and(Nat.is_eq(String.length(ref), 40n), hex.all(String.to_list(ref)))
97
98
99def row.rev(line: Row) -> String:
100 Row{rev, _name} = line
101 rev
102
103
104def row.name(line: Row) -> String:
105 Row{_rev, name} = line
106 name
107
108
109def row_of(+text: String) -> Row:
110 Row{K.word(String.split(text, '\t'), 0n), K.word(String.split(text, '\t'), 1n)}
111
112
113def row.put(+text: String) -> List<&2, Row>:
114 Bool.pick(List<&2, Row>, String.is_empty(String.trim(text)), [], [row_of(text)])
115
116
117def rows(ls: List<&2, String>) -> List<&2, Row>:
118 match ls:
119 case []:
120 []
121 case h <> t:
122 List.append(&2, Row, row.put(h), rows(t))
123
124
125
126
127
128def named.step(here: Bool, rev: String, rest: Unit -> String) -> String:
129 match here:
130 case True{}:
131 rev
132 case False{}:
133 rest(Unit{})
134
135
136
137
138
139def named(rs: List<&2, Row>, +want: String) -> String:
140 match rs:
141 case []:
142 ""
143 case +h <> t:
144 named.step(String.eq(row.name(h), want), row.rev(h), _u => named(t, want))
145
146
147def or_else(+primary: String, fallback: String) -> String:
148 Bool.pick(String, String.is_empty(primary), fallback, primary)
149
150
151
152
153
154
155def exact(+rs: List<&2, Row>, +ref: String) -> String:
156 or_else(named(rs, "refs/tags/" ++ ref ++ "^{}"),
157 or_else(named(rs, "refs/tags/" ++ ref), named(rs, "refs/heads/" ++ ref)))
158
159
160type Tip is Data:
161 Tip{rev: String, branch: String}
162
163
164
165
166def tip.step(empty: Bool, +got: String, rest: Unit -> String) -> String:
167 match empty:
168 case True{}:
169 rest(Unit{})
170 case False{}:
171 got
172
173
174def tip.rev.hex(hex: Bool, rev: String) -> String:
175 match hex:
176 case True{}:
177 rev
178 case False{}:
179 ""
180
181
182def tip.rev.pick(head: Bool, hex: Bool, rev: String) -> String:
183 match head:
184 case False{}:
185 ""
186 case True{}:
187 tip.rev.hex(hex, rev)
188
189
190def tip.rev.at(line: Row) -> String:
191 Row{+rev, name} = line
192 tip.rev.pick(String.eq(name, "HEAD"), is_rev(rev), rev)
193
194
195def tip.rev(rs: List<&2, Row>) -> String:
196 match rs:
197 case []:
198 ""
199 case +h <> t:
200 tip.step(String.is_empty(tip.rev.at(h)), tip.rev.at(h), _u => tip.rev(t))
201
202
203
204def tip.strip(+text: String, +pre: String) -> String:
205 Bool.pick(String, String.starts_with(text, pre),
206 String.drop(text, String.length(pre)), "")
207
208
209def tip.sym(+rev: String) -> String:
210 tip.strip(tip.strip(rev, "ref: "), "refs/heads/")
211
212
213def tip.branch.pick(head: Bool, rev: String) -> String:
214 match head:
215 case False{}:
216 ""
217 case True{}:
218 tip.sym(rev)
219
220
221def tip.branch.at(line: Row) -> String:
222 Row{rev, name} = line
223 tip.branch.pick(String.eq(name, "HEAD"), rev)
224
225
226def tip.branch(rs: List<&2, Row>) -> String:
227 match rs:
228 case []:
229 ""
230 case +h <> t:
231 tip.step(String.is_empty(tip.branch.at(h)), tip.branch.at(h), _u => tip.branch(t))
232
233
234def depth(path: String) -> Nat:
235 List.length(&2, String, String.split(P.norm(path), '/'))
236
237
238
239
240
241
242def root.rel(+at: String, root: String, +entry: String) -> String:
243 P.climb(Nat.sub(depth(P.dir(at)), depth(root)), P.dir(entry))
244
245
246def title.go(cs: List<&2, Char>) -> String:
247 match cs:
248 case []:
249 ""
250 case h <> t:
251 String.from_list(Char.to_upper(h) <> t)
252
253
254def title(text: String) -> String:
255 title.go(String.to_list(text))
256
257
258
259
260
261def import.line(+hash: String, +at: String) -> String:
262 "import " ++ hash ++ "/" ++ at ++ " as " ++ title(P.unbend(P.base(at)))
263
264
265
266def entry.main(no: Bool, +binary: String) -> String:
267 match no:
268 case False{}:
269 binary
270 case True{}:
271 "main.bend"
272
273
274
275def entry.bin(no: Bool, +file: String, +binary: String) -> String:
276 match no:
277 case False{}:
278 file
279 case True{}:
280 entry.main(String.is_empty(binary), binary)
281
282
283def entry.pick(+file: String, +binary: String) -> String:
284 entry.bin(String.is_empty(file), file, binary)
285
286
287def entry.sects(ok: Bool, ss: List<&2, T.Sect>) -> String:
288 match ok:
289 case False{}:
290 "main.bend"
291 case True{}:
292 +ps = M.pairs_of(ss, "package")
293 entry.pick(M.value(ps, "entry"), M.value(ps, "bin"))
294
295
296def entry.doc(toml: T.Toml) -> String:
297 T.Toml{bad, _name, _pairs, sects} = toml
298 entry.sects(String.is_empty(bad), sects)
299
300
301def entry.text(+text: String) -> String:
302 entry.doc(T.parse(text))
303
304
305def entry.maybe(got: Maybe<&2, String>) -> String:
306 match got:
307 case None{}:
308 "main.bend"
309 case Some{text}:
310 entry.text(text)
311
312
313def entry.at(+work: String) -> IO(String):
314 do IO<String>:
315 m : Maybe<&2, String> <- F.read(work ++ "/ez.toml")
316 return entry.maybe(m)
317
318
319def called.sects(ok: Bool, ss: List<&2, T.Sect>) -> String:
320 match ok:
321 case False{}:
322 ""
323 case True{}:
324 M.value(M.pairs_of(ss, "package"), "name")
325
326
327def called.doc(toml: T.Toml) -> String:
328 T.Toml{bad, _name, _pairs, sects} = toml
329 called.sects(String.is_empty(bad), sects)
330
331
332def called.maybe(got: Maybe<&2, String>) -> String:
333 match got:
334 case None{}:
335 ""
336 case Some{text}:
337 called.doc(T.parse(text))
338
339
340def entry.for(given: Bool, +entry: String, +work: String) -> IO(String):
341 match given:
342 case True{}:
343 IO.pure(String, entry)
344 case False{}:
345 entry.at(work)
346
347
348
349
350
351
352
353
354def ord.lt(lt: Bool) -> Ord:
355 match lt:
356 case True{}:
357 Lt{}
358 case False{}:
359 Gt{}
360
361def ord.nat(eq: Bool, lt: Bool) -> Ord:
362 match eq:
363 case True{}:
364 Eq{}
365 case False{}:
366 ord.lt(lt)
367
368def ord.str(eq: Bool, le: Bool) -> Ord:
369 match eq:
370 case True{}:
371 Eq{}
372 case False{}:
373 ord.lt(le)
374
375def ord.flip(order: Ord) -> Ord:
376 match order:
377 case Lt{}:
378 Gt{}
379 case Gt{}:
380 Lt{}
381 case Eq{}:
382 Eq{}
383
384def ord.pad.at(zero: Bool, rest: Unit -> Ord) -> Ord:
385 match zero:
386 case False{}:
387 Lt{}
388 case True{}:
389 rest(Unit{})
390
391def ord.pad(xs: List<&2, Nat>) -> Ord:
392 match xs:
393 case Nil{}:
394 Eq{}
395 case Con{+h, t}:
396 ord.pad.at(Nat.is_eq(h, 0n), _u => ord.pad(t))
397
398def ord.nums.at(order: Ord, rest: Unit -> Ord) -> Ord:
399 match order:
400 case Eq{}:
401 rest(Unit{})
402 case Lt{}:
403 Lt{}
404 case Gt{}:
405 Gt{}
406
407def ord.nums(left: List<&2, Nat>, right: List<&2, Nat>) -> Ord:
408 match left right:
409 case Nil{} Nil{}:
410 Eq{}
411 case Nil{} Con{+h, t}:
412 ord.pad(h <> t)
413 case Con{+h, t} Nil{}:
414 ord.flip(ord.pad(h <> t))
415 case Con{+ha, ta} Con{+hb, tb}:
416 ord.nums.at(ord.nat(Nat.is_eq(ha, hb), Nat.is_lt(ha, hb)),
417 _u => ord.nums(ta, tb))
418
419def ord.id(left: Id, right: Id) -> Ord:
420 match left right:
421 case IdNum{+n} IdNum{+m}:
422 ord.nat(Nat.is_eq(n, m), Nat.is_lt(n, m))
423 case IdNum{_n} IdWord{_s}:
424 Lt{}
425 case IdWord{_s} IdNum{_n}:
426 Gt{}
427 case IdWord{+s} IdWord{+t}:
428 ord.str(String.eq(s, t), String.is_le(s, t))
429
430def ord.ids(left: List<&2, Id>, right: List<&2, Id>) -> Ord:
431 match left right:
432 case Nil{} Nil{}:
433 Eq{}
434 case Nil{} Con{_h, _t}:
435 Lt{}
436 case Con{_h, _t} Nil{}:
437 Gt{}
438 case Con{ha, ta} Con{hb, tb}:
439 ord.nums.at(ord.id(ha, hb), _u => ord.ids(ta, tb))
440
441def ord.rel.at(ae: Bool, be: Bool, left: List<&2, Id>, right: List<&2, Id>) -> Ord:
442 match ae be:
443 case True{} True{}:
444 Eq{}
445 case True{} False{}:
446 Gt{}
447 case False{} True{}:
448 Lt{}
449 case False{} False{}:
450 ord.ids(left, right)
451
452def ord.rel(+left: List<&2, Id>, +right: List<&2, Id>) -> Ord:
453 ord.rel.at(List.is_empty(&2, Id, left), List.is_empty(&2, Id, right), left, right)
454
455def ord.ver.at(order: Ord, pa: List<&2, Id>, pb: List<&2, Id>) -> Ord:
456 ord.nums.at(order, _u => ord.rel(pa, pb))
457
458def ord.ver(left: Ver, right: Ver) -> Ord:
459 match left right:
460 case Yes{na, pa} Yes{nb, pb}:
461 ord.ver.at(ord.nums(na, nb), pa, pb)
462 case No{} _:
463 Eq{}
464 case Yes{_na, _pa} No{}:
465 Eq{}
466
467
468def dig(ch: Char) -> Nat:
469 U32.to_nat(U32.sub(Char.to_u32(ch), 48))
470
471def num.go(text: String, +acc: Nat) -> Nat:
472 match text:
473 case SNil{}:
474 acc
475 case SCon{+h, t}:
476 num.go(t, Nat.add(Nat.mul(acc, 10n), dig(h)))
477
478def flag.step(ok: Bool, rest: Unit -> Bool) -> Bool:
479 match ok:
480 case False{}:
481 False{}
482 case True{}:
483 rest(Unit{})
484
485def digits.go(text: String) -> Bool:
486 match text:
487 case SNil{}:
488 True{}
489 case SCon{+h, t}:
490 flag.step(Char.is_digit(h), _u => digits.go(t))
491
492def digits.at(empty: Bool, +text: String) -> Bool:
493 match empty:
494 case True{}:
495 False{}
496 case False{}:
497 digits.go(text)
498
499
500def digits(+text: String) -> Bool:
501 digits.at(String.is_empty(text), text)
502
503def nums.cons(value: Nat, rest: Maybe<&2, List<&2, Nat>>) -> Maybe<&2, List<&2, Nat>>:
504 match rest:
505 case None{}:
506 None{}
507 case Some{ns}:
508 Some{value <> ns}
509
510def nums.put(ok: Bool, +part: String, rest: Maybe<&2, List<&2, Nat>>) -> Maybe<&2, List<&2, Nat>>:
511 match ok:
512 case False{}:
513 None{}
514 case True{}:
515 nums.cons(num.go(part, 0n), rest)
516
517def nums.of(ps: List<&2, String>) -> Maybe<&2, List<&2, Nat>>:
518 match ps:
519 case Nil{}:
520 Some{[]}
521 case Con{+h, t}:
522 nums.put(digits(h), h, nums.of(t))
523
524def id.char(+ch: Char) -> Bool:
525 Bool.or(Char.is_digit(ch), Bool.or(Char.is_alpha(ch), Char.is_eq(ch, '-')))
526
527def id.chars(text: String) -> Bool:
528 match text:
529 case SNil{}:
530 True{}
531 case SCon{+h, t}:
532 flag.step(id.char(h), _u => id.chars(t))
533
534def id.make(num: Bool, +text: String) -> Id:
535 match num:
536 case True{}:
537 IdNum{num.go(text, 0n)}
538 case False{}:
539 IdWord{text}
540
541def ids.cons(ident: Id, rest: Maybe<&2, List<&2, Id>>) -> Maybe<&2, List<&2, Id>>:
542 match rest:
543 case None{}:
544 None{}
545 case Some{xs}:
546 Some{ident <> xs}
547
548def ids.ok(ok: Bool, num: Bool, +part: String, rest: Maybe<&2, List<&2, Id>>) -> Maybe<&2, List<&2, Id>>:
549 match ok:
550 case False{}:
551 None{}
552 case True{}:
553 ids.cons(id.make(num, part), rest)
554
555def ids.put(empty: Bool, ok: Bool, num: Bool, +part: String, rest: Maybe<&2, List<&2, Id>>) -> Maybe<&2, List<&2, Id>>:
556 match empty:
557 case True{}:
558 None{}
559 case False{}:
560 ids.ok(ok, num, part, rest)
561
562def ids.of(ps: List<&2, String>) -> Maybe<&2, List<&2, Id>>:
563 match ps:
564 case Nil{}:
565 Some{[]}
566 case Con{+h, t}:
567 ids.put(String.is_empty(h), id.chars(h), digits(h), h, ids.of(t))
568
569def pre.parts(empty: Bool, +raw: String) -> Maybe<&2, List<&2, Id>>:
570 match empty:
571 case True{}:
572 None{}
573 case False{}:
574 ids.of(String.split(raw, '.'))
575
576def pre.of(had: Bool, +raw: String) -> Maybe<&2, List<&2, Id>>:
577 match had:
578 case False{}:
579 Some{[]}
580 case True{}:
581 pre.parts(String.is_empty(raw), raw)
582
583def split1.put(ch: Char, cut: Cut) -> Cut:
584 Cut{left, right} = cut
585 Cut{SCon{ch, left}, right}
586
587def split1.step(hit: Bool, ch: Char, +tail: String, rest: Unit -> Cut) -> Cut:
588 match hit:
589 case True{}:
590 Cut{"", tail}
591 case False{}:
592 split1.put(ch, rest(Unit{}))
593
594
595
596def split1(+sep: Char, text: String) -> Cut:
597 match text:
598 case SNil{}:
599 Cut{"", ""}
600 case SCon{+h, +t}:
601 split1.step(Char.is_eq(h, sep), h, t, _u => split1(sep, t))
602
603def unv.step(drop: Bool, ch: Char, tail: String) -> String:
604 match drop:
605 case True{}:
606 tail
607 case False{}:
608 SCon{ch, tail}
609
610
611def unv(text: String) -> String:
612 match text:
613 case SNil{}:
614 ""
615 case SCon{+h, t}:
616 unv.step(Bool.or(Char.is_eq(h, 'v'), Char.is_eq(h, 'V')), h, t)
617
618def ver.yes(empty: Bool, xs: List<&2, Nat>, ys: List<&2, Id>) -> Ver:
619 match empty:
620 case True{}:
621 No{}
622 case False{}:
623 Yes{xs, ys}
624
625def ver.pre(+xs: List<&2, Nat>, ps: Maybe<&2, List<&2, Id>>) -> Ver:
626 match ps:
627 case None{}:
628 No{}
629 case Some{ys}:
630 ver.yes(List.is_empty(&2, Nat, xs), xs, ys)
631
632def ver.nums(had: Bool, ns: Maybe<&2, List<&2, Nat>>, +pre: String) -> Ver:
633 match ns:
634 case None{}:
635 No{}
636 case Some{xs}:
637 ver.pre(xs, pre.of(had, pre))
638
639def ver.from(empty: Bool, had: Bool, +core: String, +pre: String) -> Ver:
640 match empty:
641 case True{}:
642 No{}
643 case False{}:
644 ver.nums(had, nums.of(String.split(core, '.')), pre)
645
646def ver.cut(had: Bool, cut: Cut) -> Ver:
647 Cut{+core, +pre} = cut
648 ver.from(String.is_empty(core), had, core, pre)
649
650def ver.main(+main: String) -> Ver:
651 ver.cut(String.contains(main, "-"), split1('-', main))
652
653def ver.core(cut: Cut) -> Ver:
654 Cut{+main, _build} = cut
655 ver.main(main)
656
657
658def ver.of(+tag: String) -> Ver:
659 ver.core(split1('+', unv(tag)))
660
661
662def newer(order: Ord) -> Bool:
663 match order:
664 case Gt{}:
665 True{}
666 case Lt{}:
667 False{}
668 case Eq{}:
669 False{}
670
671def latest.cmp(take: Bool, +name: String, +rest: String) -> String:
672 match take:
673 case True{}:
674 name
675 case False{}:
676 rest
677
678def latest.have(empty: Bool, +name: String, +rest: String, version: Ver) -> String:
679 match empty:
680 case True{}:
681 name
682 case False{}:
683 latest.cmp(newer(ord.ver(version, ver.of(rest))), name, rest)
684
685def latest.step(version: Ver, +name: String, +rest: String) -> String:
686 match version:
687 case No{}:
688 rest
689 case Yes{ns, ps}:
690 latest.have(String.is_empty(rest), name, rest, Yes{ns, ps})
691
692
693def latest(names: List<&2, String>) -> String:
694 match names:
695 case Nil{}:
696 ""
697 case Con{+h, t}:
698 latest.step(ver.of(h), h, latest(t))
699
700
701def release.ver(version: Ver) -> Bool:
702 match version:
703 case No{}:
704 False{}
705 case Yes{_ns, ps}:
706 List.is_empty(&2, Id, ps)
707
708
709def release(+name: String) -> Bool:
710 release.ver(ver.of(name))
711
712
713
714def latest.rel.step(keep: Bool, +name: String, +rest: String) -> String:
715 match keep:
716 case False{}:
717 rest
718 case True{}:
719 latest.have(String.is_empty(rest), name, rest, ver.of(name))
720
721
722def latest.rel(names: List<&2, String>) -> String:
723 match names:
724 case Nil{}:
725 ""
726 case Con{+h, t}:
727 latest.rel.step(release(h), h, latest.rel(t))
728
729
730def has.release.step(here: Bool, rest: Unit -> Bool) -> Bool:
731 match here:
732 case True{}:
733 True{}
734 case False{}:
735 rest(Unit{})
736
737
738def has.release(names: List<&2, String>) -> Bool:
739 match names:
740 case Nil{}:
741 False{}
742 case Con{+h, t}:
743 has.release.step(release(h), _u => has.release(t))
744
745
746
747def choose.go(none: Bool, +rel: String, +any: String) -> String:
748 match none:
749 case True{}:
750 any
751 case False{}:
752 rel
753
754
755
756
757
758def choose(+names: List<&2, String>) -> String:
759 choose.go(String.is_empty(latest.rel(names)), latest.rel(names), latest(names))
760
761
762
763def tag.put(hit: Bool, +name: String) -> List<&2, String>:
764 match hit:
765 case False{}:
766 []
767 case True{}:
768 [String.drop(name, String.length("refs/tags/"))]
769
770def tag.keep(peeled: Bool, +name: String) -> List<&2, String>:
771 match peeled:
772 case True{}:
773 []
774 case False{}:
775 tag.put(String.starts_with(name, "refs/tags/"), name)
776
777def tag.one(line: Row) -> List<&2, String>:
778 Row{_rev, +name} = line
779 tag.keep(String.ends_with(name, "^{}"), name)
780
781
782def tag.names(rs: List<&2, Row>) -> List<&2, String>:
783 match rs:
784 case Nil{}:
785 []
786 case Con{+h, t}:
787 List.append(&2, String, tag.one(h), tag.names(t))