src/ws/run.bend
ezx@1.11.0.0 · 448 lines · 47 definitions
1
2
3
4
5
6
7
8import Base
9import snap@1.4.0.0/main.bend as Sn
10import ezx@1.11.0.0/src/io/file.bend as F
11import ezx@1.11.0.0/src/share/args.bend as Args
12import ezx@1.11.0.0/src/share/pass.bend as Pass
13import ezx@1.11.0.0/src/ledger/render.bend as Rend
14import ezx@1.11.0.0/src/ws/workspace.bend as W
15import ezx@1.11.0.0/src/ws/root.bend as R
16import ezx@1.11.0.0/src/ws/hop.bend as H
17import ezx@1.11.0.0/src/ws/joint.bend as J
18import ezx@1.11.0.0/src/ws/up.bend as U
19import ezx@1.11.0.0/src/ws/pin.bend as Pin
20import ezx@1.11.0.0/src/ledger/manifest.bend as M
21import ezx@1.11.0.0/src/pkg/path.bend as P
22import ezx@1.11.0.0/src/pkg/pkg.bend as K
23import ezx@1.11.0.0/src/lock/world.bend as LW
24import ezx@1.11.0.0/src/ws/doctor.bend as Doc
25import ezx@1.11.0.0/src/ws/relock.bend as Rel
26
27
28type Searched is Data:
29 Searched{top: String, found: R.Found}
30
31
32def ledger.path(+top: String, ds: List<&2, String>) -> String:
33 top ++ "/" ++ W.segs.text(List.append(&2, String, ds, ["ez.toml"]))
34
35
36def read.ups(+top: String, asks: List<&2, R.Ask>) -> IO(List<&2, R.Up>):
37 match asks:
38 case []:
39 IO.pure(List<&2, R.Up>, [])
40 case R.Ask{+ds, rs} <> tl:
41 do IO<List<&2, R.Up>>:
42 tx : Maybe<&2, String> <- F.read(ledger.path(top, ds))
43 rest : List<&2, R.Up> <- read.ups(top, tl)
44 return R.Up{ds, rs, tx} <> rest
45
46
47
48def search.at(here: Maybe<&2, List<&2, String>>, +top: String) -> IO(Searched):
49 match here:
50 case None{}:
51 IO.pure(Searched, Searched{"", R.Alone{}})
52 case Some{hs}:
53 do IO<Searched>:
54 ups : List<&2, R.Up> <- read.ups(top, R.asks(hs))
55 return Searched{top, R.root.go(ups)}
56
57
58def here.git(ok: Bool, +text: String) -> Maybe<&2, List<&2, String>>:
59 match ok:
60 case True{}:
61 R.here.of(String.trim(text))
62 case False{}:
63 None{}
64
65
66def search() -> IO(Searched):
67 do IO<Searched>:
68 +pre : String <- Sn.exec(["git", "rev-parse", "--show-prefix"])
69 +top : String <- Sn.exec(["git", "rev-parse", "--show-toplevel"])
70 search.at(here.git(Bool.and(Sn.ok(pre), Sn.ok(top)), Sn.text(pre)), String.trim(Sn.text(top)))
71
72
73def argv() -> IO(List<&2, String>):
74 do IO<List<&2, String>>:
75 xs : List<&1, String> <- IO.args()
76 return Args.copy(xs)
77
78
79def hop.go(+dir: String, +pwd: String, xs: List<&2, String>) -> IO(U32):
80 match xs:
81 case []:
82 IO.pure(U32, 1)
83 case +prog <> rest:
84 Pass.run(H.line(dir, pwd, prog, rest))
85
86
87def hop.end(zero: Bool, +code: U32) -> IO(Unit):
88 match zero:
89 case True{}:
90 IO.pure(Unit, Unit{})
91 case False{}:
92 IO.exit(code)
93
94
95def hop.run(+dir: String) -> IO(Unit):
96 do IO<Unit>:
97 +pwd : String <- Sn.exec(["pwd"])
98 xs : List<&2, String> <- argv()
99 +code : U32 <- hop.go(dir, String.trim(Sn.text(pwd)), xs)
100 hop.end(U32.is_eq(code, 0), code)
101
102
103def hop.at(hop: H.Hop, go: Unit -> IO(Unit)) -> IO(Unit):
104 match hop:
105 case H.Here{}:
106 go(Unit{})
107 case H.Stop{why}:
108 IO.die(Unit, 1, why)
109 case H.Hop{dir}:
110 hop.run(dir)
111
112
113def at.searched(st: Searched, go: Unit -> IO(Unit)) -> IO(Unit):
114 Searched{+top, found} = st
115 hop.at(H.hop(top, found), go)
116
117
118
119
120def at.root(go: Unit -> IO(Unit)) -> IO(Unit):
121 do IO<Unit>:
122 st : Searched <- search()
123 at.searched(st, go)
124
125
126
127
128
129def read.found(ps: List<&2, String>) -> IO(List<&2, J.Found>):
130 match ps:
131 case []:
132 IO.pure(List<&2, J.Found>, [])
133 case +hd <> tl:
134 do IO<List<&2, J.Found>>:
135 mm : Maybe<&2, String> <- F.read(hd)
136 rest : List<&2, J.Found> <- read.found(tl)
137 return J.Found{hd, F.text_of(mm)} <> rest
138
139
140def lines(ss: List<&2, String>) -> List<&2, String>:
141 match ss:
142 case []:
143 []
144 case +hd <> tl:
145 +more = lines(tl)
146 Bool.pick(List<&2, String>, String.is_empty(hd), more, hd <> more)
147
148
149
150type Planned is Data:
151 Planned{ledger: Maybe<&2, String>, back: U.Back, seeds: List<&2, LW.Reply>}
152
153
154def joint.at(
155 jj: J.Joint,
156 +text: String,
157 +spec: W.Spec,
158 +fs: List<&2, J.Found>,
159 +files: List<&2, K.Source>,
160 +lock: String
161) -> IO(Planned):
162 match jj:
163 case J.Lone{}:
164 IO.pure(Planned, Planned{Some{text}, U.Alone{}, []})
165 case J.Clash{why}:
166 IO.die(Planned, 1, why)
167 case J.Joint{+mm}:
168 IO.pure(Planned, Planned{Some{Rend.show(mm)}, U.Back{text, spec, fs, lock},
169 J.seeds(J.Joint{mm}, files)})
170
171
172def joint.listed(
173 ok: Bool,
174 +text: String,
175 +spec: W.Spec,
176 +out: String,
177 +files: List<&2, K.Source>
178) -> IO(Planned):
179 match ok:
180 case True{}:
181 do IO<Planned>:
182 +fs : List<&2, J.Found> <- read.found(lines(String.lines(out)))
183 lk : Maybe<&2, String> <- F.read("ez.lock.toml")
184 joint.at(J.joint(text, fs, files), text, spec, fs, files, F.text_of(lk))
185 case False{}:
186 IO.die(Planned, 1,
187 "ez: a workspace is locked from the ez.toml files git tracks, and git could not list them: " ++
188 String.trim(out))
189
190
191def joint.go(+text: String, +spec: W.Spec, +files: List<&2, K.Source>) -> IO(Planned):
192 do IO<Planned>:
193 +out : String <- Sn.exec(["git", "-c", "core.quotepath=off", "ls-files", "--", ":(glob)**/ez.toml"])
194 joint.listed(Sn.ok(out), text, spec, Sn.text(out), files)
195
196
197
198
199def joint.read(rd: W.Read, +text: String, +files: List<&2, K.Source>) -> IO(Planned):
200 match rd:
201 case W.Lone{}:
202 IO.pure(Planned, Planned{Some{text}, U.Alone{}, []})
203 case W.Bad{why}:
204 IO.die(Planned, 1, why)
205 case W.Root{+spec}:
206 joint.go(text, spec, files)
207
208
209
210
211def ledger(led: Maybe<&2, String>, +files: List<&2, K.Source>) -> IO(Planned):
212 match led:
213 case None{}:
214 IO.pure(Planned, Planned{None{}, U.Alone{}, []})
215 case Some{+text}:
216 joint.read(W.spec.of(text), text, files)
217
218
219
220
221
222def env.text(rr: Result<&1, &1, U32 & String, String>) -> String:
223 match rr:
224 case Fail{_e}:
225 ""
226 case Done{ss}:
227 String.trim(ss)
228
229
230def env.lib() -> IO(String):
231 do IO<String>:
232 rr : Result<&1, &1, U32 & String, String> <- IO.get_env("BEND_LIB")
233 return env.text(rr)
234
235
236def lib.go(+at: String, +pwd: String, xs: List<&2, String>) -> IO(U32):
237 match xs:
238 case []:
239 IO.pure(U32, 1)
240 case +prog <> rest:
241 Pass.run(H.lib.line(at, pwd, prog, rest))
242
243
244
245def lib.at(lib: H.Lib, go: Unit -> IO(Unit)) -> IO(Unit):
246 match lib:
247 case H.Keep{}:
248 go(Unit{})
249 case H.Set{+at}:
250 do IO<Unit>:
251 +pwd : String <- Sn.exec(["pwd"])
252 xs : List<&2, String> <- argv()
253 +code : U32 <- lib.go(at, String.trim(Sn.text(pwd)), xs)
254 hop.end(U32.is_eq(code, 0), code)
255
256
257def in.searched(st: Searched, +env: String, go: Unit -> IO(Unit)) -> IO(Unit):
258 Searched{+top, found} = st
259 lib.at(H.lib(top, found, env), go)
260
261
262
263
264def in.member(go: Unit -> IO(Unit)) -> IO(Unit):
265 do IO<Unit>:
266 st : Searched <- search()
267 +env : String <- env.lib()
268 in.searched(st, env, go)
269
270
271def read.under(+dir: String, ps: List<&2, String>) -> IO(List<&2, J.Found>):
272 match ps:
273 case []:
274 IO.pure(List<&2, J.Found>, [])
275 case +hd <> tl:
276 do IO<List<&2, J.Found>>:
277 mm : Maybe<&2, String> <- F.read(dir ++ "/" ++ hd)
278 rest : List<&2, J.Found> <- read.under(dir, tl)
279 return J.Found{hd, F.text_of(mm)} <> rest
280
281
282def pins.joint(jj: J.Joint) -> List<&2, M.Dep>:
283 match jj:
284 case J.Joint{mm}:
285 M.Manifest{_n, _e, _b, _h, _pa, _pv, ds, _ts} = mm
286 ds
287 case _:
288 []
289
290
291def files.under(+dir: String, ps: List<&2, String>) -> IO(List<&2, K.Source>):
292 match ps:
293 case []:
294 IO.pure(List<&2, K.Source>, [])
295 case +hd <> tl:
296 do IO<List<&2, K.Source>>:
297 mm : Maybe<&2, String> <- F.read(dir ++ "/" ++ hd)
298 rest : List<&2, K.Source> <- files.under(dir, tl)
299 return K.Source{hd, F.text_of(mm)} <> rest
300
301
302def pins.at(+dir: String) -> IO(List<&2, M.Dep>):
303 do IO<List<&2, M.Dep>>:
304 mm : Maybe<&2, String> <- F.read(dir ++ "/ez.toml")
305 +out : String <- Sn.exec(["git", "-C", dir, "-c", "core.quotepath=off", "ls-files", "--",
306 ":(glob)**/ez.toml"])
307 fs : List<&2, J.Found> <- read.under(dir, lines(String.lines(Sn.text(out))))
308 +bend : String <- Sn.exec(["git", "-C", dir, "-c", "core.quotepath=off", "ls-files", "--", "*.bend"])
309 files : List<&2, K.Source> <- files.under(dir, lines(String.lines(Sn.text(bend))))
310 return pins.joint(J.joint(F.text_of(mm), fs, files))
311
312
313def ref.held(rr: Pin.Ref) -> IO(String):
314 match rr:
315 case Pin.Given{ref}:
316 IO.pure(String, ref)
317 case Pin.Held{why}:
318 IO.die(String, 1, why)
319
320
321def ref.found(+top: String, found: R.Found, +home: String, +target: String, +ref: String) -> IO(String):
322 match found:
323 case R.Within{+root, +rel}:
324 do IO<String>:
325 +pins : List<&2, M.Dep> <- pins.at(P.join(top, W.segs.text(root)))
326 ref.held(Pin.ref(pins, W.segs.text(rel), home, target, ref))
327 case _:
328 IO.pure(String, ref)
329
330
331def add.searched(st: Searched, +home: String, +target: String, +ref: String) -> IO(String):
332 Searched{+top, found} = st
333 ref.found(top, found, home, target, ref)
334
335
336def add.ref(+home: String, +target: String, +ref: String) -> IO(String):
337 do IO<String>:
338 st : Searched <- search()
339 add.searched(st, home, target, ref)
340
341
342
343
344
345
346def sources.under(+dir: String, ps: List<&2, String>) -> IO(List<&2, LW.Source>):
347 match ps:
348 case []:
349 IO.pure(List<&2, LW.Source>, [])
350 case +hd <> tl:
351 do IO<List<&2, LW.Source>>:
352 mm : Maybe<&2, String> <- F.read(dir ++ "/" ++ hd)
353 rest : List<&2, LW.Source> <- sources.under(dir, tl)
354 return LW.Source{hd, F.text_of(mm)} <> rest
355
356
357def listing.listed(ok: Bool, +dir: String, +out: String) -> IO(LW.Listing):
358 match ok:
359 case True{}:
360 do IO<LW.Listing>:
361 fs : List<&2, LW.Source> <- sources.under(dir, lines(String.lines(out)))
362 return LW.Listed{fs}
363 case False{}:
364 IO.pure(LW.Listing, LW.Unlisted{
365 "ez: ez lock reads the .bend files git tracks, and git could not list them: " ++ String.trim(out)})
366
367
368
369def listing.at(+dir: String) -> IO(LW.Listing):
370 do IO<LW.Listing>:
371 +out : String <- Sn.exec(["git", "-C", dir, "-c", "core.quotepath=off", "ls-files", "--", "*.bend"])
372 listing.listed(Sn.ok(out), dir, Sn.text(out))
373
374
375
376def rooted(+at: String, rel: List<&2, String>) -> IO(Rel.Rooted):
377 do IO<Rel.Rooted>:
378 mm : Maybe<&2, String> <- F.read(at ++ "/ez.toml")
379 +out : String <- Sn.exec(["git", "-C", at, "-c", "core.quotepath=off", "ls-files", "--",
380 ":(glob)**/ez.toml"])
381 fs : List<&2, J.Found> <- read.under(at, lines(String.lines(Sn.text(out))))
382 ls : LW.Listing <- listing.at(at)
383 lk : Maybe<&2, String> <- F.read(at ++ "/ez.lock.toml")
384 return Rel.Rooted{at, rel, F.text_of(mm), fs, ls, F.text_of(lk)}
385
386
387def ws.found(+top: String, found: R.Found) -> IO(Rel.Ws):
388 match found:
389 case R.Within{root, rel}:
390 do IO<Rel.Ws>:
391 rt : Rel.Rooted <- rooted(P.join(top, W.segs.text(root)), rel)
392 return Rel.Inside{rt}
393 case _:
394 IO.pure(Rel.Ws, Rel.Own{})
395
396
397def ws.searched(st: Searched) -> IO(Rel.Ws):
398 Searched{+top, found} = st
399 ws.found(top, found)
400
401
402
403def relock.ws() -> IO(Rel.Ws):
404 do IO<Rel.Ws>:
405 st : Searched <- search()
406 ws.searched(st)
407
408
409def lock.searched(st: Searched) -> String:
410 Searched{+top, found} = st
411 H.lock(top, found)
412
413
414def lock.path() -> IO(String):
415 do IO<String>:
416 st : Searched <- search()
417 return lock.searched(st)
418
419
420
421
422
423def doctor.root(+text: String) -> IO(Doc.Doctored):
424 do IO<Doc.Doctored>:
425 +out : String <- Sn.exec(["git", "-c", "core.quotepath=off", "ls-files", "--", ":(glob)**/ez.toml"])
426 +fs : List<&2, J.Found> <- read.found(lines(String.lines(Sn.text(out))))
427 +bend : String <- Sn.exec(["git", "-c", "core.quotepath=off", "ls-files", "--", "*.bend"])
428 files : List<&2, K.Source> <- files.under(".", lines(String.lines(Sn.text(bend))))
429 return Doc.joint(J.joint(text, fs, files), text, fs)
430
431
432def doctor.read(rd: W.Read, +text: String) -> IO(Doc.Doctored):
433 match rd:
434 case W.Lone{}:
435 IO.pure(Doc.Doctored, Doc.Doctored{Some{text}, Doc.Alone{}})
436 case W.Bad{why}:
437 IO.pure(Doc.Doctored, Doc.Doctored{Some{text}, Doc.Back{text, [], why}})
438 case W.Root{_spec}:
439 doctor.root(text)
440
441
442
443def doctor.ledger(led: Maybe<&2, String>) -> IO(Doc.Doctored):
444 match led:
445 case None{}:
446 IO.pure(Doc.Doctored, Doc.Doctored{None{}, Doc.Alone{}})
447 case Some{+text}:
448 doctor.read(W.spec.of(text), text)