rdf-construct 0.2.1__py3-none-any.whl → 0.4.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.
- rdf_construct/__init__.py +1 -1
- rdf_construct/cli.py +1794 -0
- rdf_construct/describe/__init__.py +93 -0
- rdf_construct/describe/analyzer.py +176 -0
- rdf_construct/describe/documentation.py +146 -0
- rdf_construct/describe/formatters/__init__.py +47 -0
- rdf_construct/describe/formatters/json.py +65 -0
- rdf_construct/describe/formatters/markdown.py +275 -0
- rdf_construct/describe/formatters/text.py +315 -0
- rdf_construct/describe/hierarchy.py +232 -0
- rdf_construct/describe/imports.py +213 -0
- rdf_construct/describe/metadata.py +187 -0
- rdf_construct/describe/metrics.py +145 -0
- rdf_construct/describe/models.py +552 -0
- rdf_construct/describe/namespaces.py +180 -0
- rdf_construct/describe/profiles.py +415 -0
- rdf_construct/localise/__init__.py +114 -0
- rdf_construct/localise/config.py +508 -0
- rdf_construct/localise/extractor.py +427 -0
- rdf_construct/localise/formatters/__init__.py +36 -0
- rdf_construct/localise/formatters/markdown.py +229 -0
- rdf_construct/localise/formatters/text.py +224 -0
- rdf_construct/localise/merger.py +346 -0
- rdf_construct/localise/reporter.py +356 -0
- rdf_construct/merge/__init__.py +165 -0
- rdf_construct/merge/config.py +354 -0
- rdf_construct/merge/conflicts.py +281 -0
- rdf_construct/merge/formatters.py +426 -0
- rdf_construct/merge/merger.py +425 -0
- rdf_construct/merge/migrator.py +339 -0
- rdf_construct/merge/rules.py +377 -0
- rdf_construct/merge/splitter.py +1102 -0
- rdf_construct/refactor/__init__.py +72 -0
- rdf_construct/refactor/config.py +362 -0
- rdf_construct/refactor/deprecator.py +328 -0
- rdf_construct/refactor/formatters/__init__.py +8 -0
- rdf_construct/refactor/formatters/text.py +311 -0
- rdf_construct/refactor/renamer.py +294 -0
- {rdf_construct-0.2.1.dist-info → rdf_construct-0.4.0.dist-info}/METADATA +91 -6
- {rdf_construct-0.2.1.dist-info → rdf_construct-0.4.0.dist-info}/RECORD +43 -7
- {rdf_construct-0.2.1.dist-info → rdf_construct-0.4.0.dist-info}/WHEEL +0 -0
- {rdf_construct-0.2.1.dist-info → rdf_construct-0.4.0.dist-info}/entry_points.txt +0 -0
- {rdf_construct-0.2.1.dist-info → rdf_construct-0.4.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"""Deprecation workflow for ontology entities.
|
|
2
|
+
|
|
3
|
+
This module handles marking ontology entities as deprecated:
|
|
4
|
+
- Adds owl:deprecated true
|
|
5
|
+
- Adds dcterms:isReplacedBy with replacement URI
|
|
6
|
+
- Updates rdfs:comment with deprecation notice
|
|
7
|
+
- Preserves all existing properties
|
|
8
|
+
|
|
9
|
+
Deprecation marks entities but does NOT rename or migrate references.
|
|
10
|
+
Use 'refactor rename' to actually migrate references after deprecation.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from rdflib import Graph, URIRef, Literal, Namespace
|
|
18
|
+
from rdflib.namespace import RDF, RDFS, OWL, DCTERMS
|
|
19
|
+
|
|
20
|
+
from rdf_construct.refactor.config import DeprecationSpec, DeprecationConfig
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Dublin Core Terms namespace
|
|
24
|
+
DCTERMS = Namespace("http://purl.org/dc/terms/")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class EntityDeprecationInfo:
|
|
29
|
+
"""Information about a deprecated entity.
|
|
30
|
+
|
|
31
|
+
Attributes:
|
|
32
|
+
uri: URI of the deprecated entity.
|
|
33
|
+
found: Whether the entity was found in the graph.
|
|
34
|
+
current_labels: Current rdfs:label values.
|
|
35
|
+
current_comments: Current rdfs:comment values.
|
|
36
|
+
was_already_deprecated: Whether owl:deprecated was already present.
|
|
37
|
+
triples_added: Number of triples added.
|
|
38
|
+
reference_count: Number of references to this entity in the graph.
|
|
39
|
+
replaced_by: Replacement entity URI (if specified).
|
|
40
|
+
message: Deprecation message added.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
uri: str
|
|
44
|
+
found: bool = True
|
|
45
|
+
current_labels: list[str] = field(default_factory=list)
|
|
46
|
+
current_comments: list[str] = field(default_factory=list)
|
|
47
|
+
was_already_deprecated: bool = False
|
|
48
|
+
triples_added: int = 0
|
|
49
|
+
reference_count: int = 0
|
|
50
|
+
replaced_by: str | None = None
|
|
51
|
+
message: str | None = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class DeprecationStats:
|
|
56
|
+
"""Statistics from a deprecation operation.
|
|
57
|
+
|
|
58
|
+
Attributes:
|
|
59
|
+
entities_deprecated: Number of entities marked deprecated.
|
|
60
|
+
entities_not_found: Number of entities not found in graph.
|
|
61
|
+
entities_already_deprecated: Entities that were already deprecated.
|
|
62
|
+
triples_added: Total triples added.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
entities_deprecated: int = 0
|
|
66
|
+
entities_not_found: int = 0
|
|
67
|
+
entities_already_deprecated: int = 0
|
|
68
|
+
triples_added: int = 0
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class DeprecationResult:
|
|
73
|
+
"""Result of a deprecation operation.
|
|
74
|
+
|
|
75
|
+
Attributes:
|
|
76
|
+
deprecated_graph: The graph with deprecations added.
|
|
77
|
+
stats: Deprecation statistics.
|
|
78
|
+
success: Whether the operation succeeded.
|
|
79
|
+
error: Error message if success is False.
|
|
80
|
+
entity_info: Detailed info about each entity processed.
|
|
81
|
+
source_triples: Original triple count.
|
|
82
|
+
result_triples: Final triple count.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
deprecated_graph: Graph | None = None
|
|
86
|
+
stats: DeprecationStats = field(default_factory=DeprecationStats)
|
|
87
|
+
success: bool = True
|
|
88
|
+
error: str | None = None
|
|
89
|
+
entity_info: list[EntityDeprecationInfo] = field(default_factory=list)
|
|
90
|
+
source_triples: int = 0
|
|
91
|
+
result_triples: int = 0
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class OntologyDeprecator:
|
|
95
|
+
"""Marks ontology entities as deprecated.
|
|
96
|
+
|
|
97
|
+
Adds standard OWL deprecation annotations:
|
|
98
|
+
- owl:deprecated true
|
|
99
|
+
- dcterms:isReplacedBy (if replacement specified)
|
|
100
|
+
- Prepends "DEPRECATED: " to rdfs:comment
|
|
101
|
+
|
|
102
|
+
Example usage:
|
|
103
|
+
deprecator = OntologyDeprecator()
|
|
104
|
+
result = deprecator.deprecate(
|
|
105
|
+
graph,
|
|
106
|
+
entity="http://example.org/OldClass",
|
|
107
|
+
replaced_by="http://example.org/NewClass",
|
|
108
|
+
message="Use NewClass instead."
|
|
109
|
+
)
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
def deprecate(
|
|
113
|
+
self,
|
|
114
|
+
graph: Graph,
|
|
115
|
+
entity: str,
|
|
116
|
+
replaced_by: str | None = None,
|
|
117
|
+
message: str | None = None,
|
|
118
|
+
version: str | None = None,
|
|
119
|
+
) -> DeprecationResult:
|
|
120
|
+
"""Mark a single entity as deprecated.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
graph: Source RDF graph (will be modified in-place).
|
|
124
|
+
entity: URI of entity to deprecate.
|
|
125
|
+
replaced_by: Optional URI of replacement entity.
|
|
126
|
+
message: Optional deprecation message.
|
|
127
|
+
version: Optional version when deprecated.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
DeprecationResult with updated graph.
|
|
131
|
+
"""
|
|
132
|
+
result = DeprecationResult()
|
|
133
|
+
result.source_triples = len(graph)
|
|
134
|
+
|
|
135
|
+
entity_uri = URIRef(entity)
|
|
136
|
+
info = EntityDeprecationInfo(uri=entity)
|
|
137
|
+
|
|
138
|
+
# Check if entity exists in the graph
|
|
139
|
+
entity_exists = False
|
|
140
|
+
for s, p, o in graph:
|
|
141
|
+
if s == entity_uri:
|
|
142
|
+
entity_exists = True
|
|
143
|
+
break
|
|
144
|
+
if o == entity_uri:
|
|
145
|
+
info.reference_count += 1
|
|
146
|
+
|
|
147
|
+
if not entity_exists:
|
|
148
|
+
# Entity not found as subject - check if it's referenced
|
|
149
|
+
info.found = False
|
|
150
|
+
result.stats.entities_not_found += 1
|
|
151
|
+
result.entity_info.append(info)
|
|
152
|
+
result.deprecated_graph = graph
|
|
153
|
+
result.result_triples = len(graph)
|
|
154
|
+
return result
|
|
155
|
+
|
|
156
|
+
# Get current labels and comments
|
|
157
|
+
for label in graph.objects(entity_uri, RDFS.label):
|
|
158
|
+
if isinstance(label, Literal):
|
|
159
|
+
info.current_labels.append(str(label))
|
|
160
|
+
|
|
161
|
+
for comment in graph.objects(entity_uri, RDFS.comment):
|
|
162
|
+
if isinstance(comment, Literal):
|
|
163
|
+
info.current_comments.append(str(comment))
|
|
164
|
+
|
|
165
|
+
# Check if already deprecated
|
|
166
|
+
for obj in graph.objects(entity_uri, OWL.deprecated):
|
|
167
|
+
if str(obj).lower() == "true":
|
|
168
|
+
info.was_already_deprecated = True
|
|
169
|
+
result.stats.entities_already_deprecated += 1
|
|
170
|
+
break
|
|
171
|
+
|
|
172
|
+
# Add owl:deprecated true (if not already present)
|
|
173
|
+
if not info.was_already_deprecated:
|
|
174
|
+
graph.add((entity_uri, OWL.deprecated, Literal(True)))
|
|
175
|
+
info.triples_added += 1
|
|
176
|
+
result.stats.entities_deprecated += 1
|
|
177
|
+
|
|
178
|
+
# Add dcterms:isReplacedBy if replacement specified
|
|
179
|
+
if replaced_by:
|
|
180
|
+
replaced_by_uri = URIRef(replaced_by)
|
|
181
|
+
# Remove any existing isReplacedBy
|
|
182
|
+
graph.remove((entity_uri, DCTERMS.isReplacedBy, None))
|
|
183
|
+
graph.add((entity_uri, DCTERMS.isReplacedBy, replaced_by_uri))
|
|
184
|
+
info.triples_added += 1
|
|
185
|
+
info.replaced_by = replaced_by
|
|
186
|
+
|
|
187
|
+
# Add/update deprecation comment
|
|
188
|
+
if message:
|
|
189
|
+
# Build full deprecation message
|
|
190
|
+
deprecation_msg = f"DEPRECATED: {message}"
|
|
191
|
+
if version:
|
|
192
|
+
deprecation_msg = f"DEPRECATED (v{version}): {message}"
|
|
193
|
+
info.message = deprecation_msg
|
|
194
|
+
|
|
195
|
+
# Check if there's an existing comment to update
|
|
196
|
+
existing_deprecated_comment = None
|
|
197
|
+
for comment in list(graph.objects(entity_uri, RDFS.comment)):
|
|
198
|
+
if isinstance(comment, Literal) and str(comment).startswith("DEPRECATED"):
|
|
199
|
+
existing_deprecated_comment = comment
|
|
200
|
+
break
|
|
201
|
+
|
|
202
|
+
if existing_deprecated_comment:
|
|
203
|
+
# Remove old deprecation comment
|
|
204
|
+
graph.remove((entity_uri, RDFS.comment, existing_deprecated_comment))
|
|
205
|
+
|
|
206
|
+
# Add new deprecation comment
|
|
207
|
+
graph.add((entity_uri, RDFS.comment, Literal(deprecation_msg, lang="en")))
|
|
208
|
+
info.triples_added += 1
|
|
209
|
+
|
|
210
|
+
# Ensure dcterms namespace is bound
|
|
211
|
+
graph.bind("dcterms", DCTERMS, override=False)
|
|
212
|
+
|
|
213
|
+
result.stats.triples_added += info.triples_added
|
|
214
|
+
result.entity_info.append(info)
|
|
215
|
+
result.deprecated_graph = graph
|
|
216
|
+
result.result_triples = len(graph)
|
|
217
|
+
result.success = True
|
|
218
|
+
|
|
219
|
+
return result
|
|
220
|
+
|
|
221
|
+
def deprecate_bulk(
|
|
222
|
+
self,
|
|
223
|
+
graph: Graph,
|
|
224
|
+
specs: list[DeprecationSpec],
|
|
225
|
+
) -> DeprecationResult:
|
|
226
|
+
"""Mark multiple entities as deprecated.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
graph: Source RDF graph (will be modified in-place).
|
|
230
|
+
specs: List of deprecation specifications.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
DeprecationResult with updated graph.
|
|
234
|
+
"""
|
|
235
|
+
combined_result = DeprecationResult()
|
|
236
|
+
combined_result.source_triples = len(graph)
|
|
237
|
+
|
|
238
|
+
for spec in specs:
|
|
239
|
+
result = self.deprecate(
|
|
240
|
+
graph=graph,
|
|
241
|
+
entity=spec.entity,
|
|
242
|
+
replaced_by=spec.replaced_by,
|
|
243
|
+
message=spec.message,
|
|
244
|
+
version=spec.version,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
# Combine stats
|
|
248
|
+
combined_result.stats.entities_deprecated += result.stats.entities_deprecated
|
|
249
|
+
combined_result.stats.entities_not_found += result.stats.entities_not_found
|
|
250
|
+
combined_result.stats.entities_already_deprecated += (
|
|
251
|
+
result.stats.entities_already_deprecated
|
|
252
|
+
)
|
|
253
|
+
combined_result.stats.triples_added += result.stats.triples_added
|
|
254
|
+
|
|
255
|
+
# Combine entity info
|
|
256
|
+
combined_result.entity_info.extend(result.entity_info)
|
|
257
|
+
|
|
258
|
+
combined_result.deprecated_graph = graph
|
|
259
|
+
combined_result.result_triples = len(graph)
|
|
260
|
+
combined_result.success = True
|
|
261
|
+
|
|
262
|
+
return combined_result
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def deprecate_file(
|
|
266
|
+
source_path: Path,
|
|
267
|
+
output_path: Path,
|
|
268
|
+
specs: list[DeprecationSpec],
|
|
269
|
+
) -> DeprecationResult:
|
|
270
|
+
"""Convenience function to deprecate entities in a file.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
source_path: Path to source RDF file.
|
|
274
|
+
output_path: Path to write updated output.
|
|
275
|
+
specs: List of deprecation specifications.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
DeprecationResult with statistics.
|
|
279
|
+
"""
|
|
280
|
+
# Load source graph
|
|
281
|
+
graph = Graph()
|
|
282
|
+
try:
|
|
283
|
+
graph.parse(source_path.as_posix())
|
|
284
|
+
except Exception as e:
|
|
285
|
+
result = DeprecationResult()
|
|
286
|
+
result.success = False
|
|
287
|
+
result.error = f"Failed to parse {source_path}: {e}"
|
|
288
|
+
return result
|
|
289
|
+
|
|
290
|
+
# Perform deprecation
|
|
291
|
+
deprecator = OntologyDeprecator()
|
|
292
|
+
result = deprecator.deprecate_bulk(graph, specs)
|
|
293
|
+
|
|
294
|
+
if not result.success:
|
|
295
|
+
return result
|
|
296
|
+
|
|
297
|
+
# Write output
|
|
298
|
+
if result.deprecated_graph:
|
|
299
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
300
|
+
result.deprecated_graph.serialize(destination=output_path.as_posix(), format="turtle")
|
|
301
|
+
|
|
302
|
+
return result
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def generate_deprecation_message(
|
|
306
|
+
replaced_by: str | None,
|
|
307
|
+
message: str | None,
|
|
308
|
+
version: str | None,
|
|
309
|
+
) -> str:
|
|
310
|
+
"""Generate a standard deprecation message.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
replaced_by: Replacement entity URI.
|
|
314
|
+
message: Custom message.
|
|
315
|
+
version: Version when deprecated.
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
Formatted deprecation message.
|
|
319
|
+
"""
|
|
320
|
+
if message:
|
|
321
|
+
return message
|
|
322
|
+
|
|
323
|
+
if replaced_by:
|
|
324
|
+
# Extract local name from URI
|
|
325
|
+
local_name = replaced_by.split("#")[-1].split("/")[-1]
|
|
326
|
+
return f"Use {local_name} instead."
|
|
327
|
+
|
|
328
|
+
return "This entity is deprecated and should not be used."
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"""Text formatter for refactor command output.
|
|
2
|
+
|
|
3
|
+
Provides formatted output for dry-run previews and results.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from rdf_construct.refactor.config import RenameConfig, RenameMapping, DeprecationSpec
|
|
9
|
+
from rdf_construct.refactor.renamer import RenameResult, RenameStats
|
|
10
|
+
from rdf_construct.refactor.deprecator import DeprecationResult, EntityDeprecationInfo
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TextFormatter:
|
|
14
|
+
"""Text formatter for refactor results and previews.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
use_colour: Whether to use ANSI colour codes.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, use_colour: bool = True):
|
|
21
|
+
"""Initialize formatter.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
use_colour: Whether to use ANSI colour codes.
|
|
25
|
+
"""
|
|
26
|
+
self.use_colour = use_colour
|
|
27
|
+
|
|
28
|
+
def _colour(self, text: str, colour: str) -> str:
|
|
29
|
+
"""Apply ANSI colour code if enabled.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
text: Text to colour.
|
|
33
|
+
colour: Colour name (green, red, yellow, cyan, bold).
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Coloured text (or original if colour disabled).
|
|
37
|
+
"""
|
|
38
|
+
if not self.use_colour:
|
|
39
|
+
return text
|
|
40
|
+
|
|
41
|
+
codes = {
|
|
42
|
+
"green": "\033[32m",
|
|
43
|
+
"red": "\033[31m",
|
|
44
|
+
"yellow": "\033[33m",
|
|
45
|
+
"cyan": "\033[36m",
|
|
46
|
+
"bold": "\033[1m",
|
|
47
|
+
"dim": "\033[2m",
|
|
48
|
+
"reset": "\033[0m",
|
|
49
|
+
}
|
|
50
|
+
return f"{codes.get(colour, '')}{text}{codes['reset']}"
|
|
51
|
+
|
|
52
|
+
def format_rename_preview(
|
|
53
|
+
self,
|
|
54
|
+
mappings: list[RenameMapping],
|
|
55
|
+
source_file: str,
|
|
56
|
+
source_triples: int,
|
|
57
|
+
literal_mentions: dict[str, int] | None = None,
|
|
58
|
+
) -> str:
|
|
59
|
+
"""Format a dry-run preview for rename operation.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
mappings: List of rename mappings to apply.
|
|
63
|
+
source_file: Name of source file.
|
|
64
|
+
source_triples: Number of triples in source.
|
|
65
|
+
literal_mentions: Count of mentions in literals (won't be changed).
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Formatted preview string.
|
|
69
|
+
"""
|
|
70
|
+
lines = [
|
|
71
|
+
self._colour("Refactoring Preview: Rename", "bold"),
|
|
72
|
+
"=" * 27,
|
|
73
|
+
"",
|
|
74
|
+
f"Source: {source_file} ({source_triples:,} triples)",
|
|
75
|
+
"",
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
# Group by source type
|
|
79
|
+
namespace_mappings = [m for m in mappings if m.source == "namespace"]
|
|
80
|
+
explicit_mappings = [m for m in mappings if m.source == "explicit"]
|
|
81
|
+
|
|
82
|
+
if namespace_mappings:
|
|
83
|
+
# Group by namespace
|
|
84
|
+
namespaces: dict[str, list[RenameMapping]] = {}
|
|
85
|
+
for m in namespace_mappings:
|
|
86
|
+
# Extract namespace prefix
|
|
87
|
+
from_str = str(m.from_uri)
|
|
88
|
+
to_str = str(m.to_uri)
|
|
89
|
+
# Find common prefix
|
|
90
|
+
ns_from = from_str.rsplit("#", 1)[0] + "#" if "#" in from_str else from_str.rsplit("/", 1)[0] + "/"
|
|
91
|
+
ns_to = to_str.rsplit("#", 1)[0] + "#" if "#" in to_str else to_str.rsplit("/", 1)[0] + "/"
|
|
92
|
+
key = f"{ns_from} → {ns_to}"
|
|
93
|
+
if key not in namespaces:
|
|
94
|
+
namespaces[key] = []
|
|
95
|
+
namespaces[key].append(m)
|
|
96
|
+
|
|
97
|
+
lines.append(self._colour("Namespace renames:", "cyan"))
|
|
98
|
+
for ns_change, ns_mappings in namespaces.items():
|
|
99
|
+
lines.append(f" {ns_change}")
|
|
100
|
+
lines.append(f" - {len(ns_mappings)} entities affected")
|
|
101
|
+
lines.append("")
|
|
102
|
+
|
|
103
|
+
if explicit_mappings:
|
|
104
|
+
lines.append(self._colour("Entity renames:", "cyan"))
|
|
105
|
+
for m in explicit_mappings:
|
|
106
|
+
from_local = str(m.from_uri).split("#")[-1].split("/")[-1]
|
|
107
|
+
to_local = str(m.to_uri).split("#")[-1].split("/")[-1]
|
|
108
|
+
lines.append(f" {from_local} → {to_local}")
|
|
109
|
+
|
|
110
|
+
# Check for literal mentions
|
|
111
|
+
if literal_mentions and str(m.from_uri) in literal_mentions:
|
|
112
|
+
count = literal_mentions[str(m.from_uri)]
|
|
113
|
+
lines.append(
|
|
114
|
+
f" └─ {count} literal mention(s) "
|
|
115
|
+
f"{self._colour('(NOT changed)', 'yellow')}"
|
|
116
|
+
)
|
|
117
|
+
lines.append("")
|
|
118
|
+
|
|
119
|
+
# Summary
|
|
120
|
+
lines.append(self._colour("Totals:", "bold"))
|
|
121
|
+
lines.append(f" - {len(mappings)} entities to rename")
|
|
122
|
+
if namespace_mappings:
|
|
123
|
+
lines.append(f" - {len(namespace_mappings)} from namespace rules")
|
|
124
|
+
if explicit_mappings:
|
|
125
|
+
lines.append(f" - {len(explicit_mappings)} from explicit rules")
|
|
126
|
+
lines.append("")
|
|
127
|
+
lines.append(self._colour("Run without --dry-run to apply changes.", "dim"))
|
|
128
|
+
|
|
129
|
+
return "\n".join(lines)
|
|
130
|
+
|
|
131
|
+
def format_rename_result(self, result: RenameResult) -> str:
|
|
132
|
+
"""Format the result of a rename operation.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
result: Result from rename operation.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Formatted result string.
|
|
139
|
+
"""
|
|
140
|
+
if not result.success:
|
|
141
|
+
return self._colour(f"✗ Rename failed: {result.error}", "red")
|
|
142
|
+
|
|
143
|
+
lines = [
|
|
144
|
+
self._colour("Rename Complete", "green"),
|
|
145
|
+
"",
|
|
146
|
+
f" Source triples: {result.source_triples:,}",
|
|
147
|
+
f" Result triples: {result.result_triples:,}",
|
|
148
|
+
"",
|
|
149
|
+
self._colour("Changes:", "cyan"),
|
|
150
|
+
f" - Subjects renamed: {result.stats.subjects_renamed:,}",
|
|
151
|
+
f" - Predicates renamed: {result.stats.predicates_renamed:,}",
|
|
152
|
+
f" - Objects renamed: {result.stats.objects_renamed:,}",
|
|
153
|
+
f" - Total URI substitutions: {result.stats.total_renames:,}",
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
if result.stats.namespace_entities > 0:
|
|
157
|
+
lines.append(f" - Via namespace rules: {result.stats.namespace_entities:,}")
|
|
158
|
+
if result.stats.explicit_entities > 0:
|
|
159
|
+
lines.append(f" - Via explicit rules: {result.stats.explicit_entities:,}")
|
|
160
|
+
|
|
161
|
+
if result.stats.literal_mentions:
|
|
162
|
+
lines.append("")
|
|
163
|
+
lines.append(
|
|
164
|
+
self._colour(
|
|
165
|
+
f" ⚠ {len(result.stats.literal_mentions)} entities mentioned in literals "
|
|
166
|
+
"(not modified)",
|
|
167
|
+
"yellow",
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
return "\n".join(lines)
|
|
172
|
+
|
|
173
|
+
def format_deprecation_preview(
|
|
174
|
+
self,
|
|
175
|
+
specs: list[DeprecationSpec],
|
|
176
|
+
entity_info: list[EntityDeprecationInfo] | None = None,
|
|
177
|
+
source_file: str = "",
|
|
178
|
+
source_triples: int = 0,
|
|
179
|
+
) -> str:
|
|
180
|
+
"""Format a dry-run preview for deprecation operation.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
specs: List of deprecation specifications.
|
|
184
|
+
entity_info: Optional entity information from dry run.
|
|
185
|
+
source_file: Name of source file.
|
|
186
|
+
source_triples: Number of triples in source.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
Formatted preview string.
|
|
190
|
+
"""
|
|
191
|
+
lines = [
|
|
192
|
+
self._colour("Refactoring Preview: Deprecate", "bold"),
|
|
193
|
+
"=" * 30,
|
|
194
|
+
"",
|
|
195
|
+
f"Source: {source_file} ({source_triples:,} triples)" if source_file else "",
|
|
196
|
+
"",
|
|
197
|
+
self._colour("Entities to deprecate:", "cyan"),
|
|
198
|
+
"",
|
|
199
|
+
]
|
|
200
|
+
|
|
201
|
+
for i, spec in enumerate(specs):
|
|
202
|
+
# Extract local name
|
|
203
|
+
local_name = spec.entity.split("#")[-1].split("/")[-1]
|
|
204
|
+
lines.append(f" {self._colour(local_name, 'bold')}")
|
|
205
|
+
|
|
206
|
+
# Show current state if available
|
|
207
|
+
if entity_info and i < len(entity_info):
|
|
208
|
+
info = entity_info[i]
|
|
209
|
+
if not info.found:
|
|
210
|
+
lines.append(f" {self._colour('⚠ Entity not found in graph', 'yellow')}")
|
|
211
|
+
else:
|
|
212
|
+
if info.was_already_deprecated:
|
|
213
|
+
lines.append(f" {self._colour('Already deprecated', 'yellow')}")
|
|
214
|
+
if info.current_labels:
|
|
215
|
+
lines.append(f" rdfs:label: \"{info.current_labels[0]}\"")
|
|
216
|
+
if info.reference_count > 0:
|
|
217
|
+
lines.append(
|
|
218
|
+
f" {self._colour(f'Referenced {info.reference_count} times', 'dim')}"
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# Show what will be added
|
|
222
|
+
lines.append(" Will add:")
|
|
223
|
+
lines.append(f" owl:deprecated {self._colour('true', 'green')}")
|
|
224
|
+
|
|
225
|
+
if spec.replaced_by:
|
|
226
|
+
repl_local = spec.replaced_by.split("#")[-1].split("/")[-1]
|
|
227
|
+
lines.append(f" dcterms:isReplacedBy {self._colour(repl_local, 'cyan')}")
|
|
228
|
+
|
|
229
|
+
if spec.message:
|
|
230
|
+
msg_preview = spec.message[:50] + "..." if len(spec.message) > 50 else spec.message
|
|
231
|
+
lines.append(f" rdfs:comment \"DEPRECATED: {msg_preview}\"")
|
|
232
|
+
|
|
233
|
+
lines.append("")
|
|
234
|
+
|
|
235
|
+
# Summary
|
|
236
|
+
lines.append(self._colour("Summary:", "bold"))
|
|
237
|
+
lines.append(f" - {len(specs)} entities will be marked deprecated")
|
|
238
|
+
with_replacement = len([s for s in specs if s.replaced_by])
|
|
239
|
+
lines.append(f" - {with_replacement} with replacement")
|
|
240
|
+
lines.append(f" - {len(specs) - with_replacement} without replacement")
|
|
241
|
+
lines.append("")
|
|
242
|
+
lines.append(
|
|
243
|
+
self._colour(
|
|
244
|
+
"Note: Deprecation marks entities but does not rename or migrate.",
|
|
245
|
+
"dim",
|
|
246
|
+
)
|
|
247
|
+
)
|
|
248
|
+
lines.append(
|
|
249
|
+
self._colour(
|
|
250
|
+
" Use 'refactor rename' to actually migrate references.",
|
|
251
|
+
"dim",
|
|
252
|
+
)
|
|
253
|
+
)
|
|
254
|
+
lines.append("")
|
|
255
|
+
lines.append(self._colour("Run without --dry-run to apply changes.", "dim"))
|
|
256
|
+
|
|
257
|
+
return "\n".join(lines)
|
|
258
|
+
|
|
259
|
+
def format_deprecation_result(self, result: DeprecationResult) -> str:
|
|
260
|
+
"""Format the result of a deprecation operation.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
result: Result from deprecation operation.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
Formatted result string.
|
|
267
|
+
"""
|
|
268
|
+
if not result.success:
|
|
269
|
+
return self._colour(f"✗ Deprecation failed: {result.error}", "red")
|
|
270
|
+
|
|
271
|
+
lines = [
|
|
272
|
+
self._colour("Deprecation Complete", "green"),
|
|
273
|
+
"",
|
|
274
|
+
f" Source triples: {result.source_triples:,}",
|
|
275
|
+
f" Result triples: {result.result_triples:,}",
|
|
276
|
+
"",
|
|
277
|
+
self._colour("Changes:", "cyan"),
|
|
278
|
+
f" - Entities deprecated: {result.stats.entities_deprecated}",
|
|
279
|
+
f" - Triples added: {result.stats.triples_added}",
|
|
280
|
+
]
|
|
281
|
+
|
|
282
|
+
if result.stats.entities_not_found > 0:
|
|
283
|
+
lines.append(
|
|
284
|
+
self._colour(
|
|
285
|
+
f" ⚠ Entities not found: {result.stats.entities_not_found}",
|
|
286
|
+
"yellow",
|
|
287
|
+
)
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
if result.stats.entities_already_deprecated > 0:
|
|
291
|
+
lines.append(
|
|
292
|
+
f" - Already deprecated: {result.stats.entities_already_deprecated}"
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
# Show details of deprecated entities
|
|
296
|
+
if result.entity_info:
|
|
297
|
+
lines.append("")
|
|
298
|
+
lines.append("Details:")
|
|
299
|
+
for info in result.entity_info:
|
|
300
|
+
local_name = info.uri.split("#")[-1].split("/")[-1]
|
|
301
|
+
if not info.found:
|
|
302
|
+
lines.append(f" {self._colour('⚠', 'yellow')} {local_name} - not found")
|
|
303
|
+
elif info.was_already_deprecated and info.triples_added == 0:
|
|
304
|
+
lines.append(f" ○ {local_name} - already deprecated, no changes")
|
|
305
|
+
else:
|
|
306
|
+
lines.append(f" {self._colour('✓', 'green')} {local_name}")
|
|
307
|
+
if info.replaced_by:
|
|
308
|
+
repl_local = info.replaced_by.split("#")[-1].split("/")[-1]
|
|
309
|
+
lines.append(f" → replaced by {repl_local}")
|
|
310
|
+
|
|
311
|
+
return "\n".join(lines)
|