src/ez/named.bend

ezx@1.11.0.0 · 261 lines · 30 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.11.0.0/src/ledger/manifest.bend as M
11import ezx@1.11.0.0/src/ez/target.bend as Tgt
12import ezx@1.11.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    case M.Member{_path, _named}:
100      False{}
101
102# a dependency's name when it is this source, or the answer further on
103def 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.
113def 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.
123def 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
128def 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
132def 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.
141def 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
150def 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
160def 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
165def 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
170def 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.
176def 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
181def 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 `<name>@<version>`
194
195# the name part of a `<name>@<version>`, what comes before its `@`. It is
196# a-z, 0-9 and `-`, so it is a TOML bare key as it is.
197def 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
202def 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.
214def 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
224def 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
230def 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
239def 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
250def 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 `_`)")