domain/architecture/view/ops.bend
near-v-framework@0.7.0.0 · 350 lines · 0 definitions
1import Base
2import near-v-framework@0.7.0.0/general/rose/type.bend as R
3import near-v-framework@0.7.0.0/general/rose/ops.bend as RO
4import near-v-framework@0.7.0.0/general/compound/type.bend as C
5import near-v-framework@0.7.0.0/general/compound/ops.bend as CO
6import near-v-framework@0.7.0.0/general/compound/edge/type.bend as E
7import near-v-framework@0.7.0.0/domain/architecture/type.bend as A
8import near-v-framework@0.7.0.0/domain/architecture/ops.bend as O
9import near-v-framework@0.7.0.0/domain/architecture/system/deployment/type.bend as D
10import near-v-framework@0.7.0.0/domain/architecture/view/type.bend as Vw
11
12# ---- one element as a D2 box, in C4's colors: people dark blue, what the view is about blue,
13# everything around it gray ----
14def level_name(l: A.Level) -> String:
15 match l:
16 case A.PersonLevel{}:
17 "Person"
18 case A.SystemLevel{}:
19 "Software System"
20 case A.ContainerLevel{}:
21 "Container"
22 case A.ComponentLevel{}:
23 "Component"
24 case A.ExternalLevel{}:
25 "Software System"
26
27def kind_line(+l: A.Level, +t: String) -> String:
28 Bool.pick(String, String.is_empty(t), "[" ++ level_name(l) ++ "]", "[" ++ level_name(l) ++ ": " ++ t ++ "]")
29
30def is_person(l: A.Level) -> Bool:
31 match l:
32 case A.PersonLevel{}:
33 True{}
34 case _:
35 False{}
36
37def focus_fill(l: A.Level) -> String:
38 match l:
39 case A.PersonLevel{}:
40 "#08427B"
41 case A.SystemLevel{}:
42 "#1168BD"
43 case A.ContainerLevel{}:
44 "#438DD5"
45 case A.ComponentLevel{}:
46 "#85BBF0"
47 case A.ExternalLevel{}:
48 "#999999"
49
50def fill(+l: A.Level, focus: Bool) -> String:
51 match focus:
52 case False{}:
53 Bool.pick(String, is_person(l), "#08427B", "#999999")
54 case True{}:
55 focus_fill(l)
56
57def box(+path: String, e: A.Element, +focus: Bool) -> String:
58 match e:
59 case A.Element{n, d, t, +l}:
60 path ++ ": |md\n **" ++ n ++ "**\n\n " ++ kind_line(l, t) ++ "\n\n " ++ d ++ "\n| {shape: " ++ Bool.pick(String, is_person(l), "c4-person", "rectangle") ++ "; style.fill: \"" ++ fill(l, focus) ++ "\"; style.stroke: \"" ++ fill(l, focus) ++ "\"; style.font-color: \"#FFFFFF\"}\n"
61
62def element(+k: String, +rs: List<&2, R.Rose<A.Element>>) -> A.Element:
63 Maybe.default(&2, A.Element, RO.find(A.Element, k, rs), A.Element{k, "", "", A.ExternalLevel{}})
64
65# D2 names are the keys, prefixed so none is a D2 keyword (like near).
66def boxes(ks: List<&2, String>, +prefix: String, +focus: Bool, +rs: List<&2, R.Rose<A.Element>>) -> String:
67 match ks:
68 case []:
69 ""
70 case +k <> rest:
71 box(prefix ++ "e_" ++ k, element(k, rs), focus) ++ boxes(rest, prefix, focus, rs)
72
73# ---- which keys a view shows ----
74def keep_unless(c: Bool, x: String, rest: List<&2, String>) -> List<&2, String>:
75 match c:
76 case True{}:
77 rest
78 case False{}:
79 x <> rest
80
81def without(+k: String, xs: List<&2, String>) -> List<&2, String>:
82 match xs:
83 case []:
84 []
85 case +x <> rest:
86 keep_unless(String.eq(k, x), x, without(k, rest))
87
88# The keys the view is about, drawn inside its boundary.
89def inner(+a: A.Architecture, v: Vw.View) -> List<&2, String>:
90 match v:
91 case Vw.Context{}:
92 [O.system_key(a)]
93 case Vw.Containers{}:
94 O.container_keys(O.containers(a))
95 case Vw.Components{c}:
96 O.components_of(c, O.containers(a))
97 case Vw.Deployment{}:
98 []
99 case Vw.Dynamic{_}:
100 []
101
102# The keys the view may show: its own, the rest of the system at the level above, and the environment.
103def visible(+a: A.Architecture, v: Vw.View) -> List<&2, String>:
104 match v:
105 case Vw.Context{}:
106 O.system_key(a) <> O.environment_keys(a)
107 case Vw.Containers{}:
108 List.append(&2, String, O.container_keys(O.containers(a)), O.environment_keys(a))
109 case Vw.Components{+c}:
110 List.append(&2, String, O.components_of(c, O.containers(a)), List.append(&2, String, without(c, O.container_keys(O.containers(a))), O.environment_keys(a)))
111 case Vw.Deployment{}:
112 []
113 case Vw.Dynamic{_}:
114 []
115
116def boundary(+a: A.Architecture, v: Vw.View) -> String:
117 match v:
118 case Vw.Context{}:
119 ""
120 case Vw.Containers{}:
121 O.system_key(a)
122 case Vw.Components{c}:
123 c
124 case Vw.Deployment{}:
125 ""
126 case Vw.Dynamic{_}:
127 O.system_key(a)
128
129def ename(e: A.Element) -> String:
130 match e:
131 case A.Element{n, _, _, _}:
132 n
133
134def title(+a: A.Architecture, +rs: List<&2, R.Rose<A.Element>>, v: Vw.View) -> String:
135 match v:
136 case Vw.Context{}:
137 "System context: " ++ ename(element(O.system_key(a), rs))
138 case Vw.Containers{}:
139 "Containers: " ++ ename(element(O.system_key(a), rs))
140 case Vw.Components{c}:
141 "Components: " ++ ename(element(c, rs))
142 case Vw.Deployment{}:
143 "Deployment: " ++ ename(element(O.system_key(a), rs))
144 case Vw.Dynamic{_}:
145 "Dynamic: " ++ ename(element(O.system_key(a), rs))
146
147# ---- drawing ----
148def header(+t: String) -> String:
149 "direction: down\ntitle: \"" ++ t ++ "\" {near: top-center; shape: text; style.font-size: 28; style.bold: true}\n"
150
151def roots(c: C.Compound<A.Element>) -> List<&2, R.Rose<A.Element>>:
152 match c:
153 case C.Compound{rs, _}:
154 rs
155
156def keep_edge(c: Bool, e: E.Edge, rest: List<&2, E.Edge>) -> List<&2, E.Edge>:
157 match c:
158 case True{}:
159 e <> rest
160 case False{}:
161 rest
162
163# The edges with an end among the view's own keys.
164def touching(es: List<&2, E.Edge>, +ins: List<&2, String>) -> List<&2, E.Edge>:
165 match es:
166 case []:
167 []
168 case e <> rest:
169 match e:
170 case E.Edge{+x, +y, l, t}:
171 keep_edge(CO.has(ins, x) || CO.has(ins, y), E.Edge{x, y, l, t}, touching(rest, ins))
172
173def keep_new(c: Bool, +k: String, rest: List<&2, String>) -> List<&2, String>:
174 match c:
175 case True{}:
176 k <> rest
177 case False{}:
178 rest
179
180# The keys around the view's own: whatever its edges reach, once each.
181def around(es: List<&2, E.Edge>, +ins: List<&2, String>, +seen: List<&2, String>) -> List<&2, String>:
182 match es:
183 case []:
184 []
185 case e <> rest:
186 match e:
187 case E.Edge{+x, +y, _, _}:
188 keep_new(Bool.not(CO.has(ins, x) || CO.has(seen, x)), x,
189 keep_new(Bool.not(CO.has(ins, y) || CO.has(x <> seen, y) || String.eq(x, y)), y, around(rest, ins, x <> y <> seen)))
190
191def path(+k: String, +ins: List<&2, String>, +b: String) -> String:
192 Bool.pick(String, CO.has(ins, k) && Bool.not(String.is_empty(b)), "e_" ++ b ++ ".e_" ++ k, "e_" ++ k)
193
194def label(+d: String, +t: String) -> String:
195 "\"" ++ d ++ Bool.pick(String, String.is_empty(t), "", "\\n[" ++ t ++ "]") ++ "\""
196
197def edge_lines(es: List<&2, E.Edge>, +ins: List<&2, String>, +b: String) -> String:
198 match es:
199 case []:
200 ""
201 case e <> rest:
202 match e:
203 case E.Edge{x, y, d, t}:
204 path(x, ins, b) ++ " -> " ++ path(y, ins, b) ++ ": " ++ label(d, t) ++ "\n" ++ edge_lines(rest, ins, b)
205
206def inner_boxes(+ins: List<&2, String>, +b: String, +rs: List<&2, R.Rose<A.Element>>) -> String:
207 Bool.pick(String, String.is_empty(b), boxes(ins, "", True{}, rs),
208 "e_" ++ b ++ ": \"" ++ ename(element(b, rs)) ++ "\" {style.fill: \"transparent\"; style.stroke: \"#444444\"; style.stroke-dash: 4}\n" ++ boxes(ins, "e_" ++ b ++ ".", True{}, rs))
209
210def draw(+t: String, +ins: List<&2, String>, +b: String, +rs: List<&2, R.Rose<A.Element>>, +es: List<&2, E.Edge>) -> String:
211 header(t) ++ inner_boxes(ins, b, rs) ++ boxes(around(es, ins, []), "", False{}, rs) ++ edge_lines(es, ins, b)
212
213# Context, containers and components: the view's keys, and the architecture's edges lifted to what shows.
214def static_view(+a: A.Architecture, +v: Vw.View) -> String:
215 +c = O.to_compound(a)
216 +ins = inner(a, v)
217 draw(title(a, roots(c), v), ins, boundary(a, v), roots(c), touching(CO.lift(A.Element, c, visible(a, v)), ins))
218
219# ---- dynamic: numbered steps between elements as they are ----
220def step_edges(ss: List<&2, Vw.Step>, +n: Nat) -> List<&2, E.Edge>:
221 match ss:
222 case []:
223 []
224 case s <> rest:
225 match s:
226 case Vw.Step{x, y, d, t}:
227 E.Edge{x, y, Nat.show(n) ++ ". " ++ d, t} <> step_edges(rest, Nat.add(n, 1n))
228
229# The steps' ends inside the system, once each.
230def inside(es: List<&2, E.Edge>, +sys: List<&2, String>, +seen: List<&2, String>) -> List<&2, String>:
231 match es:
232 case []:
233 []
234 case e <> rest:
235 match e:
236 case E.Edge{+x, +y, _, _}:
237 keep_new(CO.has(sys, x) && Bool.not(CO.has(seen, x)), x,
238 keep_new(CO.has(sys, y) && Bool.not(CO.has(x <> seen, y)), y, inside(rest, sys, x <> y <> seen)))
239
240def dynamic_view(+a: A.Architecture, ss: List<&2, Vw.Step>) -> String:
241 +c = O.to_compound(a)
242 +es = step_edges(ss, 1n)
243 +sys = List.append(&2, String, O.container_keys(O.containers(a)), O.all_component_keys(O.containers(a)))
244 draw(title(a, roots(c), Vw.Dynamic{[]}), inside(es, sys, []), O.system_key(a), roots(c), es)
245
246# ---- deployment: the machines, nested, with the containers running on them ----
247def node_kind(+t: String) -> String:
248 Bool.pick(String, String.is_empty(t), "[Deployment Node]", "[Deployment Node: " ++ t ++ "]")
249
250def instance_boxes(ks: List<&2, String>, +p: String, +rs: List<&2, R.Rose<A.Element>>) -> String:
251 match ks:
252 case []:
253 ""
254 case +k <> rest:
255 box(p ++ "e_" ++ k, element(k, rs), True{}) ++ instance_boxes(rest, p, rs)
256
257def node_lines(ns: List<&2, D.Node>, +prefix: String, +rs: List<&2, R.Rose<A.Element>>) -> String:
258 match ns:
259 case []:
260 ""
261 case n <> rest:
262 match n:
263 case D.Node{+k, name, +t, _, _, _, kids, ins}:
264 prefix ++ "d_" ++ k ++ ": \"" ++ name ++ "\\n" ++ node_kind(t) ++ "\" {style.fill: \"#FFFFFF\"; style.stroke: \"#888888\"; style.font-color: \"#000000\"}\n" ++
265 instance_boxes(ins, prefix ++ "d_" ++ k ++ ".", rs) ++ node_lines(kids, prefix ++ "d_" ++ k ++ ".", rs) ++ node_lines(rest, prefix, rs)
266
267# Where each instance is drawn: its container's key and its D2 path.
268type Placed is Data:
269 Placed{key: String, path: String}
270
271def instance_places(ks: List<&2, String>, +p: String) -> List<&2, Placed>:
272 match ks:
273 case []:
274 []
275 case +k <> rest:
276 Placed{k, p ++ "e_" ++ k} <> instance_places(rest, p)
277
278def places(ns: List<&2, D.Node>, +prefix: String) -> List<&2, Placed>:
279 match ns:
280 case []:
281 []
282 case n <> rest:
283 match n:
284 case D.Node{+k, _, _, _, _, _, kids, ins}:
285 List.append(&2, Placed, instance_places(ins, prefix ++ "d_" ++ k ++ "."),
286 List.append(&2, Placed, places(kids, prefix ++ "d_" ++ k ++ "."), places(rest, prefix)))
287
288def to_each(ps: List<&2, Placed>, +from: String, +y: String, +d: String, +t: String, +rest: List<&2, E.Edge>) -> List<&2, E.Edge>:
289 match ps:
290 case []:
291 rest
292 case p <> more:
293 match p:
294 case Placed{pk, +pp}:
295 keep_edge(String.eq(pk, y) && Bool.not(String.eq(from, pp)), E.Edge{from, pp, d, t}, to_each(more, from, y, d, t, rest))
296
297def from_here(c: Bool, +all: List<&2, Placed>, +pp: String, +y: String, +d: String, +t: String, +tail: List<&2, E.Edge>) -> List<&2, E.Edge>:
298 match c:
299 case True{}:
300 to_each(all, pp, y, d, t, tail)
301 case False{}:
302 tail
303
304def from_each(ps: List<&2, Placed>, +all: List<&2, Placed>, +x: String, +y: String, +d: String, +t: String, +rest: List<&2, E.Edge>) -> List<&2, E.Edge>:
305 match ps:
306 case []:
307 rest
308 case p <> more:
309 match p:
310 case Placed{pk, +pp}:
311 from_here(String.eq(pk, x), all, pp, y, d, t, from_each(more, all, x, y, d, t, rest))
312
313# Each container edge, drawn between every pair of its ends' instances.
314def instance_edges(es: List<&2, E.Edge>, +ps: List<&2, Placed>) -> List<&2, E.Edge>:
315 match es:
316 case []:
317 []
318 case e <> rest:
319 match e:
320 case E.Edge{x, y, d, t}:
321 from_each(ps, ps, x, y, d, t, instance_edges(rest, ps))
322
323def path_lines(es: List<&2, E.Edge>) -> String:
324 match es:
325 case []:
326 ""
327 case e <> rest:
328 match e:
329 case E.Edge{x, y, d, t}:
330 x ++ " -> " ++ y ++ ": " ++ label(d, t) ++ "\n" ++ path_lines(rest)
331
332def deployment_view(+a: A.Architecture) -> String:
333 +c = O.to_compound(a)
334 +ns = O.nodes(a)
335 header(title(a, roots(c), Vw.Deployment{})) ++ node_lines(ns, "", roots(c)) ++
336 path_lines(instance_edges(CO.lift(A.Element, c, O.container_keys(O.containers(a))), places(ns, "")))
337
338# ---- a view as D2 ----
339def d2(+a: A.Architecture, v: Vw.View) -> String:
340 match v:
341 case Vw.Context{}:
342 static_view(a, Vw.Context{})
343 case Vw.Containers{}:
344 static_view(a, Vw.Containers{})
345 case Vw.Components{c}:
346 static_view(a, Vw.Components{c})
347 case Vw.Deployment{}:
348 deployment_view(a)
349 case Vw.Dynamic{ss}:
350 dynamic_view(a, ss)