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 case M.Member{_path, _named}: 100 False{} 101 102 # a dependency's name when it is this source, or the answer further on 103 def had.at(same: Bool, name: String, rest: String) -> String: 104 match same: 105 case True{}: 106 name 107 case False{}: 108 rest 109 110 # the name the ledger already gives this url, first in document order, or the 111 # fresh one when it gives none. A re-add is then the same entry, whatever the 112 # ledger called it. 113 def had(ds: List<&2, M.Dep>, +url: String, fresh: String) -> String: 114 match ds: 115 case []: 116 fresh 117 case h <> t: 118 M.Dep{n, _h, _e, s} = h 119 had.at(from(s, url), n, had(t, url, fresh)) 120 121 # the package's own name when it is a bare key, or the name its source gives 122 # it. A name that could not stand as a key falls through, not refused. 123 def own(+pkg: String, +fresh: String) -> String: 124 Bool.pick(String, bare(pkg), pkg, fresh) 125 126 # the name a dependency is recorded under: the ledger's for this source, else 127 # the package's own, else its source's 128 def name(ds: List<&2, M.Dep>, +url: String, +pkg: String, +fresh: String) -> String: 129 had(ds, url, own(pkg, fresh)) 130 131 # the first dependency of this name, asked whether it is another source 132 def clash.at(hit: Bool, src: M.Source, +url: String, rest: Bool) -> Bool: 133 match hit: 134 case True{}: 135 Bool.not(from(src, url)) 136 case False{}: 137 rest 138 139 # whether the ledger gives this name to a source other than this url. A hub 140 # dependency of the name is another source too. 141 def clash(ds: List<&2, M.Dep>, +name: String, +url: String) -> Bool: 142 match ds: 143 case []: 144 False{} 145 case h <> t: 146 M.Dep{n, _h, _e, s} = h 147 clash.at(String.eq(n, name), s, url, clash(t, name, url)) 148 149 # why a name cannot be recorded, or nothing when it can 150 def why(ds: List<&2, M.Dep>, +name: String, +url: String) -> String: 151 Bool.pick(String, String.is_empty(name), 152 "ez: " ++ url ++ " has no name to record it under", 153 Bool.pick(String, clash(ds, name, url), 154 "ez: ez.toml already has a deps." ++ name ++ " from another source; " ++ 155 "remove it first, or add this one with --rename", 156 "")) 157 158 # `--rename`, the way `cargo add --rename` works: when it is given, it is the 159 # name, ahead of every rule above 160 def as(ds: List<&2, M.Dep>, +url: String, +rename: String, +pkg: String, +fresh: String) -> String: 161 +named = name(ds, url, pkg, fresh) 162 Bool.pick(String, String.is_empty(rename), named, rename) 163 164 # a `--rename` that can stand as a key, or none at all 165 def renamed.ok(+rename: String) -> Bool: 166 Bool.or(String.is_empty(rename), bare(rename)) 167 168 # whether a `--rename` gives a second name to a source the ledger already 169 # records as `old`, "" when it records none 170 def moved.of(+old: String, +rename: String) -> Bool: 171 Bool.and(Bool.not(String.is_empty(rename)), 172 Bool.and(Bool.not(String.is_empty(old)), Bool.not(String.eq(old, rename)))) 173 174 # whether a `--rename` would record a source the ledger already names under a 175 # second name. That is refused rather than duplicated or silently renamed. 176 def moved(ds: List<&2, M.Dep>, +url: String, +rename: String) -> Bool: 177 moved.of(had(ds, url, ""), rename) 178 179 # why a name cannot be recorded when `--rename` may have been given, or 180 # nothing when it can 181 def why.as(+ds: List<&2, M.Dep>, +name: String, +url: String, +rename: String) -> String: 182 +old = had(ds, url, "") 183 +other = why(ds, name, url) 184 Bool.pick(String, renamed.ok(rename), 185 Bool.pick(String, moved(ds, url, rename), 186 "ez: " ++ url ++ " is already recorded as deps." ++ old ++ 187 "; remove it first to record it as " ++ rename, 188 other), 189 "ez: --rename " ++ rename ++ " is not a TOML bare key " ++ 190 "(letters, digits, `-` and `_`)") 191 192 # --------------------------------------------------------------------------- 193 # a hub package added by its `@` 194 195 # the name part of a `@`, what comes before its `@`. It is 196 # a-z, 0-9 and `-`, so it is a TOML bare key as it is. 197 def hub.part(+nv: String) -> String: 198 K.word(String.split(nv, '@'), 0n) 199 200 # whether a source is a hub package added by a name with this name part, 201 # whatever its version 202 def hub.from(src: M.Source, +part: String) -> Bool: 203 match src: 204 case M.Hub{named}: 205 String.eq(hub.part(named), part) 206 case M.Git{_u, _rev, _tag, _root, _nar, _vend}: 207 False{} 208 case M.Member{_path, _named}: 209 False{} 210 211 # the name the ledger already gives a hub package of this name part, first in 212 # document order, or "" when it gives none. Adding another version of a name 213 # is then the same entry, as re-adding a git dependency at a new tag is. 214 def hub.had(ds: List<&2, M.Dep>, +part: String) -> String: 215 match ds: 216 case []: 217 "" 218 case h <> t: 219 M.Dep{n, _h, _e, s} = h 220 had.at(hub.from(s, part), n, hub.had(t, part)) 221 222 # the name a hub package is recorded under: `--rename` when it is given, else 223 # the ledger's for this name part, else the name part itself 224 def hub.key(ds: List<&2, M.Dep>, +part: String, +rename: String) -> String: 225 +old = hub.had(ds, part) 226 Bool.pick(String, String.is_empty(rename), 227 Bool.pick(String, String.is_empty(old), part, old), rename) 228 229 # the first dependency of this name, asked whether it is another source 230 def hub.clash.at(hit: Bool, src: M.Source, +part: String, rest: Bool) -> Bool: 231 match hit: 232 case True{}: 233 Bool.not(hub.from(src, part)) 234 case False{}: 235 rest 236 237 # whether the ledger gives this name to anything but a hub package of this 238 # name part: a git dependency, or one added by its hash 239 def hub.clash(ds: List<&2, M.Dep>, +name: String, +part: String) -> Bool: 240 match ds: 241 case []: 242 False{} 243 case h <> t: 244 M.Dep{n, _h, _e, s} = h 245 hub.clash.at(String.eq(n, name), s, part, hub.clash(t, name, part)) 246 247 # why a hub package cannot be recorded under a name, or nothing when it can: 248 # a `--rename` that is no key, a `--rename` of a name part the ledger records 249 # under another name, or a name the ledger gives another source 250 def hub.why(+ds: List<&2, M.Dep>, +name: String, +part: String, +rename: String) -> String: 251 +old = hub.had(ds, part) 252 Bool.pick(String, renamed.ok(rename), 253 Bool.pick(String, moved.of(old, rename), 254 "ez: " ++ part ++ " is already recorded as deps." ++ old ++ 255 "; remove it first to record it as " ++ rename, 256 Bool.pick(String, hub.clash(ds, name, part), 257 "ez: ez.toml already has a deps." ++ name ++ " from another source; " ++ 258 "remove it first, or add this one with --rename", 259 "")), 260 "ez: --rename " ++ rename ++ " is not a TOML bare key " ++ 261 "(letters, digits, `-` and `_`)")