src/ws/up.bend
ezx@1.10.0.0 · 416 lines · 0 definitions
1# ws/up: `ez lock --upgrade` at a workspace root (EZ-WS-4). The upgrade is
2# planned, as the lock is, on the joint ledger (ws/joint.bend), so a source a
3# name declares moves once for the whole workspace. What it writes back is
4# not the joint ledger, which no file holds: the one write of ez.toml the
5# upgrade plans is replaced by a write of every ledger that declares a moved
6# name, the root's and each member's, each dependency and tool taking its new
7# pin from the joint ledger the upgrade leaves while keeping the source as
8# that ledger records it, relative to its own directory. Every other effect of
9# the plan is kept as it was.
10import Base
11import ezx@1.10.0.0/src/ledger/manifest.bend as M
12import ezx@1.10.0.0/src/ledger/render.bend as Rend
13import ezx@1.10.0.0/src/toml/toml.bend as T
14import ezx@1.10.0.0/src/lock/world.bend as LW
15import ezx@1.10.0.0/src/lock/plan.bend as LP
16import ezx@1.10.0.0/src/pkg/pkg.bend as K
17import ezx@1.10.0.0/src/ws/workspace.bend as W
18import ezx@1.10.0.0/src/ws/joint.bend as J
19import ezx@1.10.0.0/src/ws/moved.bend as Mv
20
21# what the write-back needs: nothing, for a project of its own, or the root's
22# ledger text, what its `[workspace]` table says, every other ez.toml git
23# lists under the root, and the text of the lock being rewritten, "" for
24# none, which records each member's hash before it moved (EZ-WS-7)
25type Back is Data:
26 Alone{}
27 Back{text: String, spec: W.Spec, found: List<&2, J.Found>, lock: String}
28
29# ---------------------------------------------------------------------------
30# a pin moved back into a ledger
31
32# a source with the pin the joint ledger now gives it: the joint's commit,
33# tag, root and narHash, and this ledger's own url and vendor bit
34def source.back(own: M.Source, joint: M.Source) -> M.Source:
35 match own:
36 case M.Hub{nv}:
37 M.Hub{nv}
38 case M.Git{url, _rev, _tag, _root, _nar, vend}:
39 match joint:
40 case M.Hub{_jn}:
41 M.Git{url, _rev, _tag, _root, _nar, vend}
42 case M.Git{_ju, jr, jt, jo, jn, _jv}:
43 M.Git{url, jr, jt, jo, jn, vend}
44 case M.Member{_jp, _jm}:
45 M.Git{url, _rev, _tag, _root, _nar, vend}
46 case M.Member{path, named}:
47 M.Member{path, named}
48
49# a dependency with the pin the joint ledger gives its name, by its own
50# source: a workspace member's section is kept as it is written, since the
51# hash and entry the joint ledger gives it are no ledger's to record
52def dep.back.src(own: M.Source, +nn: String, +hh: String, +ee: String, joint: M.Dep) -> M.Dep:
53 match own:
54 case M.Member{path, nv}:
55 M.Dep{nn, hh, ee, M.Member{path, nv}}
56 case M.Hub{nv}:
57 M.Dep{_jn, jh, je, js} = joint
58 M.Dep{nn, jh, je, source.back(M.Hub{nv}, js)}
59 case M.Git{url, rev, tag, root, nar, vend}:
60 M.Dep{_jn, jh, je, js} = joint
61 M.Dep{nn, jh, je, source.back(M.Git{url, rev, tag, root, nar, vend}, js)}
62
63# a dependency with the pin the joint ledger gives its name
64def dep.back.at(own: M.Dep, joint: M.Dep) -> M.Dep:
65 M.Dep{nn, hh, ee, os} = own
66 dep.back.src(os, nn, hh, ee, joint)
67
68# a dependency of a ledger, after the upgrade
69def dep.back(+own: M.Dep, +js: List<&2, M.Dep>) -> M.Dep:
70 dep.back.at(own, M.find(js, M.dep.name(own)))
71
72# every dependency of a ledger, after the upgrade
73def deps.back(ds: List<&2, M.Dep>, +js: List<&2, M.Dep>) -> List<&2, M.Dep>:
74 match ds:
75 case []:
76 []
77 case +hd <> tl:
78 dep.back(hd, js) <> deps.back(tl, js)
79
80# a tool with the pin the joint ledger gives its name
81def tool.back.at(own: M.Tool, joint: M.Tool) -> M.Tool:
82 M.Tool{nn, ee, bb, os} = own
83 M.Tool{_jn, _je, _jb, js} = joint
84 M.Tool{nn, ee, bb, source.back(os, js)}
85
86# a tool of a ledger, after the upgrade
87def tool.back(+own: M.Tool, +ts: List<&2, M.Tool>) -> M.Tool:
88 tool.back.at(own, M.tool.find(ts, M.tool.name(own)))
89
90# every tool of a ledger, after the upgrade
91def tools.back(ts: List<&2, M.Tool>, +js: List<&2, M.Tool>) -> List<&2, M.Tool>:
92 match ts:
93 case []:
94 []
95 case +hd <> tl:
96 tool.back(hd, js) <> tools.back(tl, js)
97
98# a ledger after the upgrade, every pin taken from the joint ledger
99def ledger.back(mm: M.Manifest, joint: M.Manifest) -> M.Manifest:
100 M.Manifest{nn, ee, bb, hh, pa, pv, ds, ts} = mm
101 M.Manifest{_jn, _je, _jb, _jh, _jpa, _jpv, +jd, +jt} = joint
102 M.Manifest{nn, ee, bb, hh, pa, pv, deps.back(ds, jd), tools.back(ts, jt)}
103
104# ---------------------------------------------------------------------------
105# the root's ledger, written back with its `[workspace]` table
106
107# texts written as a TOML array of strings
108def array.items(ts: List<&2, String>) -> String:
109 match ts:
110 case []:
111 ""
112 case +hd <> []:
113 T.quote(hd)
114 case +hd <> tl:
115 T.quote(hd) ++ ", " ++ array.items(tl)
116
117# every entry as it was written
118def entries.text(es: List<&2, W.Entry>) -> List<&2, String>:
119 match es:
120 case []:
121 []
122 case hd <> tl:
123 W.entry.text(hd) <> entries.text(tl)
124
125# every path as it was written
126def paths.text(ps: List<&2, List<&2, String>>) -> List<&2, String>:
127 match ps:
128 case []:
129 []
130 case hd <> tl:
131 W.segs.text(hd) <> paths.text(tl)
132
133# the `[workspace]` table, as it says what it says
134def spec.text(spec: W.Spec) -> String:
135 W.Spec{es, ex} = spec
136 "[workspace]\nmembers = [" ++ array.items(entries.text(es)) ++ "]\nexclude = [" ++
137 array.items(paths.text(ex)) ++ "]\n"
138
139# the root's ledger as a file: its package table when it has one, which a
140# virtual root does not, its dependencies and tools, and its `[workspace]`
141def root.text(+mm: M.Manifest, +spec: W.Spec, +root: W.Root) -> String:
142 +body = Rend.text(mm)
143 T.normal(Bool.pick(String, W.root.member(root), body, String.drop(body, 10n)) ++ spec.text(spec))
144
145# ---------------------------------------------------------------------------
146# the writes
147
148# a ledger written back when the upgrade changed what it holds, and left as
149# it was otherwise
150def write.if(+mm: M.Manifest, +joint: M.Manifest, +at: String, +text: String) -> List<&2, LP.Effect>:
151 Bool.pick(List<&2, LP.Effect>, String.eq(Rend.show(mm), Rend.show(ledger.back(mm, joint))),
152 [], [LP.Write{at, text}])
153
154# a member's ledger, written back at its directory's ez.toml
155def member.write(rd: M.Read, +ds: List<&2, String>, +joint: M.Manifest) -> List<&2, LP.Effect>:
156 match rd:
157 case M.Bad{_why}:
158 []
159 case M.Good{+mm}:
160 write.if(mm, joint, W.segs.text(ds) ++ "/ez.toml", Rend.show(ledger.back(mm, joint)))
161
162# a member's ledger written back, or, for another's, nothing
163def found.pick(hit: Bool, +text: String, +ds: List<&2, String>, +joint: M.Manifest) -> List<&2, LP.Effect>:
164 match hit:
165 case True{}:
166 member.write(M.parse(text), ds, joint)
167 case False{}:
168 []
169
170# one ez.toml git listed, written back when it is a member's
171def found.write(
172 lg: Maybe<&2, W.Ledger>,
173 +text: String,
174 +dirs: List<&2, List<&2, String>>,
175 +joint: M.Manifest
176) -> List<&2, LP.Effect>:
177 match lg:
178 case None{}:
179 []
180 case Some{ll}:
181 +ds = W.ledger.dir(ll)
182 found.pick(W.has(dirs, ds), text, ds, joint)
183
184# every member's ledger the upgrade changed, written back
185def members.write(
186 fs: List<&2, J.Found>,
187 +dirs: List<&2, List<&2, String>>,
188 +joint: M.Manifest
189) -> List<&2, LP.Effect>:
190 match fs:
191 case []:
192 []
193 case J.Found{+pp, +tt} <> tl:
194 List.append(&2, LP.Effect, found.write(W.ledger.of(pp, tt), tt, dirs, joint),
195 members.write(tl, dirs, joint))
196
197# the root's ledger, written back when the upgrade changed it
198def root.write(rd: M.Read, +spec: W.Spec, +root: W.Root, +joint: M.Manifest) -> List<&2, LP.Effect>:
199 match rd:
200 case M.Bad{_why}:
201 []
202 case M.Good{+mm}:
203 write.if(mm, joint, "ez.toml", root.text(ledger.back(mm, joint), spec, root))
204
205# every ledger the upgrade changed, the root's first, once the member set is
206# known
207def writes.members(
208 ms: W.Members,
209 +text: String,
210 +spec: W.Spec,
211 +fs: List<&2, J.Found>,
212 +joint: M.Manifest
213) -> List<&2, LP.Effect>:
214 match ms:
215 case W.Refused{_why}:
216 []
217 case W.Members{_root, +dirs}:
218 List.append(&2, LP.Effect, root.write(M.parse(text), spec, W.root.of(text), joint),
219 members.write(fs, dirs, joint))
220
221# every ledger the upgrade changed, once the joint ledger it leaves was read
222def writes.joint(joint: M.Read, +text: String, +spec: W.Spec, +fs: List<&2, J.Found>) -> List<&2, LP.Effect>:
223 match joint:
224 case M.Bad{_why}:
225 []
226 case M.Good{+jm}:
227 writes.members(W.members(spec, W.root.of(text), J.ledgers(fs)), text, spec, fs, jm)
228
229# every ledger the upgrade changed, from the joint ledger it leaves
230def writes(back: Back, joint: M.Read) -> List<&2, LP.Effect>:
231 match back:
232 case Alone{}:
233 []
234 case Back{+text, +spec, +fs, _lock}:
235 writes.joint(joint, text, spec, fs)
236
237# a write kept, or, when it is of ez.toml, the ledgers' in its place
238def effect.write(
239 hit: Bool,
240 at: String,
241 text: String,
242 ws: List<&2, LP.Effect>,
243 rest: List<&2, LP.Effect>
244) -> List<&2, LP.Effect>:
245 match hit:
246 case True{}:
247 List.append(&2, LP.Effect, ws, rest)
248 case False{}:
249 LP.Write{at, text} <> rest
250
251# one effect of the plan: the write of ez.toml replaced by the ledgers'
252def effect.back(effect: LP.Effect, +ws: List<&2, LP.Effect>, rest: List<&2, LP.Effect>) -> List<&2, LP.Effect>:
253 match effect:
254 case LP.Write{+at, text}:
255 effect.write(String.eq(at, "ez.toml"), at, text, ws, rest)
256 case LP.Lay{pl, hash, files}:
257 LP.Lay{pl, hash, files} <> rest
258 case LP.Drop{hash}:
259 LP.Drop{hash} <> rest
260 case LP.Say{text}:
261 LP.Say{text} <> rest
262 case LP.Name{nv, hash}:
263 LP.Name{nv, hash} <> rest
264
265# every effect, with the write of ez.toml replaced
266def effects.back(es: List<&2, LP.Effect>, +ws: List<&2, LP.Effect>) -> List<&2, LP.Effect>:
267 match es:
268 case []:
269 []
270 case hd <> tl:
271 rest = effects.back(tl, ws)
272 effect.back(hd, ws, rest)
273
274# the plan with its write of ez.toml replaced by the ledgers'
275def plan.back(pl: LP.Plan, +ws: List<&2, LP.Effect>) -> LP.Plan:
276 LP.Plan{es, outcome} = pl
277 LP.Plan{effects.back(es, ws), outcome}
278
279# ---------------------------------------------------------------------------
280# members imported by their hash (EZ-WS-7)
281
282# the World an upgrade plans on, and the files it rewrites
283type Up is Data:
284 Up{world: LW.World, edits: List<&2, LP.Effect>}
285
286# the committed files a World lists
287def files.of(world: LW.World) -> List<&2, K.Source>:
288 LW.World{_args, _ledger, listing, _replies, _ups} = world
289 J.files(listing)
290
291# an upgrade at a workspace root, once its moves are known: the World with
292# every committed file rewritten by them, and the writes of the files they
293# change
294def up.moves(+ms: List<&2, Mv.Shift>, +text: String, +fs: List<&2, J.Found>, +world: LW.World) -> Up:
295 +files = files.of(world)
296 +moved = Mv.rewrite(Mv.swaps(ms), files)
297 Up{Mv.world.moved(J.joint(text, fs, moved), moved, world), Mv.edits(Mv.swaps(ms), files)}
298
299# an upgrade at a workspace root: every member it moves (ws/moved.bend), its
300# import lines rewritten, until none moves further
301def up.of(+text: String, +fs: List<&2, J.Found>, +lock: String, +world: LW.World) -> Up:
302 up.moves(Mv.cascade(Mv.ctx(text, fs, files.of(world), lock, LW.args.only(world))), text, fs, world)
303
304# the World an upgrade plans on
305def up.world(got: Up) -> LW.World:
306 Up{w2, _eds} = got
307 w2
308
309# the World `ez lock` plans on at a workspace root: a plain lock's as it is,
310# and an upgrade's with every moved member's import lines rewritten
311def world.at(up: Bool, +text: String, +fs: List<&2, J.Found>, +lock: String, +world: LW.World) -> LW.World:
312 match up:
313 case True{}:
314 up.world(up.of(text, fs, lock, world))
315 case False{}:
316 world
317
318# the World `ez lock` plans on
319def plans.on(back: Back, +world: LW.World) -> LW.World:
320 match back:
321 case Alone{}:
322 world
323 case Back{+text, _spec, +fs, +lock}:
324 world.at(LW.args.upgrade(world), text, fs, lock, world)
325
326# why a plain lock at a workspace root refuses before it asks anything: a
327# committed line names the hash the lock being rewritten records for a member
328# that has moved since. "" when it does not, and always for an upgrade, which
329# rewrites the line, and for a project of its own.
330def stale.at(up: Bool, +lock: String, +world: LW.World) -> String:
331 match up:
332 case True{}:
333 ""
334 case False{}:
335 Mv.stale.world(lock, world)
336
337# and at the root as the command found it
338def stale(back: Back, +world: LW.World) -> String:
339 match back:
340 case Alone{}:
341 ""
342 case Back{_text, _spec, _fs, +lock}:
343 stale.at(LW.args.upgrade(world), lock, world)
344
345# the plan that refuses, writing nothing
346def refused(+why: String) -> LP.Plan:
347 LP.Plan{[], LP.Refused{why}}
348
349# the files an upgrade rewrites, written ahead of the plan, unless it refuses
350def edits.put(refuses: Bool, eds: List<&2, LP.Effect>, pl: LP.Plan) -> LP.Plan:
351 match refuses:
352 case True{}:
353 pl
354 case False{}:
355 LP.Plan{es, outcome} = pl
356 LP.Plan{List.append(&2, LP.Effect, eds, es), outcome}
357
358# an upgrade's plan on the World it rewrote: the files it rewrote written
359# first, so a source the lock planner rewrites too is left as the planner
360# writes it, and then the planner's plan with the ledgers written back
361def plan.up(got: Up, +back: Back) -> LP.Plan:
362 Up{+w2, eds} = got
363 edits.put(LP.refuses(w2), eds, plan.back(LP.plan(w2), writes(back, LP.ledger.next(w2))))
364
365# a plain lock's plan, once it is known whether a line is stale
366def plan.plain(fine: Bool, +why: String, +world: LW.World) -> LP.Plan:
367 match fine:
368 case True{}:
369 LP.plan(world)
370 case False{}:
371 refused(why)
372
373# what `ez lock` runs at a workspace root, under `--upgrade` or not
374def plan.at(up: Bool, +back: Back, +world: LW.World) -> LP.Plan:
375 match up:
376 case True{}:
377 match back:
378 case Alone{}:
379 LP.plan(world)
380 case Back{+text, +spec, +fs, +lock}:
381 plan.up(up.of(text, fs, lock, world), Back{text, spec, fs, lock})
382 case False{}:
383 match back:
384 case Alone{}:
385 LP.plan(world)
386 case Back{_text, _spec, _fs, +lock}:
387 +why = Mv.stale.world(lock, world)
388 plan.plain(String.is_empty(why), why, world)
389
390# what `ez lock` runs: for a project of its own, the plan the lock planner
391# makes; at a workspace root, the plan it makes on the joint ledger, with,
392# under `--upgrade`, every moved member's import lines rewritten and every
393# ledger the upgrade changed written back in place of the joint one. A plain
394# lock writes no ledger, so its plan is as it was, unless a line names a
395# moved member's old hash, which it refuses.
396def plan(back: Back, +world: LW.World) -> LP.Plan:
397 match back:
398 case Alone{}:
399 LP.plan(world)
400 case Back{+text, +spec, +fs, +lock}:
401 plan.at(LW.args.upgrade(world), Back{text, spec, fs, lock}, world)
402
403# the step a plain lock takes, once it is known whether a line is stale
404def step.plain(fine: Bool, +why: String, +world: LW.World) -> LP.Step:
405 match fine:
406 case True{}:
407 LP.step(world)
408 case False{}:
409 LP.Run{refused(why)}
410
411# the lock planner's step on the World `ez lock` plans on: its questions, or
412# its plan. A plain lock with a stale line asks nothing and refuses.
413def step(+back: Back, +world: LW.World) -> LP.Step:
414 +why = stale(back, world)
415 step.plain(String.is_empty(why), why, plans.on(back, world))
416