src/add/relock.bend
ezx@1.9.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.9.0.0/src/add/world.bend as A
17import ezx@1.9.0.0/src/add/plan.bend as AP
18import ezx@1.9.0.0/src/add/hub.bend as H
19import ezx@1.9.0.0/src/lock/plan.bend as P
20import ezx@1.9.0.0/src/lock/world.bend as W
21import ezx@1.9.0.0/src/lock/up.bend as Up
22import ezx@1.9.0.0/src/lock/lock.bend as L
23import ezx@1.9.0.0/src/ledger/manifest.bend as M
24import ezx@1.9.0.0/src/pkg/pkg.bend as K
25import ezx@1.9.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)