src/add/hub.bend
ezx@1.11.0.0 · 467 lines · 54 definitions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29import Base
30import ezx@1.11.0.0/src/add/world.bend as A
31import ezx@1.11.0.0/src/lock/plan.bend as P
32import ezx@1.11.0.0/src/lock/world.bend as W
33import ezx@1.11.0.0/src/lock/lock.bend as L
34import ezx@1.11.0.0/src/ledger/manifest.bend as M
35import ezx@1.11.0.0/src/ledger/render.bend as Rend
36import ezx@1.11.0.0/src/ledger/ignore.bend as I
37import ezx@1.11.0.0/src/pkg/pkg.bend as K
38import ezx@1.11.0.0/src/pkg/path.bend as Path
39import ezx@1.11.0.0/src/git/git.bend as Git
40import ezx@1.11.0.0/src/ez/named.bend as Named
41
42
43
44
45
46def nv(world: A.World) -> String:
47 A.target(world)
48
49
50def part(+world: A.World) -> String:
51 Named.hub.part(nv(world))
52
53
54def key(+world: A.World) -> String:
55 Named.hub.key(A.deps(world), part(world), A.rename(world))
56
57
58
59def asked(+world: A.World) -> String:
60 Bool.pick(String, String.is_empty(A.entry.arg(world)), A.ref(world), A.entry.arg(world))
61
62
63
64def heard.at(hit: Bool, answer: W.Answer, rest: Unit -> Maybe<&2, W.Answer>) -> Maybe<&2, W.Answer>:
65 match hit:
66 case True{}:
67 Some{answer}
68 case False{}:
69 rest(Unit{})
70
71
72
73def heard(rs: List<&2, W.Reply>, +key: String) -> Maybe<&2, W.Answer>:
74 match rs:
75 case []:
76 None{}
77 case W.Reply{ask, answer} <> t:
78 heard.at(String.eq(W.ask.hash(ask), key), answer, _u => heard(t, key))
79
80
81
82
83
84
85type Verdict is Data:
86 Go{hash: String, files: List<&2, K.Item>, srcs: List<&2, String>}
87 Pending{asks: List<&2, W.Ask>}
88 Stop{why: String}
89
90
91def decide.body(verdict: W.Verdict, +hash: String) -> Verdict:
92 match verdict:
93 case W.Ok{fs, ss}:
94 Go{hash, fs, ss}
95 case W.No{why}:
96 Stop{why}
97 case W.Named{_ns}:
98 Stop{"ez: " ++ hash ++ " was not answered with a package"}
99
100
101def decide.got(answer: W.Answer, +hash: String, +world: A.World) -> Verdict:
102 match answer:
103 case W.Got{how, manifest, ss}:
104 decide.body(W.hub.how(how, hash, manifest, ss), hash)
105 case W.Miss{why}:
106 Stop{W.hub.why(True{}, hash, A.hub(world), why)}
107 case _:
108 Stop{"ez: " ++ hash ++ " was not answered with a package"}
109
110
111def decide.pkg(got: Maybe<&2, W.Answer>, +hash: String, +world: A.World) -> Verdict:
112 match got:
113 case None{}:
114 Pending{[W.Pkg{hash, L.Hub{}, A.hub(world)}]}
115 case Some{answer}:
116 decide.got(answer, hash, world)
117
118
119def decide.hash(ok: Bool, +hash: String, +world: A.World) -> Verdict:
120 match ok:
121 case True{}:
122 decide.pkg(heard(A.hubs(world), hash), hash, world)
123 case False{}:
124 Stop{"ez: the hub at " ++ A.hub(world) ++ " names no package " ++ nv(world) ++
125 " (it answered `" ++ hash ++ "`)"}
126
127
128def unsaid(ez: Bool, +why: String) -> String:
129 match ez:
130 case True{}:
131 String.drop(why, 4n)
132 case False{}:
133 why
134
135
136def decide.said(answer: W.Answer, +world: A.World) -> Verdict:
137 match answer:
138 case W.Said{text}:
139 +hash = String.trim(text)
140 decide.hash(L.hash.ok(hash), hash, world)
141 case W.Miss{+why}:
142 Stop{"ez: the hub at " ++ A.hub(world) ++ " has no package " ++ nv(world) ++ " (" ++
143 unsaid(String.starts_with(why, "ez: "), why) ++ ")"}
144 case _:
145 Stop{"ez: " ++ nv(world) ++ " was not answered with a hash"}
146
147
148def decide.name(got: Maybe<&2, W.Answer>, +world: A.World) -> Verdict:
149 match got:
150 case None{}:
151 Pending{[W.Name{nv(world), A.hub(world)}]}
152 case Some{answer}:
153 decide.said(answer, world)
154
155
156
157def early(+world: A.World) -> String:
158 Bool.pick(String,
159 Bool.or(String.is_empty(A.ref(world)), String.is_empty(A.entry.arg(world))),
160 Named.hub.why(A.deps(world), key(world), part(world), A.rename(world)),
161 "ez: " ++ nv(world) ++ " pins its version; the word after it is the entry, " ++
162 "and there is only one")
163
164
165def decide.early(ok: Bool, +why: String, +world: A.World) -> Verdict:
166 match ok:
167 case True{}:
168 decide.name(heard(A.hubs(world), nv(world)), world)
169 case False{}:
170 Stop{why}
171
172
173
174def decide.read(read: M.Read, +world: A.World) -> Verdict:
175 match read:
176 case M.Bad{why}:
177 Stop{"ez: " ++ M.show(M.Bad{why})}
178 case M.Good{_m}:
179 +e = early(world)
180 decide.early(String.is_empty(e), e, world)
181
182
183def decide(+world: A.World) -> Verdict:
184 decide.read(A.read(world), world)
185
186
187
188
189
190def holds(fs: List<&2, K.Item>, +at: String) -> Bool:
191 match fs:
192 case []:
193 False{}
194 case +h <> t:
195 +rest = holds(t, at)
196 Bool.or(String.eq(K.file.at(h), at), rest)
197
198
199def top.one(+at: String) -> Bool:
200 Bool.and(String.ends_with(at, ".bend"), Bool.not(String.contains(at, "/")))
201
202
203
204def top.at(hit: Bool, +at: String, rest: Unit -> String) -> String:
205 match hit:
206 case True{}:
207 at
208 case False{}:
209 rest(Unit{})
210
211
212def top(fs: List<&2, K.Item>) -> String:
213 match fs:
214 case []:
215 ""
216 case +h <> t:
217 top.at(top.one(K.file.at(h)), K.file.at(h), _u => top(t))
218
219
220def entry.main(held: Bool, fs: List<&2, K.Item>) -> String:
221 match held:
222 case True{}:
223 "main.bend"
224 case False{}:
225 top(fs)
226
227
228
229def entry.of(+fs: List<&2, K.Item>) -> String:
230 entry.main(holds(fs, "main.bend"), fs)
231
232
233def entry.pick(none: Bool, +fs: List<&2, K.Item>, +asked: String) -> String:
234 match none:
235 case True{}:
236 entry.of(fs)
237 case False{}:
238 asked
239
240
241def entry(+fs: List<&2, K.Item>, +world: A.World) -> String:
242 entry.pick(String.is_empty(asked(world)), fs, asked(world))
243
244
245
246
247
248def dep.of(+hash: String, +fs: List<&2, K.Item>, +world: A.World) -> M.Dep:
249 M.Dep{key(world), hash, entry(fs, world), M.Hub{nv(world)}}
250
251
252
253
254def outcome.fit(fit: Bool) -> P.Outcome:
255 match fit:
256 case True{}:
257 P.Success{}
258 case False{}:
259 P.Refused{Rend.unrenderable()}
260
261
262def outcome.entry(held: Bool, fit: Bool, +at: String, +world: A.World) -> P.Outcome:
263 match held:
264 case True{}:
265 outcome.fit(fit)
266 case False{}:
267 P.Refused{"ez: " ++ at ++ " is not in " ++ nv(world)}
268
269
270def outcome.of(+hash: String, +fs: List<&2, K.Item>, +world: A.World) -> P.Outcome:
271 outcome.entry(Bool.or(String.is_empty(asked(world)), holds(fs, Path.norm(asked(world)))),
272 Rend.renderable(Rend.add.keep(A.manifest.of(A.read(world)), dep.of(hash, fs, world))),
273 asked(world), world)
274
275
276
277
278def place.of(vend: Bool) -> P.Place:
279 match vend:
280 case True{}:
281 P.Committed{}
282 case False{}:
283 P.Cache{}
284
285
286def ignore.put(same: Bool, +next: String, rest: List<&2, P.Effect>) -> List<&2, P.Effect>:
287 match same:
288 case True{}:
289 rest
290 case False{}:
291 P.Write{A.ignore.file(), next} <> rest
292
293
294
295
296def says(+hash: String, +entry: String, +world: A.World) -> List<&2, P.Effect>:
297 Bool.pick(List<&2, P.Effect>, String.is_empty(entry), [P.Say{nv(world) ++ " is " ++ hash}],
298 [P.Say{nv(world) ++ " is " ++ hash}, P.Say{Git.import.line(nv(world), Path.norm(entry))}])
299
300
301
302
303def effects.of(
304 +dep: M.Dep,
305 +hash: String,
306 +ls: List<&2, W.Source>,
307 +manifest: M.Manifest,
308 +world: A.World
309) -> List<&2, P.Effect>:
310 +old = M.find(A.deps.of(manifest), M.dep.name(dep))
311 +next = I.sync(A.deps.of(Rend.add.keep(manifest, dep)), A.ignore(world))
312 P.Lay{place.of(M.source.vendor(M.source.dep(old))), hash,
313 List.append(&2, W.Source, ls, [W.Source{"manifest", W.manifest(ls)}])} <>
314 (P.Name{nv(world), hash} <>
315 (P.Write{A.toml(), Rend.show(Rend.add.keep(manifest, dep))} <>
316 ignore.put(String.eq(next, A.ignore(world)), next,
317 says(hash, M.dep.entry(dep), world))))
318
319
320def seal(es: List<&2, P.Effect>, outcome: P.Outcome) -> P.Plan:
321 match outcome:
322 case P.Success{}:
323 P.Plan{es, P.Success{}}
324 case P.Refused{why}:
325 P.Plan{[], P.Refused{why}}
326
327
328def made(+hash: String, +fs: List<&2, K.Item>, +ss: List<&2, String>, +world: A.World) -> P.Plan:
329 seal(effects.of(dep.of(hash, fs, world), hash, W.laid(fs, ss),
330 A.manifest.of(A.read(world)), world),
331 outcome.of(hash, fs, world))
332
333
334
335def plan.v(verdict: Verdict, +world: A.World) -> P.Plan:
336 match verdict:
337 case Go{hash, fs, ss}:
338 made(hash, fs, ss, world)
339 case Pending{_asks}:
340 P.Plan{[], P.Refused{"ez: the hub was never asked what " ++ nv(world) ++ " names"}}
341 case Stop{why}:
342 P.Plan{[], P.Refused{why}}
343
344
345def plan(+world: A.World) -> P.Plan:
346 plan.v(decide(world), world)
347
348
349def asks.v(verdict: Verdict) -> List<&2, W.Ask>:
350 match verdict:
351 case Go{_hash, _fs, _ss}:
352 []
353 case Pending{asks}:
354 asks
355 case Stop{_why}:
356 []
357
358
359def wants(+world: A.World) -> List<&2, W.Ask>:
360 asks.v(decide(world))
361
362
363
364
365
366def refuses.plan(pl: P.Plan) -> Bool:
367 P.Plan{_es, outcome} = pl
368 match outcome:
369 case P.Success{}:
370 False{}
371 case P.Refused{_why}:
372 True{}
373
374
375def refuses(+world: A.World) -> Bool:
376 refuses.plan(plan(world))
377
378
379def verdict.hash(verdict: Verdict) -> String:
380 match verdict:
381 case Go{hash, _fs, _ss}:
382 hash
383 case Pending{_asks}:
384 ""
385 case Stop{_why}:
386 ""
387
388def verdict.files(verdict: Verdict) -> List<&2, K.Item>:
389 match verdict:
390 case Go{_hash, fs, _ss}:
391 fs
392 case Pending{_asks}:
393 []
394 case Stop{_why}:
395 []
396
397
398def hash(+world: A.World) -> String:
399 verdict.hash(decide(world))
400
401
402def files(+world: A.World) -> List<&2, K.Item>:
403 verdict.files(decide(world))
404
405
406def added(+world: A.World) -> M.Dep:
407 dep.of(hash(world), files(world), world)
408
409
410def ask.hub(ask: W.Ask) -> String:
411 match ask:
412 case W.Pkg{_hash, _src, hub}:
413 hub
414 case W.Name{_nv, hub}:
415 hub
416 case W.Lock{}:
417 ""
418
419
420def asks.hub(as: List<&2, W.Ask>, +hub: String) -> Bool:
421 match as:
422 case []:
423 True{}
424 case h <> t:
425 +rest = asks.hub(t, hub)
426 Bool.and(String.eq(ask.hub(h), hub), rest)
427
428
429def names.one(ask: W.Ask) -> Bool:
430 match ask:
431 case W.Name{_nv, _hub}:
432 True{}
433 case _:
434 False{}
435
436
437def names(as: List<&2, W.Ask>) -> Bool:
438 match as:
439 case []:
440 False{}
441 case h <> t:
442 +rest = names(t)
443 Bool.or(names.one(h), rest)
444
445
446def last.go(ls: List<&2, String>, +prev: String) -> String:
447 match ls:
448 case []:
449 prev
450 case h <> t:
451 last.go(t, h)
452
453
454def last(pl: P.Plan) -> String:
455 last.go(P.said(pl), "")
456
457
458def names.laid(es: List<&2, P.Effect>) -> List<&2, L.Name>:
459 match es:
460 case []:
461 []
462 case e <> t:
463 match e:
464 case P.Name{nv, hash}:
465 L.Name{nv, hash} <> names.laid(t)
466 case _:
467 names.laid(t)