rdf-construct 0.2.0__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.
Files changed (88) hide show
  1. rdf_construct/__init__.py +12 -0
  2. rdf_construct/__main__.py +0 -0
  3. rdf_construct/cli.py +1762 -0
  4. rdf_construct/core/__init__.py +33 -0
  5. rdf_construct/core/config.py +116 -0
  6. rdf_construct/core/ordering.py +219 -0
  7. rdf_construct/core/predicate_order.py +212 -0
  8. rdf_construct/core/profile.py +157 -0
  9. rdf_construct/core/selector.py +64 -0
  10. rdf_construct/core/serialiser.py +232 -0
  11. rdf_construct/core/utils.py +89 -0
  12. rdf_construct/cq/__init__.py +77 -0
  13. rdf_construct/cq/expectations.py +365 -0
  14. rdf_construct/cq/formatters/__init__.py +45 -0
  15. rdf_construct/cq/formatters/json.py +104 -0
  16. rdf_construct/cq/formatters/junit.py +104 -0
  17. rdf_construct/cq/formatters/text.py +146 -0
  18. rdf_construct/cq/loader.py +300 -0
  19. rdf_construct/cq/runner.py +321 -0
  20. rdf_construct/diff/__init__.py +59 -0
  21. rdf_construct/diff/change_types.py +214 -0
  22. rdf_construct/diff/comparator.py +338 -0
  23. rdf_construct/diff/filters.py +133 -0
  24. rdf_construct/diff/formatters/__init__.py +71 -0
  25. rdf_construct/diff/formatters/json.py +192 -0
  26. rdf_construct/diff/formatters/markdown.py +210 -0
  27. rdf_construct/diff/formatters/text.py +195 -0
  28. rdf_construct/docs/__init__.py +60 -0
  29. rdf_construct/docs/config.py +238 -0
  30. rdf_construct/docs/extractors.py +603 -0
  31. rdf_construct/docs/generator.py +360 -0
  32. rdf_construct/docs/renderers/__init__.py +7 -0
  33. rdf_construct/docs/renderers/html.py +803 -0
  34. rdf_construct/docs/renderers/json.py +390 -0
  35. rdf_construct/docs/renderers/markdown.py +628 -0
  36. rdf_construct/docs/search.py +278 -0
  37. rdf_construct/docs/templates/html/base.html.jinja +44 -0
  38. rdf_construct/docs/templates/html/class.html.jinja +152 -0
  39. rdf_construct/docs/templates/html/hierarchy.html.jinja +28 -0
  40. rdf_construct/docs/templates/html/index.html.jinja +110 -0
  41. rdf_construct/docs/templates/html/instance.html.jinja +90 -0
  42. rdf_construct/docs/templates/html/namespaces.html.jinja +37 -0
  43. rdf_construct/docs/templates/html/property.html.jinja +124 -0
  44. rdf_construct/docs/templates/html/single_page.html.jinja +169 -0
  45. rdf_construct/lint/__init__.py +75 -0
  46. rdf_construct/lint/config.py +214 -0
  47. rdf_construct/lint/engine.py +396 -0
  48. rdf_construct/lint/formatters.py +327 -0
  49. rdf_construct/lint/rules.py +692 -0
  50. rdf_construct/main.py +6 -0
  51. rdf_construct/puml2rdf/__init__.py +103 -0
  52. rdf_construct/puml2rdf/config.py +230 -0
  53. rdf_construct/puml2rdf/converter.py +420 -0
  54. rdf_construct/puml2rdf/merger.py +200 -0
  55. rdf_construct/puml2rdf/model.py +202 -0
  56. rdf_construct/puml2rdf/parser.py +565 -0
  57. rdf_construct/puml2rdf/validators.py +451 -0
  58. rdf_construct/shacl/__init__.py +56 -0
  59. rdf_construct/shacl/config.py +166 -0
  60. rdf_construct/shacl/converters.py +520 -0
  61. rdf_construct/shacl/generator.py +364 -0
  62. rdf_construct/shacl/namespaces.py +93 -0
  63. rdf_construct/stats/__init__.py +29 -0
  64. rdf_construct/stats/collector.py +178 -0
  65. rdf_construct/stats/comparator.py +298 -0
  66. rdf_construct/stats/formatters/__init__.py +83 -0
  67. rdf_construct/stats/formatters/json.py +38 -0
  68. rdf_construct/stats/formatters/markdown.py +153 -0
  69. rdf_construct/stats/formatters/text.py +186 -0
  70. rdf_construct/stats/metrics/__init__.py +26 -0
  71. rdf_construct/stats/metrics/basic.py +147 -0
  72. rdf_construct/stats/metrics/complexity.py +137 -0
  73. rdf_construct/stats/metrics/connectivity.py +130 -0
  74. rdf_construct/stats/metrics/documentation.py +128 -0
  75. rdf_construct/stats/metrics/hierarchy.py +207 -0
  76. rdf_construct/stats/metrics/properties.py +88 -0
  77. rdf_construct/uml/__init__.py +22 -0
  78. rdf_construct/uml/context.py +194 -0
  79. rdf_construct/uml/mapper.py +371 -0
  80. rdf_construct/uml/odm_renderer.py +789 -0
  81. rdf_construct/uml/renderer.py +684 -0
  82. rdf_construct/uml/uml_layout.py +393 -0
  83. rdf_construct/uml/uml_style.py +613 -0
  84. rdf_construct-0.2.0.dist-info/METADATA +431 -0
  85. rdf_construct-0.2.0.dist-info/RECORD +88 -0
  86. rdf_construct-0.2.0.dist-info/WHEEL +4 -0
  87. rdf_construct-0.2.0.dist-info/entry_points.txt +3 -0
  88. rdf_construct-0.2.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,393 @@
