1 # ez/named: the name `ez add` records a dependency under, in the order cargo 2 # names one. A source the ledger already holds keeps the name the ledger gave 3 # it. Otherwise a package that is an ez project is named by its own 4 # `[package] name`, when that is a TOML bare key; otherwise a repository by its 5 # own name, and a directory by the directory's. The entry file names nothing, 6 # because most packages call theirs `main.bend`, and two of them would share 7 # one `[deps.main]`. A name the ledger gives to another source is refused 8 # rather than written over. 9 import Base 10 import ../ledger/manifest.bend as M 11 import ../ez/target.bend as Tgt 12 import ../pkg/pkg.bend as K 13 14 # a text walked from its end: whether a slash has been passed yet, and what 15 # lies after the last one 16 type Seen is Data: 17 Seen{slash: Bool, last: String} 18 19 # one char in front of what was seen, when no slash has been passed yet 20 def last.at(slash: Bool, ch: Char, rest: String) -> Seen: 21 match slash: 22 case True{}: 23 Seen{True{}, rest} 24 case False{}: 25 Seen{False{}, SCon{ch, rest}} 26 27 # one char in front of what was seen. Once a slash has been passed, nothing 28 # further left changes the answer. 29 def last.step(+ch: Char, seen: Seen) -> Seen: 30 Seen{slash, rest} = seen 31 match slash: 32 case True{}: 33 Seen{True{}, rest} 34 case False{}: 35 last.at(Char.is_eq(ch, '/'), ch, rest) 36 37 # the text after its last slash, and whether it had one 38 def last(text: String) -> Seen: 39 match text: 40 case SNil{}: 41 Seen{False{}, ""} 42 case SCon{+h, t}: 43 last.step(h, last(t)) 44 45 # what lies after the last slash 46 def leaf.of(seen: Seen) -> String: 47 Seen{_slash, rest} = seen 48 rest 49 50 # the last component of a path, a slug or a url 51 def leaf(text: String) -> String: 52 leaf.of(last(text)) 53 54 # the name a source gives itself. A remote is its slug's last component, which 55 # is the repository with any `.git` already off; a path is the last component 56 # of the directory it was resolved to. 57 def called(place: Tgt.Place, +url: String) -> String: 58 match place: 59 case Tgt.Bad{_why}: 60 "" 61 case Tgt.Local{_path}: 62 leaf(url) 63 case Tgt.Remote{_url, slug}: 64 leaf(slug) 65 66 # one char of a key, with the rest still to be asked about. The rest arrives 67 # as a thunk, so a char that ends the key ends the walk. 68 def bare.step(ok: Bool, rest: Unit -> Bool) -> Bool: 69 match ok: 70 case True{}: 71 rest(Unit{}) 72 case False{}: 73 False{} 74 75 # the rest of a TOML bare key: letters, digits, `-` and `_` 76 def bare.rest(text: String) -> Bool: 77 match text: 78 case SNil{}: 79 True{} 80 case SCon{+h, t}: 81 bare.step(Tgt.plain.at(h), _u => bare.rest(t)) 82 83 # a TOML bare key: at least one char, each a letter, a digit, `-` or `_`. Only 84 # such a name can stand in `[deps.]` unquoted. 85 def bare(text: String) -> Bool: 86 match text: 87 case SNil{}: 88 False{} 89 case SCon{+h, t}: 90 bare.step(Tgt.plain.at(h), _u => bare.rest(t)) 91 92 # whether a source is this git url 93 def from(src: M.Source, +url: String) -> Bool: 94 match src: 95 case M.Hub{_named}: 96 False{} 97 case M.Git{u, _rev, _tag, _root, _nar, _vend}: 98 String.eq(u, url) 99 100 # a dependency's name when it is this source, or the answer further on 101 def had.at(same: Bool, name: String, rest: String) -> String: 102 match same: 103 case True{}: 104 name 105 case False{}: 106 rest 107 108 # the name the ledger already gives this url, first in document order, or the 109 # fresh one when it gives none. A re-add is then the same entry, whatever the 110 # ledger called it. 111 def had(ds: List<&2, M.Dep>, +url: String, fresh: String) -> String: 112 match ds: 113 case []: 114 fresh 115 case h <> t: 116 M.Dep{n, _h, _e, s} = h 117 had.at(from(s, url), n, had(t, url, fresh)) 118 119 # the package's own name when it is a bare key, or the name its source gives 120 # it. A name that could not stand as a key falls through, not refused. 121 def own(+pkg: String, +fresh: String) -> String: 122 Bool.pick(String, bare(pkg), pkg, fresh) 123 124 # the name a dependency is recorded under: the ledger's for this source, else 125 # the package's own, else its source's 126 def name(ds: List<&2, M.Dep>, +url: String, +pkg: String, +fresh: String) -> String: 127 had(ds, url, own(pkg, fresh)) 128 129 # the first dependency of this name, asked whether it is another source 130 def clash.at(hit: Bool, src: M.Source, +url: String, rest: Bool) -> Bool: 131 match hit: 132 case True{}: 133 Bool.not(from(src, url)) 134 case False{}: 135 rest 136 137 # whether the ledger gives this name to a source other than this url. A hub 138 # dependency of the name is another source too. 139 def clash(ds: List<&2, M.Dep>, +name: String, +url: String) -> Bool: 140 match ds: 141 case []: 142 False{} 143 case h <> t: 144 M.Dep{n, _h, _e, s} = h 145 clash.at(String.eq(n, name), s, url, clash(t, name, url)) 146 147 # why a name cannot be recorded, or nothing when it can 148 def why(ds: List<&2, M.Dep>, +name: String, +url: String) -> String: 149 Bool.pick(String, String.is_empty(name), 150 "ez: " ++ url ++ " has no name to record it under", 151 Bool.pick(String, clash(ds, name, url), 152 "ez: ez.toml already has a deps." ++ name ++ " from another source; " ++ 153 "remove it first, or add this one with --rename", 154 "")) 155 156 # `--rename`, the way `cargo add --rename` works: when it is given, it is the 157 # name, ahead of every rule above 158 def as(ds: List<&2, M.Dep>, +url: String, +rename: String, +pkg: String, +fresh: String) -> String: 159 +named = name(ds, url, pkg, fresh) 160 Bool.pick(String, String.is_empty(rename), named, rename) 161 162 # a `--rename` that can stand as a key, or none at all 163 def renamed.ok(+rename: String) -> Bool: 164 Bool.or(String.is_empty(rename), bare(rename)) 165 166 # whether a `--rename` gives a second name to a source the ledger already 167 # records as `old`, "" when it records none 168 def moved.of(+old: String, +rename: String) -> Bool: 169 Bool.and(Bool.not(String.is_empty(rename)), 170 Bool.and(Bool.not(String.is_empty(old)), Bool.not(String.eq(old, rename)))) 171 172 # whether a `--rename` would record a source the ledger already names under a 173 # second name. That is refused rather than duplicated or silently renamed. 174 def moved(ds: List<&2, M.Dep>, +url: String, +rename: String) -> Bool: 175 moved.of(had(ds, url, ""), rename) 176 177 # why a name cannot be recorded when `--rename` may have been given, or 178 # nothing when it can 179 def why.as(+ds: List<&2, M.Dep>, +name: String, +url: String, +rename: String) -> String: 180 +old = had(ds, url, "") 181 +other = why(ds, name, url) 182 Bool.pick(String, renamed.ok(rename), 183 Bool.pick(String, moved(ds, url, rename), 184 "ez: " ++ url ++ " is already recorded as deps." ++ old ++ 185 "; remove it first to record it as " ++ rename, 186 other), 187 "ez: --rename " ++ rename ++ " is not a TOML bare key " ++ 188 "(letters, digits, `-` and `_`)") 189 190 # --------------------------------------------------------------------------- 191 # a hub package added by its `@` 192 193 # the name part of a `@`, what comes before its `@`. It is 194 # a-z, 0-9 and `-`, so it is a TOML bare key as it is. 195 def hub.part(+nv: String) -> String: 196 K.word(String.split(nv, '@'), 0n) 197 198 # whether a source is a hub package added by a name with this name part, 199 # whatever its version 200 def hub.from(src: M.Source, +part: String) -> Bool: 201 match src: 202 case M.Hub{named}: 203 String.eq(hub.part(named), part) 204 case M.Git{_u, _rev, _tag, _root, _nar, _vend}: 205 False{} 206 207 # the name the ledger already gives a hub package of this name part, first in 208 # document order, or "" when it gives none. Adding another version of a name 209 # is then the same entry, as re-adding a git dependency at a new tag is. 210 def hub.had(ds: List<&2, M.Dep>, +part: String) -> String: 211 match ds: 212 case []: 213 "" 214 case h <> t: 215 M.Dep{n, _h, _e, s} = h 216 had.at(hub.from(s, part), n, hub.had(t, part)) 217 218 # the name a hub package is recorded under: `--rename` when it is given, else 219 # the ledger's for this name part, else the name part itself 220 def hub.key(ds: List<&2, M.Dep>, +part: String, +rename: String) -> String: 221 +old = hub.had(ds, part) 222 Bool.pick(String, String.is_empty(rename), 223 Bool.pick(String, String.is_empty(old), part, old), rename) 224 225 # the first dependency of this name, asked whether it is another source 226 def hub.clash.at(hit: Bool, src: M.Source, +part: String, rest: Bool) -> Bool: 227 match hit: 228 case True{}: 229 Bool.not(hub.from(src, part)) 230 case False{}: 231 rest 232 233 # whether the ledger gives this name to anything but a hub package of this 234 # name part: a git dependency, or one added by its hash 235 def hub.clash(ds: List<&2, M.Dep>, +name: String, +part: String) -> Bool: 236 match ds: 237 case []: 238 False{} 239 case h <> t: 240 M.Dep{n, _h, _e, s} = h 241 hub.clash.at(String.eq(n, name), s, part, hub.clash(t, name, part)) 242 243 # why a hub package cannot be recorded under a name, or nothing when it can: 244 # a `--rename` that is no key, a `--rename` of a name part the ledger records 245 # under another name, or a name the ledger gives another source 246 def hub.why(+ds: List<&2, M.Dep>, +name: String, +part: String, +rename: String) -> String: 247 +old = hub.had(ds, part) 248 Bool.pick(String, renamed.ok(rename), 249 Bool.pick(String, moved.of(old, rename), 250 "ez: " ++ part ++ " is already recorded as deps." ++ old ++ 251 "; remove it first to record it as " ++ rename, 252 Bool.pick(String, hub.clash(ds, name, part), 253 "ez: ez.toml already has a deps." ++ name ++ " from another source; " ++ 254 "remove it first, or add this one with --rename", 255 "")), 256 "ez: --rename " ++ rename ++ " is not a TOML bare key " ++ 257 "(letters, digits, `-` and `_`)")