domain/architecture/system/deployment/nix_darwin/brew/effects.bend
near-v-framework@0.4.0.0 · 95 lines · 13 definitions
1import Base
2import near-v-framework@0.4.0.0/domain/architecture/system/effs/io.bend as X
3import near-v-framework@0.4.0.0/domain/architecture/system/plan/type.bend as P
4import near-v-framework@0.4.0.0/domain/architecture/system/deployment/nix_darwin/brew/type.bend as Br
5import near-v-framework@0.4.0.0/domain/architecture/system/deployment/nix_darwin/brew/ops.bend as BrO
6
7
8
9
10def declared_file() -> String:
11 "/tmp/v-declared.Brewfile"
12
13def observed_file() -> String:
14 "/tmp/v-observed.Brewfile"
15
16
17
18def bundle(args: List<String>) -> IO(Result<&1, &1, U32 & String, U32 & (String & String)>):
19 Process.run("brew", "bundle" <> args, "", 67108864, 600000)
20
21
22def text_before(cs: List<&2, Char>, +pat: String) -> String:
23 match cs:
24 case []:
25 ""
26 case +c <> +rest:
27 Bool.pick(String, String.starts_with(String.from_list(c <> rest), pat), "", String.from_list([c]) ++ text_before(rest, pat))
28
29def missing(ls: List<&2, String>) -> List<&2, P.Step>:
30 match ls:
31 case []:
32 []
33 case l <> rest:
34 +t = String.trim(l)
35 List.append(&2, P.Step,
36 Bool.pick(List<&2, P.Step>, String.starts_with(t, "→ "),
37 [P.Change{"homebrew", "(absent)", text_before(String.to_list(String.drop(t, 2n)), " needs to be ")}], []),
38 missing(rest))
39
40def some_or(+xs: List<&2, P.Step>, +f: String) -> List<&2, P.Step>:
41 Bool.pick(List<&2, P.Step>, List.is_empty(&2, P.Step, xs),
42 [P.Change{"homebrew", "declared entries missing", "see brew bundle check --verbose --file " ++ f}], xs)
43
44def installs(r: Result<&1, &1, U32 & String, U32 & (String & String)>, +f: String) -> List<&2, P.Step>:
45 match r:
46 case Done{(status, (_, err))}:
47 Bool.pick(List<&2, P.Step>, U32.is_eq(status, 0), [], some_or(missing(String.lines(err)), f))
48 case Fail{_}:
49 [P.Blocked{"homebrew", "brew bundle check could not run"}]
50
51
52def label_of(+heading: String) -> String:
53 Bool.pick(String, String.eq(heading, "casks"), "Cask",
54 Bool.pick(String, String.eq(heading, "formulae"), "Formula", heading))
55
56
57def section(+l: String, +label: String) -> String:
58 Bool.pick(String, String.starts_with(l, "Would uninstall ") && String.ends_with(l, ":"),
59 label_of(String.take(String.drop(l, 16n), Nat.sub(String.length(l), 17n))),
60 Bool.pick(String, String.eq(l, "Would untap:"), "Tap",
61 Bool.pick(String, String.is_empty(l) || String.starts_with(l, "Would ") || String.starts_with(l, "Run ") || String.starts_with(l, "==>"), "", label)))
62
63def removals(ls: List<&2, String>, +label: String) -> List<&2, P.Step>:
64 match ls:
65 case []:
66 []
67 case l <> rest:
68 +t = String.trim(l)
69 +next = section(t, label)
70 List.append(&2, P.Step,
71 Bool.pick(List<&2, P.Step>, Bool.not(String.is_empty(label)) && String.eq(next, label) && Bool.not(String.is_empty(t)),
72 [P.Change{"homebrew", label ++ " " ++ t, "(absent)"}], []),
73 removals(rest, next))
74
75def removed(r: Result<&1, &1, U32 & String, U32 & (String & String)>) -> List<&2, P.Step>:
76 match r:
77 case Done{(_, (out, _))}:
78 removals(String.lines(out), "")
79 case Fail{_}:
80 [P.Blocked{"homebrew", "brew bundle cleanup could not run"}]
81
82
83def changes(brews: List<&2, Br.Brew>) -> IO(List<&2, P.Step>):
84 do IO<List<&2, P.Step>>:
85 X.write(declared_file(), BrO.brewfile(brews))
86 c : Result<&1, &1, U32 & String, U32 & (String & String)> <- bundle(["check", "--no-upgrade", "--verbose", "--file", declared_file()])
87 r : Result<&1, &1, U32 & String, U32 & (String & String)> <- bundle(["cleanup", "--file", declared_file()])
88 return List.append(&2, P.Step, installs(c, declared_file()), removed(r))
89
90
91def observed() -> IO(List<&2, Br.Brew>):
92 do IO<List<&2, Br.Brew>>:
93 ok : Bool <- X.succeeds("brew", ["bundle", "dump", "--force", "--file=" ++ observed_file()])
94 text : String <- X.read(observed_file())
95 return Bool.pick(List<&2, Br.Brew>, ok, BrO.of_brewfile(text), [])