src/init/plan.bend
ezx@1.11.0.0 · 283 lines · 32 definitions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24import Base
25import ezx@1.11.0.0/src/lock/plan.bend as P
26import ezx@1.11.0.0/src/ledger/manifest.bend as M
27import ezx@1.11.0.0/src/ledger/render.bend as Rend
28import ezx@1.11.0.0/src/pub/blurb.bend as B
29
30
31
32
33type World is Data:
34 World{name: String, entry: String, desc: String, ledger: Maybe<&2, String>,
35 ignore: Maybe<&2, String>, main: Maybe<&2, String>, lib: Maybe<&2, String>}
36
37
38def toml() -> String:
39 "ez.toml"
40
41
42def ignore.file() -> String:
43 ".gitignore"
44
45
46def lib.file() -> String:
47 "src/lib.bend"
48
49
50def name.or(+at: String) -> String:
51 Bool.pick(String, String.is_empty(at), "app", at)
52
53
54def entry.or(+file: String) -> String:
55 Bool.pick(String, String.is_empty(file), "main.bend", file)
56
57
58
59
60
61
62
63
64
65
66
67
68
69def rules(+name: String) -> List<&2, String>:
70 [".ez/*", "!.ez/lib", ".ez/lib/*", "bin/" ++ name ++ ".out"]
71
72
73
74
75
76
77def stated.step(here: Bool, rest: Unit -> Bool) -> Bool:
78 match here:
79 case True{}:
80 True{}
81 case False{}:
82 rest(Unit{})
83
84
85
86def stated(ls: List<&2, String>, +rule: String) -> Bool:
87 match ls:
88 case Nil{}:
89 False{}
90 case Con{+h, t}:
91 stated.step(String.eq(String.trim(h), rule), _u => stated(t, rule))
92
93
94def missing(rs: List<&2, String>, +ls: List<&2, String>) -> List<&2, String>:
95 match rs:
96 case Nil{}:
97 Nil{}
98 case Con{+h, t}:
99 +more = missing(t, ls)
100 Bool.pick(List<&2, String>, stated(ls, h), more, h <> more)
101
102
103
104
105def tail(+text: String) -> String:
106 Bool.pick(String, Bool.or(String.is_empty(text), String.ends_with(text, "\n")),
107 text, text ++ "\n")
108
109
110
111def ignored(+text: String, ms: List<&2, String>) -> String:
112 tail(text) ++ "# written by ez\n" ++ String.join(ms, "\n") ++ "\n"
113
114
115
116
117
118def header(+name: String, +desc: String) -> String:
119 "# " ++ name ++ ": " ++ Bool.pick(String, String.is_empty(desc), "TODO describe " ++ name, desc)
120
121
122
123def body(lays: Bool) -> String:
124 match lays:
125 case True{}:
126 "import Base\nimport ./src/lib.bend as Lib\n\ndef main() -> IO(Unit):\n IO.print(Lib.greeting())\n"
127 case False{}:
128 "import Base\n\ndef main() -> IO(Unit):\n IO.print(\"hello\")\n"
129
130
131def stub(+name: String, +desc: String, lays: Bool) -> String:
132 header(name, desc) ++ "\n" ++ body(lays)
133
134
135def lib() -> String:
136 "import Base\n\n# what the program says\ndef greeting() -> String:\n \"hello\"\n"
137
138
139def text.of(got: Maybe<&2, String>) -> String:
140 match got:
141 case None{}:
142 ""
143 case Some{text}:
144 text
145
146
147
148
149def ignore.put(ms: List<&2, String>, +text: String, rest: List<&2, P.Effect>) -> List<&2, P.Effect>:
150 match ms:
151 case Nil{}:
152 rest
153 case Con{h, t}:
154 P.Write{ignore.file(), ignored(text, h <> t)} <> rest
155
156
157
158
159def due(got: Maybe<&2, String>, +at: String) -> Bool:
160 match got:
161 case Some{_s}:
162 False{}
163 case None{}:
164 Bool.not(Bool.or(String.eq(at, toml()), String.eq(at, ignore.file())))
165
166
167
168
169
170def lays(got: Maybe<&2, String>, due: Bool, +at: String) -> Bool:
171 match got:
172 case Some{_s}:
173 False{}
174 case None{}:
175 Bool.and(due, Bool.not(String.contains(at, "/")))
176
177
178def main.put(due: Bool, +at: String, +text: String, rest: List<&2, P.Effect>) -> List<&2, P.Effect>:
179 match due:
180 case True{}:
181 P.Write{at, text} <> rest
182 case False{}:
183 rest
184
185
186def lib.put(lays: Bool, rest: List<&2, P.Effect>) -> List<&2, P.Effect>:
187 match lays:
188 case True{}:
189 P.Write{lib.file(), lib()} <> rest
190 case False{}:
191 rest
192
193
194
195def files(
196 +name: String,
197 +at: String,
198 +desc: String,
199 main: Maybe<&2, String>,
200 lib: Maybe<&2, String>
201) -> List<&2, P.Effect>:
202 +d = due(main, at)
203 +l = lays(lib, d, at)
204 lib.put(l, main.put(d, at, stub(name, desc, l), []))
205
206
207
208def model(+name: String, +at: String) -> M.Manifest:
209 M.Manifest{name, at, "", "", "", "", [], []}
210
211
212def fresh(+name: String, +at: String) -> String:
213 Rend.show(model(name, at))
214
215
216
217
218def fits(+name: String, +at: String, +desc: String) -> Bool:
219 Bool.and(Rend.renderable(model(name, at)), Bool.not(B.has.nl(header(name, desc))))
220
221
222def why.unfit(+name: String, +at: String) -> String:
223 Bool.pick(String, Rend.renderable(model(name, at)),
224 "ez: a description is one line, the first line of the entry, and this one holds a newline",
225 Rend.unrenderable())
226
227
228
229
230def plan.new(
231 ok: Bool,
232 +name: String,
233 +at: String,
234 +desc: String,
235 ignore: Maybe<&2, String>,
236 main: Maybe<&2, String>,
237 lib: Maybe<&2, String>
238) -> P.Plan:
239 match ok:
240 case True{}:
241 +text = text.of(ignore)
242 P.Plan{P.Write{toml(), fresh(name, at)} <>
243 ignore.put(missing(rules(name), String.lines(text)), text,
244 files(name, at, desc, main, lib)), P.Success{}}
245 case False{}:
246 P.Plan{[], P.Refused{why.unfit(name, at)}}
247
248
249
250
251def plan.go(
252 led: Maybe<&2, String>,
253 +name: String,
254 +at: String,
255 +desc: String,
256 ignore: Maybe<&2, String>,
257 main: Maybe<&2, String>,
258 lib: Maybe<&2, String>
259) -> P.Plan:
260 match led:
261 case Some{_s}:
262 P.Plan{[], P.Refused{"ez: " ++ toml() ++
263 " already exists; ez init writes a new project and leaves it alone"}}
264 case None{}:
265 plan.new(fits(name, at, desc), name, at, desc, ignore, main, lib)
266
267
268def plan(world: World) -> P.Plan:
269 World{n, e, d, led, ig, mn, lb} = world
270 plan.go(led, name.or(n), entry.or(e), d, ig, mn, lb)
271
272
273def refuses.plan(pl: P.Plan) -> Bool:
274 P.Plan{_es, outcome} = pl
275 match outcome:
276 case P.Success{}:
277 False{}
278 case P.Refused{_why}:
279 True{}
280
281
282def refuses(world: World) -> Bool:
283 refuses.plan(plan(world))