src/ez/named.bend
ezx@1.8.0.0 · 257 lines · 0 definitions
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.
9import Base
10import ezx@1.8.0.0/src/ledger/manifest.bend as M
11import ezx@1.8.0.0/src/ez/target.bend as Tgt
12import ezx@1.8.0.0/src/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
16type 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
20def 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.
29def 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
38def 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
46def 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
51def 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.
57def 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.
68def 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 `_`
76def 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.<name>]` unquoted.
85def 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
93def 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
101def 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.
111def 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.
121def 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
126def 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
130def 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.
139def 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
148def 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
158def 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
163def 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
168def 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.
174def 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
179def 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 `<name>@<version>`
192
193# the name part of a `<name>@<version>`, what comes before its `@`. It is
194# a-z, 0-9 and `-`, so it is a TOML bare key as it is.
195def 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
200def 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.
210def 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
220def 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
226def 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
235def 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
246def 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 `_`)")