buildzr 0.0.7__py3-none-any.whl → 0.0.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- buildzr/__about__.py +1 -1
- buildzr/dsl/__init__.py +4 -1
- buildzr/dsl/color.py +121 -0
- buildzr/dsl/dsl.py +626 -179
- buildzr/dsl/explorer.py +4 -25
- buildzr/dsl/expression.py +60 -22
- buildzr/dsl/interfaces/__init__.py +0 -4
- buildzr/dsl/interfaces/interfaces.py +23 -37
- buildzr/dsl/relations.py +11 -76
- buildzr/encoders/encoder.py +24 -4
- {buildzr-0.0.7.dist-info → buildzr-0.0.9.dist-info}/METADATA +51 -66
- buildzr-0.0.9.dist-info/RECORD +24 -0
- {buildzr-0.0.7.dist-info → buildzr-0.0.9.dist-info}/WHEEL +1 -1
- buildzr-0.0.7.dist-info/RECORD +0 -23
- {buildzr-0.0.7.dist-info → buildzr-0.0.9.dist-info}/licenses/LICENSE.md +0 -0
buildzr/dsl/dsl.py
CHANGED
@@ -3,9 +3,10 @@ import buildzr
|
|
3
3
|
from .factory import GenerateId
|
4
4
|
from typing_extensions import (
|
5
5
|
Self,
|
6
|
-
TypeGuard,
|
7
6
|
TypeIs,
|
8
7
|
)
|
8
|
+
from collections import deque
|
9
|
+
from contextvars import ContextVar
|
9
10
|
from typing import (
|
10
11
|
Any,
|
11
12
|
Union,
|
@@ -16,35 +17,26 @@ from typing import (
|
|
16
17
|
Optional,
|
17
18
|
Generic,
|
18
19
|
TypeVar,
|
19
|
-
Protocol,
|
20
20
|
Callable,
|
21
21
|
Iterable,
|
22
22
|
Literal,
|
23
23
|
cast,
|
24
|
-
overload,
|
25
|
-
Sequence,
|
26
24
|
Type,
|
27
25
|
)
|
28
26
|
|
29
27
|
from buildzr.sinks.interfaces import Sink
|
30
|
-
|
31
28
|
from buildzr.dsl.interfaces import (
|
32
29
|
DslWorkspaceElement,
|
33
30
|
DslElement,
|
34
31
|
DslViewElement,
|
35
32
|
DslViewsElement,
|
36
|
-
DslFluentSink,
|
37
|
-
TSrc, TDst,
|
38
|
-
TParent, TChild,
|
39
33
|
)
|
40
34
|
from buildzr.dsl.relations import (
|
41
|
-
_is_software_fluent_relationship,
|
42
|
-
_is_container_fluent_relationship,
|
43
|
-
_Relationship,
|
44
|
-
_RelationshipDescription,
|
45
|
-
_FluentRelationship,
|
46
35
|
DslElementRelationOverrides,
|
36
|
+
DslRelationship,
|
37
|
+
_Relationship,
|
47
38
|
)
|
39
|
+
from buildzr.dsl.color import Color
|
48
40
|
|
49
41
|
def _child_name_transform(name: str) -> str:
|
50
42
|
return name.lower().replace(' ', '_')
|
@@ -58,6 +50,11 @@ class TypedDynamicAttribute(Generic[TypedModel]):
|
|
58
50
|
def __getattr__(self, name: str) -> TypedModel:
|
59
51
|
return cast(TypedModel, self._dynamic_attributes.get(name))
|
60
52
|
|
53
|
+
_current_workspace: ContextVar[Optional['Workspace']] = ContextVar('current_workspace', default=None)
|
54
|
+
_current_group_stack: ContextVar[List['Group']] = ContextVar('current_group', default=[])
|
55
|
+
_current_software_system: ContextVar[Optional['SoftwareSystem']] = ContextVar('current_software_system', default=None)
|
56
|
+
_current_container: ContextVar[Optional['Container']] = ContextVar('current_container', default=None)
|
57
|
+
|
61
58
|
class Workspace(DslWorkspaceElement):
|
62
59
|
"""
|
63
60
|
Represents a Structurizr workspace, which is a wrapper for a software architecture model, views, and documentation.
|
@@ -75,11 +72,21 @@ class Workspace(DslWorkspaceElement):
|
|
75
72
|
def children(self) -> Optional[List[Union['Person', 'SoftwareSystem']]]:
|
76
73
|
return self._children
|
77
74
|
|
78
|
-
def __init__(
|
75
|
+
def __init__(
|
76
|
+
self,
|
77
|
+
name: str,
|
78
|
+
description: str="",
|
79
|
+
scope: Literal['landscape', 'software_system', None]='software_system',
|
80
|
+
implied_relationships: bool=False,
|
81
|
+
group_separator: str='/',
|
82
|
+
) -> None:
|
83
|
+
|
79
84
|
self._m = buildzr.models.Workspace()
|
80
85
|
self._parent = None
|
81
86
|
self._children: Optional[List[Union['Person', 'SoftwareSystem']]] = []
|
82
87
|
self._dynamic_attrs: Dict[str, Union['Person', 'SoftwareSystem']] = {}
|
88
|
+
self._use_implied_relationships = implied_relationships
|
89
|
+
self._group_separator = group_separator
|
83
90
|
self.model.id = GenerateId.for_workspace()
|
84
91
|
self.model.name = name
|
85
92
|
self.model.description = description
|
@@ -102,58 +109,57 @@ class Workspace(DslWorkspaceElement):
|
|
102
109
|
scope=scope_mapper[scope],
|
103
110
|
)
|
104
111
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
*models: Union[
|
109
|
-
'Person',
|
110
|
-
'SoftwareSystem',
|
111
|
-
_FluentRelationship['SoftwareSystem'],
|
112
|
-
_FluentRelationship['Container'],
|
113
|
-
]
|
114
|
-
) -> None:
|
112
|
+
self.model.model.properties = {
|
113
|
+
'structurizr.groupSeparator': group_separator,
|
114
|
+
}
|
115
115
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
116
|
+
def __enter__(self) -> Self:
|
117
|
+
self._token = _current_workspace.set(self)
|
118
|
+
return self
|
119
|
+
|
120
|
+
def __exit__(self, exc_type: Optional[Type[BaseException]], exc_value: Optional[BaseException], traceback: Optional[Any]) -> None:
|
121
|
+
|
122
|
+
from buildzr.dsl.explorer import Explorer
|
123
|
+
|
124
|
+
# Process implied relationships:
|
125
|
+
# If we have relationship s >> do >> a.b, then create s >> do >> a.
|
126
|
+
# If we have relationship s.ss >> do >> a.b.c, then create s.ss >> do >> a.b and s.ss >> do >> a.
|
127
|
+
# And so on...
|
128
|
+
if self._use_implied_relationships:
|
129
|
+
explorer = Explorer(self)
|
130
|
+
relationships = list(explorer.walk_relationships())
|
131
|
+
for relationship in relationships:
|
132
|
+
source = relationship.source
|
133
|
+
destination = relationship.destination
|
134
|
+
destination_parent = destination.parent
|
135
|
+
|
136
|
+
while destination_parent is not None and \
|
137
|
+
isinstance(source, DslElement) and \
|
138
|
+
not isinstance(source.model, buildzr.models.Workspace) and \
|
139
|
+
not isinstance(destination_parent, DslWorkspaceElement):
|
140
|
+
|
141
|
+
if destination_parent is source.parent:
|
142
|
+
break
|
143
|
+
|
144
|
+
rels = source.model.relationships
|
145
|
+
|
146
|
+
if rels:
|
147
|
+
already_exists = any(
|
148
|
+
r.destinationId == destination_parent.model.id and
|
149
|
+
r.description == relationship.model.description and
|
150
|
+
r.technology == relationship.model.technology
|
151
|
+
for r in rels
|
152
|
+
)
|
153
|
+
if not already_exists:
|
154
|
+
r = source.uses(
|
155
|
+
destination_parent,
|
156
|
+
description=relationship.model.description,
|
157
|
+
technology=relationship.model.technology,
|
158
|
+
)
|
159
|
+
r.model.linkedRelationshipId = relationship.model.id
|
160
|
+
destination_parent = destination_parent.parent
|
161
|
+
|
162
|
+
_current_workspace.reset(self._token)
|
157
163
|
|
158
164
|
def person(self) -> TypedDynamicAttribute['Person']:
|
159
165
|
return TypedDynamicAttribute['Person'](self._dynamic_attrs)
|
@@ -161,34 +167,68 @@ class Workspace(DslWorkspaceElement):
|
|
161
167
|
def software_system(self) -> TypedDynamicAttribute['SoftwareSystem']:
|
162
168
|
return TypedDynamicAttribute['SoftwareSystem'](self._dynamic_attrs)
|
163
169
|
|
164
|
-
def
|
165
|
-
if isinstance(
|
166
|
-
self._m.model.people.append(
|
167
|
-
|
168
|
-
self.
|
169
|
-
|
170
|
-
|
171
|
-
self.
|
172
|
-
|
173
|
-
self.
|
174
|
-
|
175
|
-
self._dynamic_attrs[_child_name_transform(element.model.name)] = element
|
176
|
-
if element._label:
|
177
|
-
self._dynamic_attrs[_child_name_transform(element._label)] = element
|
178
|
-
self._children.append(element)
|
170
|
+
def add_model(self, model: Union['Person', 'SoftwareSystem']) -> None:
|
171
|
+
if isinstance(model, Person):
|
172
|
+
self._m.model.people.append(model._m)
|
173
|
+
model._parent = self
|
174
|
+
self._add_dynamic_attr(model.model.name, model)
|
175
|
+
self._children.append(model)
|
176
|
+
elif isinstance(model, SoftwareSystem):
|
177
|
+
self._m.model.softwareSystems.append(model._m)
|
178
|
+
model._parent = self
|
179
|
+
self._add_dynamic_attr(model.model.name, model)
|
180
|
+
self._children.append(model)
|
179
181
|
else:
|
180
|
-
raise ValueError('Invalid element type: Trying to add an element of type {} to a workspace.'.format(type(
|
182
|
+
raise ValueError('Invalid element type: Trying to add an element of type {} to a workspace.'.format(type(model)))
|
181
183
|
|
182
|
-
def
|
183
|
-
self,
|
184
|
-
*views: Union[
|
185
|
-
'SystemLandscapeView',
|
184
|
+
def apply_views( self, *views: Union[ 'SystemLandscapeView',
|
186
185
|
'SystemContextView',
|
187
186
|
'ContainerView',
|
188
187
|
'ComponentView',
|
189
188
|
]
|
190
|
-
) ->
|
191
|
-
|
189
|
+
) -> None:
|
190
|
+
Views(self).add_views(*views)
|
191
|
+
|
192
|
+
def apply_style( self,
|
193
|
+
style: Union['StyleElements', 'StyleRelationships'],
|
194
|
+
) -> None:
|
195
|
+
|
196
|
+
style._parent = self
|
197
|
+
|
198
|
+
if not self.model.views:
|
199
|
+
self.model.views = buildzr.models.Views()
|
200
|
+
if not self.model.views.configuration:
|
201
|
+
self.model.views.configuration = buildzr.models.Configuration()
|
202
|
+
if not self.model.views.configuration.styles:
|
203
|
+
self.model.views.configuration.styles = buildzr.models.Styles()
|
204
|
+
|
205
|
+
if isinstance(style, StyleElements):
|
206
|
+
if self.model.views.configuration.styles.elements:
|
207
|
+
self.model.views.configuration.styles.elements.extend(style.model)
|
208
|
+
else:
|
209
|
+
self.model.views.configuration.styles.elements = style.model
|
210
|
+
elif isinstance(style, StyleRelationships):
|
211
|
+
if self.model.views.configuration.styles.relationships:
|
212
|
+
self.model.views.configuration.styles.relationships.extend(style.model)
|
213
|
+
else:
|
214
|
+
self.model.views.configuration.styles.relationships = style.model
|
215
|
+
|
216
|
+
def to_json(self, path: str) -> None:
|
217
|
+
from buildzr.sinks.json_sink import JsonSink, JsonSinkConfig
|
218
|
+
sink = JsonSink()
|
219
|
+
sink.write(workspace=self.model, config=JsonSinkConfig(path=path))
|
220
|
+
|
221
|
+
def _add_dynamic_attr(self, name: str, model: Union['Person', 'SoftwareSystem']) -> None:
|
222
|
+
if isinstance(model, Person):
|
223
|
+
self._dynamic_attrs[_child_name_transform(name)] = model
|
224
|
+
if model._label:
|
225
|
+
self._dynamic_attrs[_child_name_transform(model._label)] = model
|
226
|
+
elif isinstance(model, SoftwareSystem):
|
227
|
+
self._dynamic_attrs[_child_name_transform(name)] = model
|
228
|
+
if model._label:
|
229
|
+
self._dynamic_attrs[_child_name_transform(model._label)] = model
|
230
|
+
else:
|
231
|
+
raise ValueError('Invalid element type: Trying to add an element of type {} to a workspace.'.format(type(model)))
|
192
232
|
|
193
233
|
def __getattr__(self, name: str) -> Union['Person', 'SoftwareSystem']:
|
194
234
|
return self._dynamic_attrs[name]
|
@@ -232,16 +272,22 @@ class SoftwareSystem(DslElementRelationOverrides[
|
|
232
272
|
def destinations(self) -> List[DslElement]:
|
233
273
|
return self._destinations
|
234
274
|
|
275
|
+
@property
|
276
|
+
def relationships(self) -> Set[_Relationship]:
|
277
|
+
return self._relationships
|
278
|
+
|
235
279
|
@property
|
236
280
|
def tags(self) -> Set[str]:
|
237
281
|
return self._tags
|
238
282
|
|
239
283
|
def __init__(self, name: str, description: str="", tags: Set[str]=set(), properties: Dict[str, Any]=dict()) -> None:
|
240
284
|
self._m = buildzr.models.SoftwareSystem()
|
285
|
+
self.model.containers = []
|
241
286
|
self._parent: Optional[Workspace] = None
|
242
287
|
self._children: Optional[List['Container']] = []
|
243
288
|
self._sources: List[DslElement] = []
|
244
289
|
self._destinations: List[DslElement] = []
|
290
|
+
self._relationships: Set[_Relationship] = set()
|
245
291
|
self._tags = {'Element', 'Software System'}.union(tags)
|
246
292
|
self._dynamic_attrs: Dict[str, 'Container'] = {}
|
247
293
|
self._label: Optional[str] = None
|
@@ -251,37 +297,41 @@ class SoftwareSystem(DslElementRelationOverrides[
|
|
251
297
|
self.model.tags = ','.join(self._tags)
|
252
298
|
self.model.properties = properties
|
253
299
|
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
if not self.model.containers:
|
259
|
-
self.model.containers = []
|
260
|
-
|
261
|
-
for child in containers:
|
262
|
-
if isinstance(child, Container):
|
263
|
-
self.add_element(child)
|
264
|
-
elif _is_container_fluent_relationship(child):
|
265
|
-
self.add_element(child._parent)
|
266
|
-
return _FluentRelationship['SoftwareSystem'](self)
|
300
|
+
workspace = _current_workspace.get()
|
301
|
+
if workspace is not None:
|
302
|
+
workspace.add_model(self)
|
303
|
+
workspace._add_dynamic_attr(self.model.name, self)
|
267
304
|
|
268
|
-
|
269
|
-
|
305
|
+
stack = _current_group_stack.get()
|
306
|
+
if stack:
|
307
|
+
stack[-1].add_element(self)
|
308
|
+
|
309
|
+
def __enter__(self) -> Self:
|
310
|
+
self._token = _current_software_system.set(self)
|
270
311
|
return self
|
271
312
|
|
313
|
+
def __exit__(self, exc_type: Optional[Type[BaseException]], exc_value: Optional[BaseException], traceback: Optional[Any]) -> None:
|
314
|
+
_current_software_system.reset(self._token)
|
315
|
+
|
272
316
|
def container(self) -> TypedDynamicAttribute['Container']:
|
273
317
|
return TypedDynamicAttribute['Container'](self._dynamic_attrs)
|
274
318
|
|
275
|
-
def
|
276
|
-
if isinstance(
|
277
|
-
self.model.containers.append(
|
278
|
-
|
279
|
-
self.
|
280
|
-
|
281
|
-
self._dynamic_attrs[_child_name_transform(element._label)] = element
|
282
|
-
self._children.append(element)
|
319
|
+
def add_container(self, container: 'Container') -> None:
|
320
|
+
if isinstance(container, Container):
|
321
|
+
self.model.containers.append(container.model)
|
322
|
+
container._parent = self
|
323
|
+
self._add_dynamic_attr(container.model.name, container)
|
324
|
+
self._children.append(container)
|
283
325
|
else:
|
284
|
-
raise ValueError('Invalid element type: Trying to add an element of type {} to a software system.'.format(type(
|
326
|
+
raise ValueError('Invalid element type: Trying to add an element of type {} to a software system.'.format(type(container)))
|
327
|
+
|
328
|
+
def _add_dynamic_attr(self, name: str, model: 'Container') -> None:
|
329
|
+
if isinstance(model, Container):
|
330
|
+
self._dynamic_attrs[_child_name_transform(name)] = model
|
331
|
+
if model._label:
|
332
|
+
self._dynamic_attrs[_child_name_transform(model._label)] = model
|
333
|
+
else:
|
334
|
+
raise ValueError('Invalid element type: Trying to add an element of type {} to a software system.'.format(type(model)))
|
285
335
|
|
286
336
|
def __getattr__(self, name: str) -> 'Container':
|
287
337
|
return self._dynamic_attrs[name]
|
@@ -292,6 +342,13 @@ class SoftwareSystem(DslElementRelationOverrides[
|
|
292
342
|
def __dir__(self) -> Iterable[str]:
|
293
343
|
return list(super().__dir__()) + list(self._dynamic_attrs.keys())
|
294
344
|
|
345
|
+
def labeled(self, label: str) -> 'SoftwareSystem':
|
346
|
+
self._label = label
|
347
|
+
workspace = _current_workspace.get()
|
348
|
+
if workspace is not None:
|
349
|
+
workspace._add_dynamic_attr(label, self)
|
350
|
+
return self
|
351
|
+
|
295
352
|
class Person(DslElementRelationOverrides[
|
296
353
|
'Person',
|
297
354
|
Union[
|
@@ -329,6 +386,10 @@ class Person(DslElementRelationOverrides[
|
|
329
386
|
def destinations(self) -> List[DslElement]:
|
330
387
|
return self._destinations
|
331
388
|
|
389
|
+
@property
|
390
|
+
def relationships(self) -> Set[_Relationship]:
|
391
|
+
return self._relationships
|
392
|
+
|
332
393
|
@property
|
333
394
|
def tags(self) -> Set[str]:
|
334
395
|
return self._tags
|
@@ -338,6 +399,7 @@ class Person(DslElementRelationOverrides[
|
|
338
399
|
self._parent: Optional[Workspace] = None
|
339
400
|
self._sources: List[DslElement] = []
|
340
401
|
self._destinations: List[DslElement] = []
|
402
|
+
self._relationships: Set[_Relationship] = set()
|
341
403
|
self._tags = {'Element', 'Person'}.union(tags)
|
342
404
|
self._label: Optional[str] = None
|
343
405
|
self.model.id = GenerateId.for_element()
|
@@ -347,8 +409,19 @@ class Person(DslElementRelationOverrides[
|
|
347
409
|
self.model.tags = ','.join(self._tags)
|
348
410
|
self.model.properties = properties
|
349
411
|
|
412
|
+
workspace = _current_workspace.get()
|
413
|
+
if workspace is not None:
|
414
|
+
workspace.add_model(self)
|
415
|
+
|
416
|
+
stack = _current_group_stack.get()
|
417
|
+
if stack:
|
418
|
+
stack[-1].add_element(self)
|
419
|
+
|
350
420
|
def labeled(self, label: str) -> 'Person':
|
351
421
|
self._label = label
|
422
|
+
workspace = _current_workspace.get()
|
423
|
+
if workspace is not None:
|
424
|
+
workspace._add_dynamic_attr(label, self)
|
352
425
|
return self
|
353
426
|
|
354
427
|
class Container(DslElementRelationOverrides[
|
@@ -384,23 +457,22 @@ class Container(DslElementRelationOverrides[
|
|
384
457
|
def destinations(self) -> List[DslElement]:
|
385
458
|
return self._destinations
|
386
459
|
|
460
|
+
@property
|
461
|
+
def relationships(self) -> Set[_Relationship]:
|
462
|
+
return self._relationships
|
463
|
+
|
387
464
|
@property
|
388
465
|
def tags(self) -> Set[str]:
|
389
466
|
return self._tags
|
390
467
|
|
391
|
-
def contains(self, *components: 'Component') -> _FluentRelationship['Container']:
|
392
|
-
if not self.model.components:
|
393
|
-
self.model.components = []
|
394
|
-
for component in components:
|
395
|
-
self.add_element(component)
|
396
|
-
return _FluentRelationship['Container'](self)
|
397
|
-
|
398
468
|
def __init__(self, name: str, description: str="", technology: str="", tags: Set[str]=set(), properties: Dict[str, Any]=dict()) -> None:
|
399
469
|
self._m = buildzr.models.Container()
|
470
|
+
self.model.components = []
|
400
471
|
self._parent: Optional[SoftwareSystem] = None
|
401
472
|
self._children: Optional[List['Component']] = []
|
402
473
|
self._sources: List[DslElement] = []
|
403
474
|
self._destinations: List[DslElement] = []
|
475
|
+
self._relationships: Set[_Relationship] = set()
|
404
476
|
self._tags = {'Element', 'Container'}.union(tags)
|
405
477
|
self._dynamic_attrs: Dict[str, 'Component'] = {}
|
406
478
|
self._label: Optional[str] = None
|
@@ -412,23 +484,48 @@ class Container(DslElementRelationOverrides[
|
|
412
484
|
self.model.tags = ','.join(self._tags)
|
413
485
|
self.model.properties = properties
|
414
486
|
|
487
|
+
software_system = _current_software_system.get()
|
488
|
+
if software_system is not None:
|
489
|
+
software_system.add_container(self)
|
490
|
+
software_system._add_dynamic_attr(self.model.name, self)
|
491
|
+
|
492
|
+
stack = _current_group_stack.get()
|
493
|
+
if stack:
|
494
|
+
stack[-1].add_element(self)
|
495
|
+
|
496
|
+
def __enter__(self) -> Self:
|
497
|
+
self._token = _current_container.set(self)
|
498
|
+
return self
|
499
|
+
|
500
|
+
def __exit__(self, exc_type: Optional[Type[BaseException]], exc_value: Optional[BaseException], traceback: Optional[Any]) -> None:
|
501
|
+
_current_container.reset(self._token)
|
502
|
+
|
415
503
|
def labeled(self, label: str) -> 'Container':
|
416
504
|
self._label = label
|
505
|
+
software_system = _current_software_system.get()
|
506
|
+
if software_system is not None:
|
507
|
+
software_system._add_dynamic_attr(label, self)
|
417
508
|
return self
|
418
509
|
|
419
510
|
def component(self) -> TypedDynamicAttribute['Component']:
|
420
511
|
return TypedDynamicAttribute['Component'](self._dynamic_attrs)
|
421
512
|
|
422
|
-
def
|
423
|
-
if isinstance(
|
424
|
-
self.model.components.append(
|
425
|
-
|
426
|
-
self.
|
427
|
-
|
428
|
-
self._dynamic_attrs[_child_name_transform(element._label)] = element
|
429
|
-
self._children.append(element)
|
513
|
+
def add_component(self, component: 'Component') -> None:
|
514
|
+
if isinstance(component, Component):
|
515
|
+
self.model.components.append(component.model)
|
516
|
+
component._parent = self
|
517
|
+
self._add_dynamic_attr(component.model.name, component)
|
518
|
+
self._children.append(component)
|
430
519
|
else:
|
431
|
-
raise ValueError('Invalid element type: Trying to add an element of type {} to a container.'.format(type(
|
520
|
+
raise ValueError('Invalid element type: Trying to add an element of type {} to a container.'.format(type(component)))
|
521
|
+
|
522
|
+
def _add_dynamic_attr(self, name: str, model: 'Component') -> None:
|
523
|
+
if isinstance(model, Component):
|
524
|
+
self._dynamic_attrs[_child_name_transform(name)] = model
|
525
|
+
if model._label:
|
526
|
+
self._dynamic_attrs[_child_name_transform(model._label)] = model
|
527
|
+
else:
|
528
|
+
raise ValueError('Invalid element type: Trying to add an element of type {} to a container.'.format(type(model)))
|
432
529
|
|
433
530
|
def __getattr__(self, name: str) -> 'Component':
|
434
531
|
return self._dynamic_attrs[name]
|
@@ -472,6 +569,10 @@ class Component(DslElementRelationOverrides[
|
|
472
569
|
def destinations(self) -> List[DslElement]:
|
473
570
|
return self._destinations
|
474
571
|
|
572
|
+
@property
|
573
|
+
def relationships(self) -> Set[_Relationship]:
|
574
|
+
return self._relationships
|
575
|
+
|
475
576
|
@property
|
476
577
|
def tags(self) -> Set[str]:
|
477
578
|
return self._tags
|
@@ -481,6 +582,7 @@ class Component(DslElementRelationOverrides[
|
|
481
582
|
self._parent: Optional[Container] = None
|
482
583
|
self._sources: List[DslElement] = []
|
483
584
|
self._destinations: List[DslElement] = []
|
585
|
+
self._relationships: Set[_Relationship] = set()
|
484
586
|
self._tags = {'Element', 'Component'}.union(tags)
|
485
587
|
self._label: Optional[str] = None
|
486
588
|
self.model.id = GenerateId.for_element()
|
@@ -491,8 +593,20 @@ class Component(DslElementRelationOverrides[
|
|
491
593
|
self.model.tags = ','.join(self._tags)
|
492
594
|
self.model.properties = properties
|
493
595
|
|
596
|
+
container = _current_container.get()
|
597
|
+
if container is not None:
|
598
|
+
container.add_component(self)
|
599
|
+
container._add_dynamic_attr(self.model.name, self)
|
600
|
+
|
601
|
+
stack = _current_group_stack.get()
|
602
|
+
if stack:
|
603
|
+
stack[-1].add_element(self)
|
604
|
+
|
494
605
|
def labeled(self, label: str) -> 'Component':
|
495
606
|
self._label = label
|
607
|
+
container = _current_container.get()
|
608
|
+
if container is not None:
|
609
|
+
container._add_dynamic_attr(label, self)
|
496
610
|
return self
|
497
611
|
|
498
612
|
class Group:
|
@@ -500,27 +614,60 @@ class Group:
|
|
500
614
|
def __init__(
|
501
615
|
self,
|
502
616
|
name: str,
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
617
|
+
workspace: Optional[Workspace]=None,
|
618
|
+
) -> None:
|
619
|
+
|
620
|
+
if not workspace:
|
621
|
+
workspace = _current_workspace.get()
|
622
|
+
if workspace is not None:
|
623
|
+
self._group_separator = workspace._group_separator
|
624
|
+
|
625
|
+
self._group_separator = workspace._group_separator
|
509
626
|
self._name = name
|
510
|
-
self._elements = elements
|
511
627
|
|
512
|
-
|
628
|
+
if len(self._group_separator) > 1:
|
629
|
+
raise ValueError('Group separator must be a single character.')
|
513
630
|
|
514
|
-
|
515
|
-
|
631
|
+
if self._group_separator in self._name:
|
632
|
+
raise ValueError('Group name cannot contain the group separator.')
|
516
633
|
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
sink.write(workspace=self._workspace.model, config=JsonSinkConfig(path=path))
|
634
|
+
stack = _current_group_stack.get()
|
635
|
+
new_stack = stack.copy()
|
636
|
+
new_stack.extend([self])
|
521
637
|
|
522
|
-
|
523
|
-
|
638
|
+
self._full_name = self._group_separator.join([group._name for group in new_stack])
|
639
|
+
|
640
|
+
def full_name(self) -> str:
|
641
|
+
return self._full_name
|
642
|
+
|
643
|
+
def add_element(
|
644
|
+
self,
|
645
|
+
model: Union[
|
646
|
+
'Person',
|
647
|
+
'SoftwareSystem',
|
648
|
+
'Container',
|
649
|
+
'Component',
|
650
|
+
]
|
651
|
+
) -> None:
|
652
|
+
|
653
|
+
|
654
|
+
model.model.group = self._full_name
|
655
|
+
|
656
|
+
def __enter__(self) -> Self:
|
657
|
+
stack = _current_group_stack.get() # stack: a/b
|
658
|
+
stack.extend([self]) # stack: a/b -> a/b/self
|
659
|
+
self._token = _current_group_stack.set(stack)
|
660
|
+
return self
|
661
|
+
|
662
|
+
def __exit__(
|
663
|
+
self,
|
664
|
+
exc_type: Optional[Type[BaseException]],
|
665
|
+
exc_value: Optional[BaseException],
|
666
|
+
traceback: Optional[Any]
|
667
|
+
) -> None:
|
668
|
+
stack = _current_group_stack.get()
|
669
|
+
stack.pop() # stack: a/b/self -> a/b
|
670
|
+
_current_group_stack.reset(self._token)
|
524
671
|
|
525
672
|
_RankDirection = Literal['tb', 'bt', 'lr', 'rl']
|
526
673
|
|
@@ -607,10 +754,10 @@ class SystemLandscapeView(DslViewElement):
|
|
607
754
|
description: str,
|
608
755
|
auto_layout: _AutoLayout='tb',
|
609
756
|
title: Optional[str]=None,
|
610
|
-
include_elements: List[Callable[[Workspace, Element], bool]]=[],
|
611
|
-
exclude_elements: List[Callable[[Workspace, Element], bool]]=[],
|
612
|
-
include_relationships: List[Callable[[Workspace, Relationship], bool]]=[],
|
613
|
-
exclude_relationships: List[Callable[[Workspace, Relationship], bool]]=[],
|
757
|
+
include_elements: List[Union[DslElement, Callable[[Workspace, Element], bool]]]=[],
|
758
|
+
exclude_elements: List[Union[DslElement, Callable[[Workspace, Element], bool]]]=[],
|
759
|
+
include_relationships: List[Union[DslElement, Callable[[Workspace, Relationship], bool]]]=[],
|
760
|
+
exclude_relationships: List[Union[DslElement, Callable[[Workspace, Relationship], bool]]]=[],
|
614
761
|
properties: Optional[Dict[str, str]]=None,
|
615
762
|
) -> None:
|
616
763
|
self._m = buildzr.models.SystemLandscapeView()
|
@@ -628,6 +775,10 @@ class SystemLandscapeView(DslViewElement):
|
|
628
775
|
self._include_relationships = include_relationships
|
629
776
|
self._exclude_relationships = exclude_relationships
|
630
777
|
|
778
|
+
workspace = _current_workspace.get()
|
779
|
+
if workspace is not None:
|
780
|
+
workspace.apply_views(self)
|
781
|
+
|
631
782
|
def _on_added(self) -> None:
|
632
783
|
|
633
784
|
from buildzr.dsl.expression import Expression, Element, Relationship
|
@@ -642,17 +793,17 @@ class SystemLandscapeView(DslViewElement):
|
|
642
793
|
|
643
794
|
workspace = self._parent._parent
|
644
795
|
|
645
|
-
include_view_elements_filter: List[Callable[[Workspace, Element], bool]] = [
|
796
|
+
include_view_elements_filter: List[Union[DslElement, Callable[[Workspace, Element], bool]]] = [
|
646
797
|
lambda w, e: e.type == Person,
|
647
798
|
lambda w, e: e.type == SoftwareSystem
|
648
799
|
]
|
649
800
|
|
650
|
-
exclude_view_elements_filter: List[Callable[[Workspace, Element], bool]] = [
|
801
|
+
exclude_view_elements_filter: List[Union[DslElement, Callable[[Workspace, Element], bool]]] = [
|
651
802
|
lambda w, e: e.type == Container,
|
652
803
|
lambda w, e: e.type == Component,
|
653
804
|
]
|
654
805
|
|
655
|
-
include_view_relationships_filter: List[Callable[[Workspace, Relationship], bool]] = [
|
806
|
+
include_view_relationships_filter: List[Union[DslElement, Callable[[Workspace, Relationship], bool]]] = [
|
656
807
|
lambda w, r: r.source.type == Person,
|
657
808
|
lambda w, r: r.source.type == SoftwareSystem,
|
658
809
|
lambda w, r: r.destination.type == Person,
|
@@ -703,15 +854,15 @@ class SystemContextView(DslViewElement):
|
|
703
854
|
|
704
855
|
def __init__(
|
705
856
|
self,
|
706
|
-
software_system_selector: Callable[[Workspace], SoftwareSystem],
|
857
|
+
software_system_selector: Union[SoftwareSystem, Callable[[Workspace], SoftwareSystem]],
|
707
858
|
key: str,
|
708
859
|
description: str,
|
709
860
|
auto_layout: _AutoLayout='tb',
|
710
861
|
title: Optional[str]=None,
|
711
|
-
include_elements: List[Callable[[Workspace, Element], bool]]=[],
|
712
|
-
exclude_elements: List[Callable[[Workspace, Element], bool]]=[],
|
713
|
-
include_relationships: List[Callable[[Workspace, Relationship], bool]]=[],
|
714
|
-
exclude_relationships: List[Callable[[Workspace, Relationship], bool]]=[],
|
862
|
+
include_elements: List[Union[DslElement, Callable[[Workspace, Element], bool]]]=[],
|
863
|
+
exclude_elements: List[Union[DslElement, Callable[[Workspace, Element], bool]]]=[],
|
864
|
+
include_relationships: List[Union[DslElement, Callable[[Workspace, Relationship], bool]]]=[],
|
865
|
+
exclude_relationships: List[Union[DslElement, Callable[[Workspace, Relationship], bool]]]=[],
|
715
866
|
properties: Optional[Dict[str, str]]=None,
|
716
867
|
) -> None:
|
717
868
|
self._m = buildzr.models.SystemContextView()
|
@@ -730,20 +881,27 @@ class SystemContextView(DslViewElement):
|
|
730
881
|
self._include_relationships = include_relationships
|
731
882
|
self._exclude_relationships = exclude_relationships
|
732
883
|
|
884
|
+
workspace = _current_workspace.get()
|
885
|
+
if workspace is not None:
|
886
|
+
workspace.apply_views(self)
|
887
|
+
|
733
888
|
def _on_added(self) -> None:
|
734
889
|
|
735
890
|
from buildzr.dsl.expression import Expression, Element, Relationship
|
736
891
|
from buildzr.models import ElementView, RelationshipView
|
737
892
|
|
738
|
-
|
893
|
+
if isinstance(self._selector, SoftwareSystem):
|
894
|
+
software_system = self._selector
|
895
|
+
else:
|
896
|
+
software_system = self._selector(self._parent._parent)
|
739
897
|
self._m.softwareSystemId = software_system.model.id
|
740
|
-
view_elements_filter: List[Callable[[Workspace, Element], bool]] = [
|
898
|
+
view_elements_filter: List[Union[DslElement, Callable[[Workspace, Element], bool]]] = [
|
741
899
|
lambda w, e: e == software_system,
|
742
900
|
lambda w, e: software_system.model.id in e.sources.ids,
|
743
901
|
lambda w, e: software_system.model.id in e.destinations.ids,
|
744
902
|
]
|
745
903
|
|
746
|
-
view_relationships_filter: List[Callable[[Workspace, Relationship], bool]] = [
|
904
|
+
view_relationships_filter: List[Union[DslElement, Callable[[Workspace, Relationship], bool]]] = [
|
747
905
|
lambda w, r: software_system == r.source,
|
748
906
|
lambda w, r: software_system == r.destination,
|
749
907
|
]
|
@@ -789,15 +947,15 @@ class ContainerView(DslViewElement):
|
|
789
947
|
|
790
948
|
def __init__(
|
791
949
|
self,
|
792
|
-
software_system_selector: Callable[[Workspace], SoftwareSystem],
|
950
|
+
software_system_selector: Union[SoftwareSystem, Callable[[Workspace], SoftwareSystem]],
|
793
951
|
key: str,
|
794
952
|
description: str,
|
795
953
|
auto_layout: _AutoLayout='tb',
|
796
954
|
title: Optional[str]=None,
|
797
|
-
include_elements: List[Callable[[Workspace, Element], bool]]=[],
|
798
|
-
exclude_elements: List[Callable[[Workspace, Element], bool]]=[],
|
799
|
-
include_relationships: List[Callable[[Workspace, Relationship], bool]]=[],
|
800
|
-
exclude_relationships: List[Callable[[Workspace, Relationship], bool]]=[],
|
955
|
+
include_elements: List[Union[DslElement, Callable[[Workspace, Element], bool]]]=[],
|
956
|
+
exclude_elements: List[Union[DslElement, Callable[[Workspace, Element], bool]]]=[],
|
957
|
+
include_relationships: List[Union[DslElement, Callable[[Workspace, Relationship], bool]]]=[],
|
958
|
+
exclude_relationships: List[Union[DslElement, Callable[[Workspace, Relationship], bool]]]=[],
|
801
959
|
properties: Optional[Dict[str, str]]=None,
|
802
960
|
) -> None:
|
803
961
|
self._m = buildzr.models.ContainerView()
|
@@ -816,23 +974,30 @@ class ContainerView(DslViewElement):
|
|
816
974
|
self._include_relationships = include_relationships
|
817
975
|
self._exclude_relationships = exclude_relationships
|
818
976
|
|
977
|
+
workspace = _current_workspace.get()
|
978
|
+
if workspace is not None:
|
979
|
+
workspace.apply_views(self)
|
980
|
+
|
819
981
|
def _on_added(self) -> None:
|
820
982
|
|
821
983
|
from buildzr.dsl.expression import Expression, Element, Relationship
|
822
984
|
from buildzr.models import ElementView, RelationshipView
|
823
985
|
|
824
|
-
|
986
|
+
if isinstance(self._selector, SoftwareSystem):
|
987
|
+
software_system = self._selector
|
988
|
+
else:
|
989
|
+
software_system = self._selector(self._parent._parent)
|
825
990
|
self._m.softwareSystemId = software_system.model.id
|
826
991
|
|
827
992
|
container_ids = { container.model.id for container in software_system.children}
|
828
993
|
|
829
|
-
view_elements_filter: List[Callable[[Workspace, Element], bool]] = [
|
994
|
+
view_elements_filter: List[Union[DslElement, Callable[[Workspace, Element], bool]]] = [
|
830
995
|
lambda w, e: e.parent == software_system,
|
831
996
|
lambda w, e: any(container_ids.intersection({ id for id in e.sources.ids })),
|
832
997
|
lambda w, e: any(container_ids.intersection({ id for id in e.destinations.ids })),
|
833
998
|
]
|
834
999
|
|
835
|
-
view_relationships_filter: List[Callable[[Workspace, Relationship], bool]] = [
|
1000
|
+
view_relationships_filter: List[Union[DslElement, Callable[[Workspace, Relationship], bool]]] = [
|
836
1001
|
lambda w, r: software_system == r.source.parent,
|
837
1002
|
lambda w, r: software_system == r.destination.parent,
|
838
1003
|
]
|
@@ -878,15 +1043,15 @@ class ComponentView(DslViewElement):
|
|
878
1043
|
|
879
1044
|
def __init__(
|
880
1045
|
self,
|
881
|
-
container_selector: Callable[[Workspace], Container],
|
1046
|
+
container_selector: Union[Container, Callable[[Workspace], Container]],
|
882
1047
|
key: str,
|
883
1048
|
description: str,
|
884
1049
|
auto_layout: _AutoLayout='tb',
|
885
1050
|
title: Optional[str]=None,
|
886
|
-
include_elements: List[Callable[[Workspace, Element], bool]]=[],
|
887
|
-
exclude_elements: List[Callable[[Workspace, Element], bool]]=[],
|
888
|
-
include_relationships: List[Callable[[Workspace, Relationship], bool]]=[],
|
889
|
-
exclude_relationships: List[Callable[[Workspace, Relationship], bool]]=[],
|
1051
|
+
include_elements: List[Union[DslElement, Callable[[Workspace, Element], bool]]]=[],
|
1052
|
+
exclude_elements: List[Union[DslElement, Callable[[Workspace, Element], bool]]]=[],
|
1053
|
+
include_relationships: List[Union[DslElement, Callable[[Workspace, Relationship], bool]]]=[],
|
1054
|
+
exclude_relationships: List[Union[DslElement, Callable[[Workspace, Relationship], bool]]]=[],
|
890
1055
|
properties: Optional[Dict[str, str]]=None,
|
891
1056
|
) -> None:
|
892
1057
|
self._m = buildzr.models.ComponentView()
|
@@ -905,23 +1070,30 @@ class ComponentView(DslViewElement):
|
|
905
1070
|
self._include_relationships = include_relationships
|
906
1071
|
self._exclude_relationships = exclude_relationships
|
907
1072
|
|
1073
|
+
workspace = _current_workspace.get()
|
1074
|
+
if workspace is not None:
|
1075
|
+
workspace.apply_views(self)
|
1076
|
+
|
908
1077
|
def _on_added(self) -> None:
|
909
1078
|
|
910
1079
|
from buildzr.dsl.expression import Expression, Element, Relationship
|
911
1080
|
from buildzr.models import ElementView, RelationshipView
|
912
1081
|
|
913
|
-
|
1082
|
+
if isinstance(self._selector, Container):
|
1083
|
+
container = self._selector
|
1084
|
+
else:
|
1085
|
+
container = self._selector(self._parent._parent)
|
914
1086
|
self._m.containerId = container.model.id
|
915
1087
|
|
916
1088
|
component_ids = { component.model.id for component in container.children }
|
917
1089
|
|
918
|
-
view_elements_filter: List[Callable[[Workspace, Element], bool]] = [
|
1090
|
+
view_elements_filter: List[Union[DslElement, Callable[[Workspace, Element], bool]]] = [
|
919
1091
|
lambda w, e: e.parent == container,
|
920
1092
|
lambda w, e: any(component_ids.intersection({ id for id in e.sources.ids })),
|
921
1093
|
lambda w, e: any(component_ids.intersection({ id for id in e.destinations.ids })),
|
922
1094
|
]
|
923
1095
|
|
924
|
-
view_relationships_filter: List[Callable[[Workspace, Relationship], bool]] = [
|
1096
|
+
view_relationships_filter: List[Union[DslElement, Callable[[Workspace, Relationship], bool]]] = [
|
925
1097
|
lambda w, r: container == r.source.parent,
|
926
1098
|
lambda w, r: container == r.destination.parent,
|
927
1099
|
]
|
@@ -955,6 +1127,9 @@ class ComponentView(DslViewElement):
|
|
955
1127
|
|
956
1128
|
class Views(DslViewsElement):
|
957
1129
|
|
1130
|
+
# TODO: Make this view a "hidden" class -- it's not a "first class citizen"
|
1131
|
+
# in buildzr DSL.
|
1132
|
+
|
958
1133
|
@property
|
959
1134
|
def model(self) -> buildzr.models.Views:
|
960
1135
|
return self._m
|
@@ -971,10 +1146,10 @@ class Views(DslViewsElement):
|
|
971
1146
|
self._parent = workspace
|
972
1147
|
self._parent._m.views = self._m
|
973
1148
|
|
974
|
-
def
|
1149
|
+
def add_views(
|
975
1150
|
self,
|
976
1151
|
*views: DslViewElement
|
977
|
-
) ->
|
1152
|
+
) -> None:
|
978
1153
|
|
979
1154
|
for view in views:
|
980
1155
|
if isinstance(view, SystemLandscapeView):
|
@@ -1008,10 +1183,282 @@ class Views(DslViewsElement):
|
|
1008
1183
|
else:
|
1009
1184
|
raise NotImplementedError("The view {0} is currently not supported", type(view))
|
1010
1185
|
|
1011
|
-
return _FluentSink(self._parent)
|
1012
|
-
|
1013
1186
|
def get_workspace(self) -> Workspace:
|
1014
1187
|
"""
|
1015
1188
|
Get the `Workspace` which contain this views definition.
|
1016
1189
|
"""
|
1017
|
-
return self._parent
|
1190
|
+
return self._parent
|
1191
|
+
|
1192
|
+
class StyleElements:
|
1193
|
+
|
1194
|
+
from buildzr.dsl.expression import Element
|
1195
|
+
|
1196
|
+
Shapes = Union[
|
1197
|
+
Literal['Box'],
|
1198
|
+
Literal['RoundedBox'],
|
1199
|
+
Literal['Circle'],
|
1200
|
+
Literal['Ellipse'],
|
1201
|
+
Literal['Hexagon'],
|
1202
|
+
Literal['Cylinder'],
|
1203
|
+
Literal['Pipe'],
|
1204
|
+
Literal['Person'],
|
1205
|
+
Literal['Robot'],
|
1206
|
+
Literal['Folder'],
|
1207
|
+
Literal['WebBrowser'],
|
1208
|
+
Literal['MobileDevicePortrait'],
|
1209
|
+
Literal['MobileDeviceLandscape'],
|
1210
|
+
Literal['Component'],
|
1211
|
+
]
|
1212
|
+
|
1213
|
+
@property
|
1214
|
+
def model(self) -> List[buildzr.models.ElementStyle]:
|
1215
|
+
return self._m
|
1216
|
+
|
1217
|
+
@property
|
1218
|
+
def parent(self) -> Optional[Workspace]:
|
1219
|
+
return self._parent
|
1220
|
+
|
1221
|
+
# TODO: Validate arguments with pydantic.
|
1222
|
+
def __init__(
|
1223
|
+
self,
|
1224
|
+
on: List[Union[
|
1225
|
+
DslElement,
|
1226
|
+
Group,
|
1227
|
+
Callable[[Workspace, Element], bool],
|
1228
|
+
Type[Union['Person', 'SoftwareSystem', 'Container', 'Component']],
|
1229
|
+
str
|
1230
|
+
]],
|
1231
|
+
shape: Optional[Shapes]=None,
|
1232
|
+
icon: Optional[str]=None,
|
1233
|
+
width: Optional[int]=None,
|
1234
|
+
height: Optional[int]=None,
|
1235
|
+
background: Optional[Union['str', Tuple[int, int, int], Color]]=None,
|
1236
|
+
color: Optional[Union['str', Tuple[int, int, int], Color]]=None,
|
1237
|
+
stroke: Optional[Union[str, Tuple[int, int, int], Color]]=None,
|
1238
|
+
stroke_width: Optional[int]=None,
|
1239
|
+
font_size: Optional[int]=None,
|
1240
|
+
border: Optional[Literal['solid', 'dashed', 'dotted']]=None,
|
1241
|
+
opacity: Optional[int]=None,
|
1242
|
+
metadata: Optional[bool]=None,
|
1243
|
+
description: Optional[bool]=None,
|
1244
|
+
) -> None:
|
1245
|
+
|
1246
|
+
# How the tag is populated depends on each element type in the
|
1247
|
+
# `elemenets`.
|
1248
|
+
# - If the element is a `DslElement`, then we create a unique tag
|
1249
|
+
# specifically to help the stylizer identify that specific element.
|
1250
|
+
# For example, if the element has an id `3`, then we should create a
|
1251
|
+
# tag, say, `style-element-3`.
|
1252
|
+
# - If the element is a `Group`, then we simply make create the tag
|
1253
|
+
# based on the group name and its nested path. For example,
|
1254
|
+
# `Group:Company 1/Department 1`.
|
1255
|
+
# - If the element is a `Callable[[Workspace, Element], bool]`, we just
|
1256
|
+
# run the function to filter out all the elements that matches the
|
1257
|
+
# description, and create a unique tag for all of the filtered
|
1258
|
+
# elements.
|
1259
|
+
# - If the element is a `Type[Union['Person', 'SoftwareSystem', 'Container', 'Component']]`,
|
1260
|
+
# we create a tag based on the class name. This is based on the fact
|
1261
|
+
# that the default tag for each element is the element's type.
|
1262
|
+
# - If the element is a `str`, we just use the string as the tag.
|
1263
|
+
# This is useful for when you want to apply a style to all elements
|
1264
|
+
# with a specific tag, just like in the original Structurizr DSL.
|
1265
|
+
#
|
1266
|
+
# Note that a new `buildzr.models.ElementStyle` is created for each
|
1267
|
+
# item, not for each of `StyleElements` instance. This makes the styling
|
1268
|
+
# makes more concise and flexible.
|
1269
|
+
|
1270
|
+
from buildzr.dsl.expression import Element
|
1271
|
+
from uuid import uuid4
|
1272
|
+
|
1273
|
+
if background:
|
1274
|
+
assert Color.is_valid_color(background), "Invalid background color: {}".format(background)
|
1275
|
+
if color:
|
1276
|
+
assert Color.is_valid_color(color), "Invalid color: {}".format(color)
|
1277
|
+
if stroke:
|
1278
|
+
assert Color.is_valid_color(stroke), "Invalid stroke color: {}".format(stroke)
|
1279
|
+
|
1280
|
+
self._m: List[buildzr.models.ElementStyle] = []
|
1281
|
+
self._parent: Optional[Workspace] = None
|
1282
|
+
|
1283
|
+
workspace = _current_workspace.get()
|
1284
|
+
if workspace is not None:
|
1285
|
+
self._parent = workspace
|
1286
|
+
|
1287
|
+
self._elements = on
|
1288
|
+
|
1289
|
+
border_enum: Dict[str, buildzr.models.Border] = {
|
1290
|
+
'solid': buildzr.models.Border.Solid,
|
1291
|
+
'dashed': buildzr.models.Border.Dashed,
|
1292
|
+
'dotted': buildzr.models.Border.Dotted,
|
1293
|
+
}
|
1294
|
+
|
1295
|
+
shape_enum: Dict[str, buildzr.models.Shape] = {
|
1296
|
+
'Box': buildzr.models.Shape.Box,
|
1297
|
+
'RoundedBox': buildzr.models.Shape.RoundedBox,
|
1298
|
+
'Circle': buildzr.models.Shape.Circle,
|
1299
|
+
'Ellipse': buildzr.models.Shape.Ellipse,
|
1300
|
+
'Hexagon': buildzr.models.Shape.Hexagon,
|
1301
|
+
'Cylinder': buildzr.models.Shape.Cylinder,
|
1302
|
+
'Pipe': buildzr.models.Shape.Pipe,
|
1303
|
+
'Person': buildzr.models.Shape.Person,
|
1304
|
+
'Robot': buildzr.models.Shape.Robot,
|
1305
|
+
'Folder': buildzr.models.Shape.Folder,
|
1306
|
+
'WebBrowser': buildzr.models.Shape.WebBrowser,
|
1307
|
+
'MobileDevicePortrait': buildzr.models.Shape.MobileDevicePortrait,
|
1308
|
+
'MobileDeviceLandscape': buildzr.models.Shape.MobileDeviceLandscape,
|
1309
|
+
'Component': buildzr.models.Shape.Component,
|
1310
|
+
}
|
1311
|
+
|
1312
|
+
# A single unique element to be applied to all elements
|
1313
|
+
# affected by this style.
|
1314
|
+
element_tag = "buildzr-styleelements-{}".format(uuid4().hex)
|
1315
|
+
|
1316
|
+
for element in self._elements:
|
1317
|
+
|
1318
|
+
element_style = buildzr.models.ElementStyle()
|
1319
|
+
element_style.shape = shape_enum[shape] if shape else None
|
1320
|
+
element_style.icon = icon
|
1321
|
+
element_style.width = width
|
1322
|
+
element_style.height = height
|
1323
|
+
element_style.background = Color(background).to_hex() if background else None
|
1324
|
+
element_style.color = Color(color).to_hex() if color else None
|
1325
|
+
element_style.stroke = Color(stroke).to_hex() if stroke else None
|
1326
|
+
element_style.strokeWidth = stroke_width
|
1327
|
+
element_style.fontSize = font_size
|
1328
|
+
element_style.border = border_enum[border] if border else None
|
1329
|
+
element_style.opacity = opacity
|
1330
|
+
element_style.metadata = metadata
|
1331
|
+
element_style.description = description
|
1332
|
+
|
1333
|
+
if isinstance(element, DslElement) and not isinstance(element.model, buildzr.models.Workspace):
|
1334
|
+
element_style.tag = element_tag
|
1335
|
+
element.add_tags(element_tag)
|
1336
|
+
elif isinstance(element, Group):
|
1337
|
+
element_style.tag = f"Group:{element.full_name()}"
|
1338
|
+
elif isinstance(element, type):
|
1339
|
+
element_style.tag = f"{element.__name__}"
|
1340
|
+
elif isinstance(element, str):
|
1341
|
+
element_style.tag = element
|
1342
|
+
elif callable(element):
|
1343
|
+
from buildzr.dsl.expression import Element, Expression
|
1344
|
+
if self._parent:
|
1345
|
+
matched_elems = Expression(include_elements=[element]).elements(self._parent)
|
1346
|
+
for e in matched_elems:
|
1347
|
+
element_style.tag = element_tag
|
1348
|
+
e.add_tags(element_tag)
|
1349
|
+
else:
|
1350
|
+
raise ValueError("Cannot use callable to select elements to style without a Workspace.")
|
1351
|
+
self._m.append(element_style)
|
1352
|
+
|
1353
|
+
workspace = _current_workspace.get()
|
1354
|
+
if workspace is not None:
|
1355
|
+
workspace.apply_style(self)
|
1356
|
+
|
1357
|
+
class StyleRelationships:
|
1358
|
+
|
1359
|
+
from buildzr.dsl.expression import Relationship
|
1360
|
+
|
1361
|
+
@property
|
1362
|
+
def model(self) -> List[buildzr.models.RelationshipStyle]:
|
1363
|
+
return self._m
|
1364
|
+
|
1365
|
+
@property
|
1366
|
+
def parent(self) -> Optional[Workspace]:
|
1367
|
+
return self._parent
|
1368
|
+
|
1369
|
+
def __init__(
|
1370
|
+
self,
|
1371
|
+
on: Optional[List[Union[
|
1372
|
+
DslRelationship,
|
1373
|
+
Group,
|
1374
|
+
Callable[[Workspace, Relationship], bool],
|
1375
|
+
str
|
1376
|
+
]]]=None,
|
1377
|
+
thickness: Optional[int]=None,
|
1378
|
+
color: Optional[Union[str, Tuple[int, int, int], Color]]=None,
|
1379
|
+
routing: Optional[Literal['Direct', 'Orthogonal', 'Curved']]=None,
|
1380
|
+
font_size: Optional[int]=None,
|
1381
|
+
width: Optional[int]=None,
|
1382
|
+
dashed: Optional[bool]=None,
|
1383
|
+
position: Optional[int]=None,
|
1384
|
+
opacity: Optional[int]=None,
|
1385
|
+
) -> None:
|
1386
|
+
|
1387
|
+
from uuid import uuid4
|
1388
|
+
|
1389
|
+
if color is not None:
|
1390
|
+
assert Color.is_valid_color(color), "Invalid color: {}".format(color)
|
1391
|
+
|
1392
|
+
routing_enum: Dict[str, buildzr.models.Routing1] = {
|
1393
|
+
'Direct': buildzr.models.Routing1.Direct,
|
1394
|
+
'Orthogonal': buildzr.models.Routing1.Orthogonal,
|
1395
|
+
'Curved': buildzr.models.Routing1.Curved,
|
1396
|
+
}
|
1397
|
+
|
1398
|
+
self._m: List[buildzr.models.RelationshipStyle] = []
|
1399
|
+
self._parent: Optional[Workspace] = None
|
1400
|
+
|
1401
|
+
workspace = _current_workspace.get()
|
1402
|
+
if workspace is not None:
|
1403
|
+
self._parent = workspace
|
1404
|
+
|
1405
|
+
# A single unique tag to be applied to all relationships
|
1406
|
+
# affected by this style.
|
1407
|
+
relation_tag = "buildzr-stylerelationships-{}".format(uuid4().hex)
|
1408
|
+
|
1409
|
+
if on is None:
|
1410
|
+
self._m.append(buildzr.models.RelationshipStyle(
|
1411
|
+
thickness=thickness,
|
1412
|
+
color=Color(color).to_hex() if color else None,
|
1413
|
+
routing=routing_enum[routing] if routing else None,
|
1414
|
+
fontSize=font_size,
|
1415
|
+
width=width,
|
1416
|
+
dashed=dashed,
|
1417
|
+
position=position,
|
1418
|
+
opacity=opacity,
|
1419
|
+
tag="Relationship",
|
1420
|
+
))
|
1421
|
+
else:
|
1422
|
+
for relationship in on:
|
1423
|
+
|
1424
|
+
relationship_style = buildzr.models.RelationshipStyle()
|
1425
|
+
relationship_style.thickness = thickness
|
1426
|
+
relationship_style.color = Color(color).to_hex() if color else None
|
1427
|
+
relationship_style.routing = routing_enum[routing] if routing else None
|
1428
|
+
relationship_style.fontSize = font_size
|
1429
|
+
relationship_style.width = width
|
1430
|
+
relationship_style.dashed = dashed
|
1431
|
+
relationship_style.position = position
|
1432
|
+
relationship_style.opacity = opacity
|
1433
|
+
|
1434
|
+
if isinstance(relationship, DslRelationship):
|
1435
|
+
relationship.add_tags(relation_tag)
|
1436
|
+
relationship_style.tag = relation_tag
|
1437
|
+
elif isinstance(relationship, Group):
|
1438
|
+
from buildzr.dsl.expression import Expression
|
1439
|
+
if self._parent:
|
1440
|
+
rels = Expression(include_relationships=[
|
1441
|
+
lambda w, r: r.source.group == relationship.full_name() and \
|
1442
|
+
r.destination.group == relationship.full_name()
|
1443
|
+
]).relationships(self._parent)
|
1444
|
+
for r in rels:
|
1445
|
+
r.add_tags(relation_tag)
|
1446
|
+
relationship_style.tag = relation_tag
|
1447
|
+
else:
|
1448
|
+
raise ValueError("Cannot use callable to select elements to style without a Workspace.")
|
1449
|
+
elif isinstance(relationship, str):
|
1450
|
+
relationship_style.tag = relationship
|
1451
|
+
elif callable(relationship):
|
1452
|
+
from buildzr.dsl.expression import Expression
|
1453
|
+
if self._parent:
|
1454
|
+
matched_rels = Expression(include_relationships=[relationship]).relationships(self._parent)
|
1455
|
+
for matched_rel in matched_rels:
|
1456
|
+
matched_rel.add_tags(relation_tag)
|
1457
|
+
relationship_style.tag = relation_tag
|
1458
|
+
else:
|
1459
|
+
raise ValueError("Cannot use callable to select elements to style without a Workspace.")
|
1460
|
+
self._m.append(relationship_style)
|
1461
|
+
|
1462
|
+
workspace = _current_workspace.get()
|
1463
|
+
if workspace is not None:
|
1464
|
+
workspace.apply_style(self)
|