domain/architecture/view/ops.bend

near-v-framework@0.5.1.0 · 350 lines · 0 definitions

1import Base
2import near-v-framework@0.5.1.0/general/rose/type.bend as R
3import near-v-framework@0.5.1.0/general/rose/ops.bend as RO
4import near-v-framework@0.5.1.0/general/compound/type.bend as C
5import near-v-framework@0.5.1.0/general/compound/ops.bend as CO
6import near-v-framework@0.5.1.0/general/compound/edge/type.bend as E
7import near-v-framework@0.5.1.0/domain/architecture/type.bend as A
8import near-v-framework@0.5.1.0/domain/architecture/ops.bend as O
9import near-v-framework@0.5.1.0/domain/architecture/system/deployment/type.bend as D
10import near-v-framework@0.5.1.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)