src/add/relock.bend

ezx@1.10.0.0 · 227 lines · 0 definitions

1# add/relock: `ez add` as it runs, the add and the lock in one plan
2# (EZ-DOC-6). The add is planned as before (`AP.command`, add/plan.bend);
3# once it is known to succeed, the plain `ez lock` planner is run over the
4# World it leaves (`R.world`, relock/relock.bend): the ledger the add writes,
5# the `.bend` files git lists, and the lock's answers. The package the add
6# walked is handed to the lock as the answer for its hash, read as a tree
7# under BEND_LIB, which is where the add lays it, so it is not read twice; a
8# hub package's answers are the ones the add was given. Every other package
9# the lock asks about is answered by the lock's interpreter as `ez lock`
10# answers it.
11#
12# The plan, when both succeed: the add's effects, a note when the lock does
13# not hold the package added (EZ-DOC-7), then the lock's effects, the lock
14# last. When either refuses, a refusal with no effect at all.
15import Base
16import ezx@1.10.0.0/src/add/world.bend as A
17import ezx@1.10.0.0/src/add/plan.bend as AP
18import ezx@1.10.0.0/src/add/hub.bend as H
19import ezx@1.10.0.0/src/lock/plan.bend as P
20import ezx@1.10.0.0/src/lock/world.bend as W
21import ezx@1.10.0.0/src/lock/up.bend as Up
22import ezx@1.10.0.0/src/lock/lock.bend as L
23import ezx@1.10.0.0/src/ledger/manifest.bend as M
24import ezx@1.10.0.0/src/pkg/pkg.bend as K
25import ezx@1.10.0.0/src/relock/relock.bend as R
26
27# what `ez add` reads: the add's World, the `.bend` files git lists, and the
28# answers to the lock's questions
29type World is Data:
30  World{add: A.World, listing: W.Listing, locks: List<&2, W.Reply>}
31
32# the add's World
33def add(world: World) -> A.World:
34  World{a, _listing, _locks} = world
35  a
36
37# ---------------------------------------------------------------------------
38# the lock's World
39
40# this file's text when it is the one at the path, else the rest's. The rest
41# arrives as a thunk, so the first hit ends the scan.
42def found.pick(hit: Bool, body: String, rest: Unit -> String) -> String:
43  match hit:
44    case True{}:
45      body
46    case False{}:
47      rest(Unit{})
48
49# the text of the file at a path among a walk's files, "" when none is there
50def text.at(fs: List<&2, K.Source>, +at: String) -> String:
51  match fs:
52    case []:
53      ""
54    case K.Source{+p, body} <> t:
55      found.pick(String.eq(p, at), body, _u => text.at(t, at))
56
57# the text of each file a manifest names, in its order
58def texts(+fs: List<&2, K.Source>, items: List<&2, K.Item>) -> List<&2, String>:
59  match items:
60    case []:
61      []
62    case K.Item{at, _sum} <> t:
63      text.at(fs, at) <> texts(fs, t)
64
65# the package a walk made, as the answer to the lock's question about its
66# hash: its manifest and the text of each file it names, read as a tree under
67# BEND_LIB, which is where the add lays it. The lock checks it as it checks
68# any tree there. A walk that made no package answers nothing.
69def seed.of(walk: K.Walked) -> List<&2, W.Reply>:
70  match walk:
71    case K.Walked{h, _root, +fs, ss}:
72      +manifest = K.manifest_of(ss)
73      [W.Reply{W.Pkg{h, L.Hub{}, ""},
74        W.Got{W.Lib{}, manifest, texts(fs, L.manifest.files(String.lines(manifest)))}}]
75    case K.Asks{_at}:
76      []
77    case K.Refused{_why}:
78      []
79
80# the answers the add already has for the lock: a hub package's are those the
81# hub gave the add, and a git package's is the package its walk made
82def seeds.of(named: Bool, +aw: A.World) -> List<&2, W.Reply>:
83  match named:
84    case True{}:
85      A.hubs(aw)
86    case False{}:
87      seed.of(AP.walked(aw))
88
89# the answers the add already has for the lock, on the add's World
90def seeds(+aw: A.World) -> List<&2, W.Reply>:
91  seeds.of(A.named(aw), aw)
92
93# the World a plain `ez lock` reads once the add has run: the ledger it
94# writes, the `.bend` files git lists, and the answers it was given, the add's
95# first
96def lock.world(world: World) -> W.World:
97  World{+a, listing, locks} = world
98  R.world(A.here(a), R.ledger(AP.command(a), A.ledger(a)), listing,
99    List.append(&2, W.Reply, seeds(a), locks))
100
101# the plan of that lock
102def lock(world: World) -> P.Plan:
103  P.plan(lock.world(world))
104
105# ---------------------------------------------------------------------------
106# the note (EZ-DOC-7)
107
108# the hash of the package the add records
109def added.of(named: Bool, +aw: A.World) -> String:
110  match named:
111    case True{}:
112      H.hash(aw)
113    case False{}:
114      AP.hash(aw)
115
116# the hash of the package the add records, on the add's World
117def added(+aw: A.World) -> String:
118  added.of(A.named(aw), aw)
119
120# whether the lock holds a package under the hash
121def held(ps: List<&2, L.Pack>, +hash: String) -> Bool:
122  match ps:
123    case []:
124      False{}
125    case h <> t:
126      L.any.step(String.eq(L.pack.hash(h), hash), _u => held(t, hash))
127
128# the line that says the lock does not hold it
129def unlocked(+hash: String) -> String:
130  "ez: no .bend file git lists imports " ++ hash ++ " yet, so ez.lock.toml does " ++
131    "not hold it. Import it, `git add` the file, and run `ez lock`."
132
133# the note: the line when the lock does not hold the package, none when it does
134def notes.of(held: Bool, +hash: String) -> List<&2, P.Effect>:
135  match held:
136    case True{}:
137      []
138    case False{}:
139      [P.Say{unlocked(hash)}]
140
141# the note on a World
142def notes(+world: World) -> List<&2, P.Effect>:
143  notes.of(held(P.packs(lock.world(world)), added(add(world))), added(add(world)))
144
145# ---------------------------------------------------------------------------
146# the plan
147
148# what is added after the lock's reason when the lock refuses: that nothing
149# was written, and, when the add moves a dependency's hash, where the old one
150# may still be imported from
151def moved.of(moved: Bool, +name: String, +old: String, +new: String) -> String:
152  match moved:
153    case True{}:
154      "\nez: ez add moves " ++ name ++ " from " ++ old ++ " to " ++ new ++
155        "; a .bend file that imports " ++ old ++ " has to name " ++ new ++
156        " first, or `ez lock --upgrade --package " ++ name ++ "` moves it"
157    case False{}:
158      ""
159
160def tail.dep(+dep: M.Dep, +old: M.Dep) -> String:
161  "\nez: ez add wrote nothing, since it writes ez.toml and ez.lock.toml together" ++
162    moved.of(Bool.and(Bool.not(String.is_empty(M.dep.hash(old))),
163      Bool.not(String.eq(M.dep.hash(old), M.dep.hash(dep)))),
164      M.dep.name(dep), M.dep.hash(old), M.dep.hash(dep))
165
166# the dependency the add records
167def dep.of(named: Bool, +aw: A.World) -> M.Dep:
168  match named:
169    case True{}:
170      H.added(aw)
171    case False{}:
172      AP.added(aw)
173
174# what is added after the lock's reason, on the add's World
175def tail(+aw: A.World) -> String:
176  +dep = dep.of(A.named(aw), aw)
177  tail.dep(dep, M.find(A.deps(aw), M.dep.name(dep)))
178
179# `ez add` over a World: the add, then the lock over what it leaves
180def plan(world: World) -> P.Plan:
181  World{+a, +listing, +locks} = world
182  R.join(AP.command(a), lock(World{a, listing, locks}), notes(World{a, listing, locks}),
183    tail(a))
184
185# whether `ez add` refuses on a World
186def refuses(world: World) -> Bool:
187  R.refuses(plan(world))
188
189# ---------------------------------------------------------------------------
190# the step
191
192# what the interpreter does next: answer git's questions or the hub's for the
193# add, answer the lock's, or run the plan
194type Next is Data:
195  AskGit{asks: List<&2, Up.Ask>}
196  AskHub{asks: List<&2, W.Ask>}
197  AskLock{asks: List<&2, W.Ask>}
198  Planned{plan: P.Plan}
199
200# the lock's step, once the add is planned and succeeds
201def next.lock(st: P.Step, +world: World) -> Next:
202  match st:
203    case P.Asking{as, ups, ns}:
204      AskLock{R.asks(P.Asking{as, ups, ns})}
205    case P.Run{_pl}:
206      Planned{plan(world)}
207
208# the add's plan made: refused, it is the plan; otherwise the lock is next
209def next.made(refused: Bool, +world: World) -> Next:
210  match refused:
211    case True{}:
212      Planned{plan(world)}
213    case False{}:
214      next.lock(P.step(lock.world(world)), world)
215
216def next.add(nx: AP.Next, +world: World) -> Next:
217  match nx:
218    case AP.AskGit{asks}:
219      AskGit{asks}
220    case AP.AskHub{asks}:
221      AskHub{asks}
222    case AP.Planned{pl}:
223      next.made(R.refuses(pl), world)
224
225# what the interpreter does next on a World
226def next(+world: World) -> Next:
227  next.add(AP.next(add(world)), world)