src/lock/world.bend
ezx@1.11.0.0 · 577 lines · 69 definitions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import Base
18import ezx@1.11.0.0/src/lock/lock.bend as L
19import ezx@1.11.0.0/src/lock/up.bend as Up
20import ezx@1.11.0.0/src/ledger/upgrade.bend as U
21import ezx@1.11.0.0/src/ledger/manifest.bend as M
22import ezx@1.11.0.0/src/pkg/pkg.bend as K
23import ezx@1.11.0.0/src/hub/hub.bend as Web
24import ezx@1.11.0.0/src/share/sha.bend as Sha
25import ezx@1.11.0.0/src/share/say.bend as Say
26
27
28
29
30
31
32type Args is Data:
33 Args{upgrade: Bool, only: String, here: String}
34
35
36type Source is Data:
37 Source{at: String, text: String}
38
39
40
41type Listing is Data:
42 Listed{files: List<&2, Source>}
43 Unlisted{why: String}
44
45
46
47
48
49type How is Data:
50 Lib{}
51 Clone{nar: String}
52 Served{}
53
54
55
56
57
58
59type Ask is Data:
60 Pkg{hash: String, src: L.Src, hub: String}
61 Name{nv: String, hub: String}
62 Lock{}
63
64
65
66
67type Answer is Data:
68 Got{how: How, manifest: String, srcs: List<&2, String>}
69 Miss{why: String}
70 Said{text: String}
71 Locked{text: String}
72
73
74type Reply is Data:
75 Reply{ask: Ask, answer: Answer}
76
77
78
79
80type World is Data:
81 World{args: Args, ledger: Maybe<&2, String>, listing: Listing,
82 replies: List<&2, Reply>, ups: List<&2, Up.Reply>}
83
84
85
86
87type Verdict is Data:
88 Ok{files: List<&2, K.Item>, srcs: List<&2, String>}
89 No{why: String}
90 Named{names: List<&2, L.Name>}
91
92
93type Judged is Data:
94 Judged{hash: String, verdict: Verdict}
95
96
97
98
99
100
101
102type Inputs is Data:
103 Inputs{ledger: M.Read, stop: String, listing: Listing, judged: List<&2, Judged>}
104
105
106def args.upgrade(world: World) -> Bool:
107 World{args, _ledger, _listing, _replies, _ups} = world
108 Args{up, _only, _here} = args
109 up
110
111
112def args.only(world: World) -> String:
113 World{args, _ledger, _listing, _replies, _ups} = world
114 Args{_up, only, _here} = args
115 only
116
117
118def here.of(args: Args) -> String:
119 Args{_up, _only, here} = args
120 here
121
122
123def args.here(world: World) -> String:
124 World{args, _ledger, _listing, _replies, _ups} = world
125 here.of(args)
126
127
128def ledger.of(world: World) -> Maybe<&2, String>:
129 World{_args, ledger, _listing, _replies, _ups} = world
130 ledger
131
132
133def lockfile() -> String:
134 "ez.lock.toml"
135
136
137
138
139def ask.hash(ask: Ask) -> String:
140 match ask:
141 case Pkg{hash, _src, _hub}:
142 hash
143 case Name{nv, _hub}:
144 nv
145 case Lock{}:
146 lockfile()
147
148
149
150
151
152
153def sums.ok(fs: List<&2, K.Item>, ss: List<&2, String>) -> Bool:
154 match fs ss:
155 case Nil{} Nil{}:
156 True{}
157 case Nil{} Con{_h, _t}:
158 False{}
159 case Con{_f, _g} Nil{}:
160 False{}
161 case Con{f, g} Con{s, t}:
162 +rest = sums.ok(g, t)
163 Bool.and(String.eq(Sha.hex(s), K.file.sum(f)), rest)
164
165
166def laid(fs: List<&2, K.Item>, ss: List<&2, String>) -> List<&2, Source>:
167 match fs ss:
168 case Nil{} Nil{}:
169 []
170 case Nil{} Con{_s, _t}:
171 []
172 case Con{_f, _g} Nil{}:
173 []
174 case Con{f, g} Con{s, t}:
175 Source{K.file.at(f), s} <> laid(g, t)
176
177
178def weigh(src: Source) -> K.Item:
179 Source{at, +text} = src
180 K.Item{at, Sha.hex(text)}
181
182
183def weighs(ls: List<&2, Source>) -> List<&2, K.Item>:
184 match ls:
185 case []:
186 []
187 case h <> t:
188 weigh(h) <> weighs(t)
189
190
191
192def manifest(+ls: List<&2, Source>) -> String:
193 K.manifest_of(K.files_of(weighs(ls)))
194
195
196def named(+ls: List<&2, Source>) -> String:
197 K.hash_of(K.files_of(weighs(ls)))
198
199
200
201
202
203
204
205def body.laid(ok: Bool, +hash: String, fs: List<&2, K.Item>, ss: List<&2, String>) -> Verdict:
206 match ok:
207 case True{}:
208 Ok{fs, ss}
209 case False{}:
210 No{"ez: " ++ hash ++ ": its manifest is not written the way its name is taken" ++
211 " (one line per file, in path order, each file once)"}
212
213
214def body.sums(ok: Bool, +hash: String, +fs: List<&2, K.Item>, +ss: List<&2, String>) -> Verdict:
215 match ok:
216 case True{}:
217 body.laid(String.eq(hash, named(laid(fs, ss))), hash, fs, ss)
218 case False{}:
219 No{"ez: " ++ hash ++ ": a file does not match its manifest"}
220
221
222def body.escape.go(clean: Bool, +bad: String, +hash: String, +fs: List<&2, K.Item>, +ss: List<&2, String>) -> Verdict:
223 match clean:
224 case True{}:
225 body.sums(sums.ok(fs, ss), hash, fs, ss)
226 case False{}:
227 No{"ez: " ++ hash ++ ": its manifest escapes the package (" ++ bad ++ ")"}
228
229
230def body.escape(+bad: String, +hash: String, +fs: List<&2, K.Item>, +ss: List<&2, String>) -> Verdict:
231 body.escape.go(String.is_empty(bad), bad, hash, fs, ss)
232
233
234def body.named(ok: Bool, +hash: String, +manifest: String, +ss: List<&2, String>) -> Verdict:
235 match ok:
236 case True{}:
237 +fs = L.manifest.files(String.lines(manifest))
238 body.escape(K.escaped(fs), hash, fs, ss)
239 case False{}:
240 No{"ez: " ++ hash ++ ": its manifest does not hash to its name"}
241
242
243
244
245
246
247
248def body(+hash: String, +manifest: String, +ss: List<&2, String>) -> Verdict:
249 body.named(Web.matches(L.want(hash), manifest), hash, manifest, ss)
250
251
252
253def git.body(empty: Bool, +hash: String, +manifest: String, +ss: List<&2, String>) -> Verdict:
254 match empty:
255 case True{}:
256 No{"ez: " ++ hash ++ ": its manifest names no files"}
257 case False{}:
258 body(hash, manifest, ss)
259
260
261
262
263
264
265
266def git.rule(named: Bool, +hash: String, +manifest: String, +ss: List<&2, String>) -> Verdict:
267 match named:
268 case True{}:
269 git.body(List.is_empty(&2, K.Item, L.manifest.files(String.lines(manifest))),
270 hash, manifest, ss)
271 case False{}:
272 +fs = L.manifest.files(String.lines(manifest))
273 +bs = K.bare(fs)
274 git.body(List.is_empty(&2, K.Item, bs), hash, K.manifest.lines(bs),
275 K.bare.srcs(fs, ss))
276
277
278
279
280def git.weighed.go(same: Bool, +why: String, +hash: String, +manifest: String, +ss: List<&2, String>) -> Verdict:
281 match same:
282 case True{}:
283 git.rule(Web.matches(L.want(hash), manifest), hash, manifest, ss)
284 case False{}:
285 No{why}
286
287def git.weighed(+why: String, +hash: String, +manifest: String, +ss: List<&2, String>) -> Verdict:
288 git.weighed.go(String.is_empty(why), why, hash, manifest, ss)
289
290
291def git.how(
292 how: How,
293 +hash: String,
294 +url: String,
295 +rev: String,
296 +nar: String,
297 +manifest: String,
298 +ss: List<&2, String>
299) -> Verdict:
300 match how:
301 case Lib{}:
302 git.weighed(L.nar.why(nar, nar, url, rev), hash, manifest, ss)
303 case Clone{got}:
304 git.weighed(L.nar.why(nar, got, url, rev), hash, manifest, ss)
305 case Served{}:
306 No{"ez: " ++ hash ++ " is a git package and was not read from git"}
307
308
309def hub.how(how: How, +hash: String, +manifest: String, +ss: List<&2, String>) -> Verdict:
310 match how:
311 case Lib{}:
312 No{"ez: " ++ hash ++ " is a hub package and was not served by the hub"}
313 case Clone{_got}:
314 No{"ez: " ++ hash ++ " is a hub package and was not served by the hub"}
315 case Served{}:
316 body(hash, manifest, ss)
317
318
319
320
321
322def member.how(how: How, +hash: String, +manifest: String, +ss: List<&2, String>) -> Verdict:
323 match how:
324 case Lib{}:
325 body(hash, manifest, ss)
326 case Clone{_got}:
327 body(hash, manifest, ss)
328 case Served{}:
329 No{"ez: " ++ hash ++ " is a workspace member and was not read from the workspace"}
330
331
332def accept.src(src: L.Src, how: How, +hash: String, +manifest: String, +ss: List<&2, String>) -> Verdict:
333 match src:
334 case L.Hub{}:
335 hub.how(how, hash, manifest, ss)
336 case L.Git{url, rev, _entry, _root, nar, _tag}:
337 git.how(how, hash, url, rev, nar, manifest, ss)
338 case L.Member{_path, _entry}:
339 member.how(how, hash, manifest, ss)
340
341
342
343
344def hub.why(named: Bool, +hash: String, +hub: String, +why: String) -> String:
345 match named:
346 case True{}:
347 Say.hub.explain(hash, L.url_of(hub, hash, "manifest"), why)
348 case False{}:
349 "ez: " ++ hash ++ " is imported, ez.toml does not name it, and the hub at " ++
350 hub ++ " did not serve it (" ++ why ++ "). A package taken from git is recorded with `ez add`."
351
352
353def miss.src(src: L.Src, named: Bool, +hash: String, +hub: String, +why: String) -> String:
354 match src:
355 case L.Hub{}:
356 hub.why(named, hash, hub, why)
357 case L.Git{_url, _rev, _entry, _root, _nar, _tag}:
358 why
359 case L.Member{_path, _entry}:
360 why
361
362
363
364def said.ok(ok: Bool, +nv: String, +hash: String, +hub: String) -> Verdict:
365 match ok:
366 case True{}:
367 Named{[L.Name{nv, hash}]}
368 case False{}:
369 No{"ez: the hub at " ++ hub ++ " names no package " ++ nv ++ " (it answered `" ++
370 hash ++ "`)"}
371
372
373
374def said(+nv: String, +hub: String, +text: String) -> Verdict:
375 +hash = String.trim(text)
376 said.ok(L.hash.ok(hash), nv, hash, hub)
377
378
379def locked(text: String) -> Verdict:
380 Named{L.names.read(text)}
381
382
383def accept(+orgs: List<&2, L.Origin>, +hub: String, +hash: String, answer: Answer) -> Verdict:
384 match answer:
385 case Miss{why}:
386 No{miss.src(L.origin(orgs, hash), L.known(orgs, hash), hash, hub, why)}
387 case Got{how, manifest, ss}:
388 accept.src(L.origin(orgs, hash), how, hash, manifest, ss)
389 case Said{text}:
390 said(hash, hub, text)
391 case Locked{text}:
392 locked(text)
393
394
395def judge(+orgs: List<&2, L.Origin>, +hub: String, rs: List<&2, Reply>) -> List<&2, Judged>:
396 match rs:
397 case []:
398 []
399 case Reply{ask, answer} <> t:
400 +hash = ask.hash(ask)
401 Judged{hash, accept(orgs, hub, hash, answer)} <> judge(orgs, hub, t)
402
403
404
405def verdict.names(verdict: Verdict) -> List<&2, L.Name>:
406 match verdict:
407 case Ok{_fs, _ss}:
408 []
409 case No{_why}:
410 []
411 case Named{ns}:
412 ns
413
414
415
416
417def names.of(js: List<&2, Judged>) -> List<&2, L.Name>:
418 match js:
419 case []:
420 []
421 case Judged{_h, v} <> t:
422 List.append(&2, L.Name, verdict.names(v), names.of(t))
423
424
425
426def parsed(ledger: Maybe<&2, String>) -> M.Read:
427 match ledger:
428 case None{}:
429 M.Bad{"no ez.toml here; ez lock works on a project's ledger, and ez init makes one"}
430 case Some{text}:
431 M.parse(text)
432
433
434def origins(+ledger: Maybe<&2, String>) -> List<&2, L.Origin>:
435 L.ledger.read(parsed(ledger))
436
437
438def dep(world: World, name: String) -> M.Dep:
439 World{_args, ledger, _listing, _replies, _ups} = world
440 M.dep(parsed(ledger), name)
441
442
443def ledger.hub(+ledger: Maybe<&2, String>) -> String:
444 M.hub_of(parsed(ledger))
445
446
447
448
449
450
451
452def next.up(up: Bool, +only: String, +ledger: Maybe<&2, String>, ups: List<&2, Up.Reply>) -> Up.Next:
453 match up:
454 case False{}:
455 Up.plain(parsed(ledger))
456 case True{}:
457 Up.next(only, parsed(ledger), ups)
458
459
460def next(args: Args, +ledger: Maybe<&2, String>, ups: List<&2, Up.Reply>) -> Up.Next:
461 Args{up, +only, _here} = args
462 next.up(up, only, ledger, ups)
463
464
465def next.origins(+nx: Up.Next) -> List<&2, L.Origin>:
466 L.ledger.read(Up.next.read(nx))
467
468
469def next.hub(+nx: Up.Next) -> String:
470 M.hub_of(Up.next.read(nx))
471
472
473
474def laid.reply(laid: Up.Laid, +orgs: List<&2, L.Origin>, +hub: String) -> Reply:
475 Up.Laid{+hash, nar, manifest, srcs} = laid
476 Reply{Pkg{hash, L.origin(orgs, hash), hub}, Got{Clone{nar}, manifest, srcs}}
477
478def laid.replies(ls: List<&2, Up.Laid>, +orgs: List<&2, L.Origin>, +hub: String) -> List<&2, Reply>:
479 match ls:
480 case []:
481 []
482 case h <> t:
483 laid.reply(h, orgs, hub) <> laid.replies(t, orgs, hub)
484
485
486
487def replies.of(+nx: Up.Next, replies: List<&2, Reply>) -> List<&2, Reply>:
488 List.append(&2, Reply,
489 laid.replies(Up.next.trees(nx), next.origins(nx), next.hub(nx)), replies)
490
491
492def judged.of(+nx: Up.Next, replies: List<&2, Reply>) -> List<&2, Judged>:
493 List.append(&2, Judged,
494 judge(next.origins(nx), next.hub(nx),
495 laid.replies(Up.next.trees(nx), next.origins(nx), next.hub(nx))),
496 judge(next.origins(nx), next.hub(nx), replies))
497
498
499
500def sources.swap(+ss: List<&2, U.Swap>, fs: List<&2, Source>) -> List<&2, Source>:
501 match fs:
502 case []:
503 []
504 case Source{at, text} <> t:
505 Source{at, U.reimport.many(ss, text)} <> sources.swap(ss, t)
506
507def listing.swap(+ss: List<&2, U.Swap>, listing: Listing) -> Listing:
508 match listing:
509 case Listed{fs}:
510 Listed{sources.swap(ss, fs)}
511 case Unlisted{why}:
512 Unlisted{why}
513
514
515
516def listing.moved(ss: List<&2, U.Swap>, listing: Listing) -> Listing:
517 match ss:
518 case []:
519 listing
520 case h <> t:
521 listing.swap(h <> t, listing)
522
523def listing.of(+nx: Up.Next, listing: Listing) -> Listing:
524 listing.moved(Up.next.swaps(nx), listing)
525
526
527def inputs.of(+nx: Up.Next, listing: Listing, replies: List<&2, Reply>) -> Inputs:
528 Inputs{Up.next.read(nx), Up.next.stop(nx), listing.of(nx, listing),
529 judged.of(nx, replies)}
530
531
532def inputs(world: World) -> Inputs:
533 World{args, +ledger, listing, replies, ups} = world
534 inputs.of(next(args, ledger, ups), listing, replies)
535
536
537
538
539
540
541
542def reclone.how(how: How, +src: L.Src) -> How:
543 match how:
544 case Lib{}:
545 Clone{L.src.nar(src)}
546 case Clone{got}:
547 Clone{got}
548 case Served{}:
549 Served{}
550
551def reclone.one(+orgs: List<&2, L.Origin>, +hash: String, answer: Answer) -> Answer:
552 match answer:
553 case Miss{why}:
554 Miss{why}
555 case Got{how, manifest, ss}:
556 Got{reclone.how(how, L.origin(orgs, hash)), manifest, ss}
557 case Said{text}:
558 Said{text}
559 case Locked{text}:
560 Locked{text}
561
562
563
564def reclone.all(+orgs: List<&2, L.Origin>, rs: List<&2, Reply>) -> List<&2, Reply>:
565 match rs:
566 case []:
567 []
568 case Reply{+ask, answer} <> t:
569 Reply{ask, reclone.one(orgs, ask.hash(ask), answer)} <> reclone.all(orgs, t)
570
571
572
573
574def reclone(world: World) -> World:
575 World{+args, +ledger, listing, replies, +ups} = world
576 World{args, ledger, listing,
577 reclone.all(next.origins(next(args, ledger, ups)), replies), ups}