src/pub/blurb.bend

ezx@1.8.0.0 · 124 lines · 0 definitions

1# pub/blurb: the line the hub shows as a package's description, pure. The
2# hub takes the first line of the package's first file by path, in plain
3# string order (so an uppercase letter comes before every lowercase one), and
4# passes over a file named LICENSE, which bend 2.0.27 sends beside the files
5# it publishes. `ez publish` says this line before it uploads (pub/plan.bend),
6# and `ez init` opens the entry it writes with a line of its own for it
7# (init/plan.bend), so the line a project is shown by on the hub is one its
8# author wrote.
9#
10# Paths are compared with `String.is_le`, the order the package walk writes a
11# manifest in (`K.file.le`). It compares characters by code point, which is
12# JavaScript's order for every path whose characters are all in the Basic
13# Multilingual Plane.
14import Base
15import ezx@1.8.0.0/src/pkg/pkg.bend as K
16import ezx@1.8.0.0/src/pkg/path.bend as Path
17
18# one step of a scan for a newline, with the head's answer in hand; the rest
19# arrives as a thunk, so the first newline ends the scan
20def nl.step(here: Bool, rest: Unit -> Bool) -> Bool:
21  match here:
22    case True{}:
23      True{}
24    case False{}:
25      rest(Unit{})
26
27# whether a text holds a newline, so is more than one line
28def has.nl(text: String) -> Bool:
29  match text:
30    case SNil{}:
31      False{}
32    case SCon{+h, t}:
33      nl.step(Char.is_eq(h, '\n'), _u => has.nl(t))
34
35# one step of the first line: a newline ends it, and any other character
36# starts what the rest of the line is
37def line.step(end: Bool, +head: Char, rest: Unit -> String) -> String:
38  match end:
39    case True{}:
40      SNil{}
41    case False{}:
42      SCon{head, rest(Unit{})}
43
44# a text's first line: everything before its first newline, the whole text
45# when it has none
46def line.first(text: String) -> String:
47  match text:
48    case SNil{}:
49      SNil{}
50    case SCon{+h, t}:
51      line.step(Char.is_eq(h, '\n'), h, _u => line.first(t))
52
53# whether a file of a package is a LICENSE, which the hub passes over
54def lic(file: K.Source) -> Bool:
55  K.Source{at, _text} = file
56  String.eq(Path.base(at), "LICENSE")
57
58# where a file stands in the hub's order
59def at(file: K.Source) -> String:
60  K.Source{a, _text} = file
61  a
62
63# the earlier of the file found so far and the next one; a tie keeps the one
64# found first
65def least.vs(cur: Maybe<&2, K.Source>, +next: K.Source) -> Maybe<&2, K.Source>:
66  match cur:
67    case None{}:
68      Some{next}
69    case Some{+c}:
70      Bool.pick(Maybe<&2, K.Source>, String.is_le(at(c), at(next)), Some{c}, Some{next})
71
72# the next file weighed: a LICENSE is passed over, and any other file is the
73# earlier of itself and the one found so far
74def least.pick(skip: Bool, cur: Maybe<&2, K.Source>, +next: K.Source) -> Maybe<&2, K.Source>:
75  match skip:
76    case True{}:
77      cur
78    case False{}:
79      least.vs(cur, next)
80
81# the first file by path among these and the one found so far, LICENSE files
82# passed over
83def least(fs: List<&2, K.Source>, cur: Maybe<&2, K.Source>) -> Maybe<&2, K.Source>:
84  match fs:
85    case []:
86      cur
87    case +h <> t:
88      least(t, least.pick(lic(h), cur, h))
89
90# a file's first line
91def line.file(file: K.Source) -> String:
92  K.Source{_at, text} = file
93  line.first(text)
94
95# the first line of the file found, "" when there is none
96def line.of(got: Maybe<&2, K.Source>) -> String:
97  match got:
98    case None{}:
99      ""
100    case Some{f}:
101      line.file(f)
102
103# the line the hub describes a package by: the first line of its first file
104# by path that is not a LICENSE
105def blurb(fs: List<&2, K.Source>) -> String:
106  line.of(least(fs, None{}))
107
108# one step of the walk below, with the head's answer in hand; the rest
109# arrives as a thunk, so the first file that comes earlier ends the walk
110def after.step(here: Bool, rest: Unit -> Bool) -> Bool:
111  match here:
112    case True{}:
113      rest(Unit{})
114    case False{}:
115      False{}
116
117# whether every file of these comes no earlier than a path, a LICENSE
118# included. Law vocabulary: a file whose path is this one comes first.
119def after(+path: String, fs: List<&2, K.Source>) -> Bool:
120  match fs:
121    case []:
122      True{}
123    case +h <> t:
124      after.step(String.is_le(path, at(h)), _u => after(path, t))