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. 14 import Base 15 import ../pkg/pkg.bend as K 16 import ../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 20 def 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 28 def 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 37 def 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 46 def 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 54 def 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 59 def 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 65 def 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 74 def 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 83 def 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 91 def 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 96 def 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 105 def 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 110 def 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. 119 def 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))