1
+ """PlantUML layout configuration for RDF class diagrams.
2
+
3
+ Provides control over diagram direction, spacing, grouping,
4
+ and other layout-related aspects.
5
+
6
+ Enhanced features:
7
+ - together: Group classes to be placed adjacent
8
+ - linetype: Orthogonal or polyline routing
9
+ - group_inheritance: Merge arrow heads for multiple subclasses
10
+ - layout_hints: Hidden links to influence positioning
11
+ """
12
+
13
+ from dataclasses import dataclass, field
14
+ from pathlib import Path
15
+ from typing import Any, Literal, Optional
16
+
17
+ import yaml
18
+
19
+
20
+ LayoutDirection = Literal[
21
+ "top_to_bottom",
22
+ "bottom_to_top",
23
+ "left_to_right",
24
+ "right_to_left",
25
+ ]
26
+
27
+ LineType = Literal["ortho", "polyline", "spline"]
28
+
29
+
30
+ @dataclass
31
+ class LayoutHint:
32
+ """A hidden link hint to influence layout positioning.
33
+
34
+ Attributes:
35
+ from_entity: Source entity CURIE (e.g., 'ies:Entity')
36
+ to_entity: Target entity CURIE (e.g., 'building:Building')
37
+ direction: Optional direction hint ('up', 'down', 'left', 'right')
38
+ weight: Number of hidden links (higher = stronger influence)
39
+ """
40
+ from_entity: str
41
+ to_entity: str
42
+ direction: Optional[str] = None
43
+ weight: int = 1
44
+
45
+
46
+ class LayoutConfig:
47
+ """Layout configuration for PlantUML diagrams.
48
+
49
+ Attributes:
50
+ name: Layout identifier
51
+ description: Human-readable description
52
+ direction: Primary layout direction
53
+ hide_empty_members: Whether to hide classes with no attributes/methods
54
+ spacing: Spacing configuration dict
55
+ group_by_namespace: Whether to group classes by namespace
56
+ arrow_direction: Direction hint for arrows ('up', 'down', 'left', 'right')
57
+ linetype: Line routing style ('ortho', 'polyline', 'spline')
58
+ group_inheritance: Threshold for merging inheritance arrows (0 = disabled)
59
+ together_groups: List of class groups to place adjacent
60
+ layout_hints: List of hidden link hints for positioning
61
+ """
62
+
63
+ def __init__(self, name: str, config: dict[str, Any]):
64
+ """Initialize layout configuration.
65
+
66
+ Args:
67
+ name: Layout identifier
68
+ config: Layout configuration dictionary from YAML
69
+ """
70
+ self.name = name
71
+ self.description = config.get("description", "")
72
+
73
+ # Layout direction
74
+ direction_str = config.get("direction", "top_to_bottom")
75
+ self.direction: LayoutDirection = self._validate_direction(direction_str)
76
+
77
+ # Display options
78
+ self.hide_empty_members = config.get("hide_empty_members", False)
79
+ self.show_arrows = config.get("show_arrows", True)
80
+
81
+ # Arrow direction hints for hierarchy
82
+ arrow_dir = config.get("arrow_direction", "up")
83
+ self.arrow_direction = self._validate_arrow_direction(arrow_dir)
84
+
85
+ # Grouping
86
+ self.group_by_namespace = config.get("group_by_namespace", False)
87
+
88
+ # Spacing (PlantUML skinparam settings)
89
+ self.spacing = config.get("spacing", {})
90
+
91
+ # NEW: Line type for edge routing
92
+ linetype = config.get("linetype")
93
+ self.linetype: Optional[LineType] = self._validate_linetype(linetype)
94
+
95
+ # NEW: Group inheritance threshold
96
+ self.group_inheritance = config.get("group_inheritance", 0)
97
+
98
+ # NEW: Together groups - classes to place adjacent
99
+ self.together_groups: list[list[str]] = config.get("together", [])
100
+
101
+ # NEW: Layout hints - hidden links to influence positioning
102
+ self.layout_hints: list[LayoutHint] = []
103
+ for hint in config.get("layout_hints", []):
104
+ if isinstance(hint, dict) and "from" in hint and "to" in hint:
105
+ self.layout_hints.append(LayoutHint(
106
+ from_entity=hint["from"],
107
+ to_entity=hint["to"],
108
+ direction=hint.get("direction"),
109
+ weight=hint.get("weight", 1),
110
+ ))
111
+
112
+ def _validate_direction(self, direction: str) -> LayoutDirection:
113
+ """Validate and normalize layout direction.
114
+
115
+ Args:
116
+ direction: Direction string from config
117
+
118
+ Returns:
119
+ Validated LayoutDirection
120
+ """
121
+ direction_map = {
122
+ "top_to_bottom": "top_to_bottom",
123
+ "ttb": "top_to_bottom",
124
+ "tb": "top_to_bottom",
125
+ "bottom_to_top": "bottom_to_top",
126
+ "btt": "bottom_to_top",
127
+ "bt": "bottom_to_top",
128
+ "left_to_right": "left_to_right",
129
+ "ltr": "left_to_right",
130
+ "lr": "left_to_right",
131
+ "right_to_left": "right_to_left",
132
+ "rtl": "right_to_left",
133
+ "rl": "right_to_left",
134
+ }
135
+
136
+ normalized = direction_map.get(direction.lower(), "top_to_bottom")
137
+ return normalized # type: ignore
138
+
139
+ def _validate_arrow_direction(self, arrow_dir: str) -> str:
140
+ """Validate arrow direction hint.
141
+
142
+ Args:
143
+ arrow_dir: Arrow direction from config
144
+
145
+ Returns:
146
+ Validated arrow direction ('up', 'down', 'left', 'right')
147
+ """
148
+ valid_directions = {"up", "down", "left", "right"}
149
+ if arrow_dir.lower() in valid_directions:
150
+ return arrow_dir.lower()
151
+ return "up" # Default: parents above children
152
+
153
+ def _validate_linetype(self, linetype: Optional[str]) -> Optional[LineType]:
154
+ """Validate line type setting.
155
+
156
+ Args:
157
+ linetype: Line type from config
158
+
159
+ Returns:
160
+ Validated LineType or None
161
+ """
162
+ if linetype is None:
163
+ return None
164
+ valid_types = {"ortho", "polyline", "spline"}
165
+ if linetype.lower() in valid_types:
166
+ return linetype.lower() # type: ignore
167
+ return None
168
+
169
+ def get_arrow_syntax(self, relationship_type: str) -> str:
170
+ """Get PlantUML arrow syntax with direction hint.
171
+
172
+ For subclass relationships (inheritance), we can specify arrow
173
+ direction to influence layout. For example:
174
+ - '-up->' : child points up to parent
175
+ - '-down->' : parent points down to child
176
+ - '-->' : no direction hint
177
+
178
+ Args:
179
+ relationship_type: Type of relationship ('subclass', 'instance',
180
+ 'object_property', etc.)
181
+
182
+ Returns:
183
+ PlantUML arrow syntax with optional direction hint
184
+ """
185
+ if not self.show_arrows:
186
+ return "--"
187
+
188
+ # Map relationship types to arrow styles
189
+ arrow_map = {
190
+ "subclass": "|>", # Inheritance (triangle)
191
+ "instance": "|>", # Instance-of (typically dotted)
192
+ "object_property": ">", # Association
193
+ }
194
+
195
+ arrow_glyph = arrow_map.get(relationship_type, ">")
196
+
197
+ # Add direction hint for hierarchical relationships
198
+ if relationship_type in ("subclass", "instance"):
199
+ if self.arrow_direction in ("up", "down", "left", "right"):
200
+ return f"-{self.arrow_direction}-{arrow_glyph}"
201
+
202
+ # Default: no direction hint
203
+ return f"-{arrow_glyph}"
204
+
205
+ def get_plantuml_directives(self) -> list[str]:
206
+ """Generate PlantUML directives for layout control.
207
+
208
+ Note: PlantUML only reliably supports 'top to bottom direction' and
209
+ 'left to right direction'. Other directions may not work as expected.
210
+
211
+ Returns:
212
+ List of PlantUML directive strings (skinparam, etc.)
213
+ """
214
+ directives = []
215
+
216
+ # Layout direction
217
+ # Note: Only top_to_bottom and left_to_right are reliably supported
218
+ direction_map = {
219
+ "top_to_bottom": "top to bottom direction",
220
+ "left_to_right": "left to right direction",
221
+ # These are not reliably supported by PlantUML:
222
+ # "bottom_to_top": "bottom to top direction",
223
+ # "right_to_left": "right to left direction",
224
+ }
225
+ if self.direction in direction_map:
226
+ directives.append(direction_map[self.direction])
227
+
228
+ # Hide empty members
229
+ if self.hide_empty_members:
230
+ directives.append("hide empty members")
231
+
232
+ # Line type (edge routing)
233
+ if self.linetype:
234
+ directives.append(f"skinparam linetype {self.linetype}")
235
+
236
+ # Group inheritance (merge arrow heads)
237
+ if self.group_inheritance and self.group_inheritance > 0:
238
+ directives.append(f"skinparam groupInheritance {self.group_inheritance}")
239
+
240
+ # Spacing settings
241
+ if self.spacing:
242
+ for key, value in self.spacing.items():
243
+ directives.append(f"skinparam {key} {value}")
244
+
245
+ return directives
246
+
247
+ def get_together_blocks(self, id_resolver: Optional[callable] = None) -> list[str]:
248
+ """Generate PlantUML 'together' blocks for class grouping.
249
+
250
+ Args:
251
+ id_resolver: Optional function to convert CURIEs to PlantUML identifiers.
252
+ If None, CURIEs are used as-is with colons replaced.
253
+
254
+ Returns:
255
+ List of PlantUML 'together' block strings
256
+ """
257
+ if not self.together_groups:
258
+ return []
259
+
260
+ lines = []
261
+ for group in self.together_groups:
262
+ if not group:
263
+ continue
264
+
265
+ lines.append("together {")
266
+ for curie in group:
267
+ if id_resolver:
268
+ class_id = id_resolver(curie)
269
+ else:
270
+ # Simple conversion: replace : with . for PlantUML
271
+ class_id = curie.replace(":", ".")
272
+ lines.append(f" class {class_id}")
273
+ lines.append("}")
274
+ lines.append("")
275
+
276
+ return lines
277
+
278
+ def get_hidden_links(self, id_resolver: Optional[callable] = None) -> list[str]:
279
+ """Generate PlantUML hidden links for layout hints.
280
+
281
+ Args:
282
+ id_resolver: Optional function to convert CURIEs to PlantUML identifiers.
283
+ If None, CURIEs are used as-is with colons replaced.
284
+
285
+ Returns:
286
+ List of PlantUML hidden link strings
287
+ """
288
+ if not self.layout_hints:
289
+ return []
290
+
291
+ lines = []
292
+ for hint in self.layout_hints:
293
+ if id_resolver:
294
+ from_id = id_resolver(hint.from_entity)
295
+ to_id = id_resolver(hint.to_entity)
296
+ else:
297
+ from_id = hint.from_entity.replace(":", ".")
298
+ to_id = hint.to_entity.replace(":", ".")
299
+
300
+ # Build arrow syntax
301
+ if hint.direction:
302
+ arrow = f"-[hidden,{hint.direction}]->"
303
+ else:
304
+ arrow = "-[hidden]->"
305
+
306
+ # Repeat for weight (more links = stronger influence)
307
+ for _ in range(hint.weight):
308
+ lines.append(f"{from_id} {arrow} {to_id}")
309
+
310
+ return lines
311
+
312
+ def __repr__(self) -> str:
313
+ return (
314
+ f"LayoutConfig(name={self.name!r}, "
315
+ f"direction={self.direction}, "
316
+ f"arrow_dir={self.arrow_direction}, "
317
+ f"linetype={self.linetype}, "
318
+ f"group_inheritance={self.group_inheritance}, "
319
+ f"together_groups={len(self.together_groups)}, "
320
+ f"layout_hints={len(self.layout_hints)})"
321
+ )
322
+
323
+
324
+ class LayoutConfigManager:
325
+ """Manager for layout configurations.
326
+
327
+ Loads and manages YAML-based layout specifications with support
328
+ for multiple layouts and shared configuration via YAML anchors.
329
+
330
+ Attributes:
331
+ defaults: Default layout settings
332
+ layouts: Dictionary of available layout configurations
333
+ """
334
+
335
+ def __init__(self, yaml_path: Path | str):
336
+ """Load layout configuration from a YAML file.
337
+
338
+ Args:
339
+ yaml_path: Path to YAML layout configuration file
340
+ """
341
+ yaml_path = Path(yaml_path)
342
+ self.config = yaml.safe_load(yaml_path.read_text(encoding="utf-8"))
343
+
344
+ self.defaults = self.config.get("defaults", {}) or {}
345
+
346
+ # Load layouts
347
+ self.layouts = {}
348
+ for layout_name, layout_config in (
349
+ self.config.get("layouts", {}) or {}
350
+ ).items():
351
+ self.layouts[layout_name] = LayoutConfig(layout_name, layout_config)
352
+
353
+ def get_layout(self, name: str) -> LayoutConfig:
354
+ """Get a layout configuration by name.
355
+
356
+ Args:
357
+ name: Layout identifier
358
+
359
+ Returns:
360
+ LayoutConfig instance
361
+
362
+ Raises:
363
+ KeyError: If layout name not found
364
+ """
365
+ if name not in self.layouts:
366
+ raise KeyError(
367
+ f"Layout '{name}' not found. Available layouts: "
368
+ f"{', '.join(self.layouts.keys())}"
369
+ )
370
+ return self.layouts[name]
371
+
372
+ def list_layouts(self) -> list[str]:
373
+ """Get list of available layout names.
374
+
375
+ Returns:
376
+ List of layout identifier strings
377
+ """
378
+ return list(self.layouts.keys())
379
+
380
+ def __repr__(self) -> str:
381
+ return f"LayoutConfigManager(layouts={list(self.layouts.keys())})"
382
+
383
+
384
+ def load_layout_config(path: Path | str) -> LayoutConfigManager:
385
+ """Load layout configuration from a YAML file.
386
+
387
+ Args:
388
+ path: Path to YAML layout configuration file
389
+
390
+ Returns:
391
+ LayoutConfigManager instance
392
+ """
393
+ return LayoutConfigManager(path)