ara-cli 0.1.9.50__py3-none-any.whl → 0.1.9.52__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.
- ara_cli/__main__.py +3 -1
- ara_cli/analyse_artefacts.py +133 -0
- ara_cli/ara_command_action.py +91 -76
- ara_cli/ara_command_parser.py +5 -0
- ara_cli/ara_config.py +65 -38
- ara_cli/artefact_creator.py +3 -4
- ara_cli/artefact_lister.py +57 -48
- ara_cli/artefact_models/artefact_model.py +28 -14
- ara_cli/artefact_models/feature_artefact_model.py +4 -1
- ara_cli/artefact_reader.py +104 -63
- ara_cli/artefact_renamer.py +8 -8
- ara_cli/artefact_scan.py +46 -0
- ara_cli/file_classifier.py +10 -32
- ara_cli/prompt_extractor.py +10 -2
- ara_cli/tag_extractor.py +6 -16
- ara_cli/templates/specification_breakdown_files/template.concept.md +12 -14
- ara_cli/tests/test_ara_command_action.py +242 -108
- ara_cli/tests/test_artefact_lister.py +598 -171
- ara_cli/tests/test_artefact_reader.py +7 -76
- ara_cli/tests/test_artefact_scan.py +126 -0
- ara_cli/tests/test_file_classifier.py +69 -30
- ara_cli/tests/test_tag_extractor.py +42 -61
- ara_cli/version.py +1 -1
- {ara_cli-0.1.9.50.dist-info → ara_cli-0.1.9.52.dist-info}/METADATA +1 -1
- {ara_cli-0.1.9.50.dist-info → ara_cli-0.1.9.52.dist-info}/RECORD +28 -27
- ara_cli/artefact.py +0 -172
- ara_cli/tests/test_artefact.py +0 -304
- {ara_cli-0.1.9.50.dist-info → ara_cli-0.1.9.52.dist-info}/WHEEL +0 -0
- {ara_cli-0.1.9.50.dist-info → ara_cli-0.1.9.52.dist-info}/entry_points.txt +0 -0
- {ara_cli-0.1.9.50.dist-info → ara_cli-0.1.9.52.dist-info}/top_level.txt +0 -0
ara_cli/artefact_lister.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
from ara_cli.file_classifier import FileClassifier
|
|
2
|
-
from ara_cli.template_manager import DirectoryNavigator
|
|
3
2
|
from ara_cli.artefact_reader import ArtefactReader
|
|
4
3
|
from ara_cli.file_lister import list_files_in_directory
|
|
5
|
-
from ara_cli.
|
|
4
|
+
from ara_cli.artefact_models.artefact_model import Artefact
|
|
6
5
|
from ara_cli.list_filter import ListFilter, filter_list
|
|
7
6
|
from ara_cli.artefact_fuzzy_search import suggest_close_name_matches
|
|
8
7
|
import os
|
|
@@ -13,16 +12,26 @@ class ArtefactLister:
|
|
|
13
12
|
self.file_system = file_system or os
|
|
14
13
|
|
|
15
14
|
@staticmethod
|
|
16
|
-
def artefact_content_retrieval(artefact):
|
|
17
|
-
|
|
15
|
+
def artefact_content_retrieval(artefact: Artefact):
|
|
16
|
+
content = artefact.serialize()
|
|
17
|
+
return content
|
|
18
18
|
|
|
19
19
|
@staticmethod
|
|
20
|
-
def artefact_path_retrieval(artefact):
|
|
20
|
+
def artefact_path_retrieval(artefact: Artefact):
|
|
21
21
|
return artefact.file_path
|
|
22
22
|
|
|
23
23
|
@staticmethod
|
|
24
|
-
def artefact_tags_retrieval(artefact):
|
|
25
|
-
|
|
24
|
+
def artefact_tags_retrieval(artefact: Artefact):
|
|
25
|
+
final_tags = []
|
|
26
|
+
|
|
27
|
+
if not artefact:
|
|
28
|
+
return []
|
|
29
|
+
|
|
30
|
+
final_tags.extend([f"user_{user}" for user in artefact.users])
|
|
31
|
+
final_tags.append(artefact.status)
|
|
32
|
+
final_tags.extend(artefact.tags)
|
|
33
|
+
|
|
34
|
+
return final_tags
|
|
26
35
|
|
|
27
36
|
def filter_artefacts(self, classified_files: list, list_filter: ListFilter):
|
|
28
37
|
filtered_list = filter_list(
|
|
@@ -40,17 +49,15 @@ class ArtefactLister:
|
|
|
40
49
|
navigate_to_target=False,
|
|
41
50
|
list_filter: ListFilter | None = None
|
|
42
51
|
):
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if navigate_to_target:
|
|
46
|
-
navigator.navigate_to_target()
|
|
52
|
+
artefact_list = ArtefactReader.read_artefacts(tags=tags)
|
|
53
|
+
artefact_list = self.filter_artefacts(artefact_list, list_filter)
|
|
47
54
|
|
|
55
|
+
filtered_artefact_list = {
|
|
56
|
+
key: [artefact for artefact in value if artefact is not None]
|
|
57
|
+
for key, value in artefact_list.items()
|
|
58
|
+
}
|
|
48
59
|
file_classifier = FileClassifier(self.file_system)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
classified_files = self.filter_artefacts(classified_files, list_filter)
|
|
52
|
-
|
|
53
|
-
file_classifier.print_classified_files(classified_files)
|
|
60
|
+
file_classifier.print_classified_files(filtered_artefact_list)
|
|
54
61
|
|
|
55
62
|
def list_branch(
|
|
56
63
|
self,
|
|
@@ -59,21 +66,23 @@ class ArtefactLister:
|
|
|
59
66
|
list_filter: ListFilter | None = None
|
|
60
67
|
):
|
|
61
68
|
file_classifier = FileClassifier(os)
|
|
62
|
-
|
|
63
69
|
classified_artefacts = file_classifier.classify_files()
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
70
|
+
artefact_info = classified_artefacts.get(classifier, [])
|
|
71
|
+
matching_artefact_info = [p for p in artefact_info if p["title"] == artefact_name]
|
|
72
|
+
|
|
73
|
+
if not matching_artefact_info:
|
|
74
|
+
suggest_close_name_matches(
|
|
75
|
+
artefact_name,
|
|
76
|
+
[info["title"] for info in artefact_info]
|
|
77
|
+
)
|
|
68
78
|
|
|
69
79
|
artefacts_by_classifier = {classifier: []}
|
|
70
80
|
ArtefactReader.step_through_value_chain(
|
|
71
81
|
artefact_name=artefact_name,
|
|
72
82
|
classifier=classifier,
|
|
73
|
-
artefacts_by_classifier=artefacts_by_classifier
|
|
83
|
+
artefacts_by_classifier=artefacts_by_classifier,
|
|
74
84
|
)
|
|
75
85
|
artefacts_by_classifier = self.filter_artefacts(artefacts_by_classifier, list_filter)
|
|
76
|
-
|
|
77
86
|
file_classifier.print_classified_files(artefacts_by_classifier)
|
|
78
87
|
|
|
79
88
|
def list_children(
|
|
@@ -83,22 +92,24 @@ class ArtefactLister:
|
|
|
83
92
|
list_filter: ListFilter | None = None
|
|
84
93
|
):
|
|
85
94
|
file_classifier = FileClassifier(os)
|
|
86
|
-
|
|
87
95
|
classified_artefacts = file_classifier.classify_files()
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
96
|
+
artefact_info = classified_artefacts.get(classifier, [])
|
|
97
|
+
matching_artefact_info = [p for p in artefact_info if p["title"] == artefact_name]
|
|
98
|
+
|
|
99
|
+
if not matching_artefact_info:
|
|
100
|
+
suggest_close_name_matches(
|
|
101
|
+
artefact_name,
|
|
102
|
+
[info["title"] for info in artefact_info]
|
|
103
|
+
)
|
|
92
104
|
|
|
93
105
|
child_artefacts = ArtefactReader.find_children(
|
|
94
|
-
|
|
95
|
-
|
|
106
|
+
artefact_name=artefact_name,
|
|
107
|
+
classifier=classifier
|
|
96
108
|
)
|
|
109
|
+
|
|
97
110
|
child_artefacts = self.filter_artefacts(child_artefacts, list_filter)
|
|
98
111
|
|
|
99
|
-
file_classifier.print_classified_files(
|
|
100
|
-
files_by_classifier=child_artefacts
|
|
101
|
-
)
|
|
112
|
+
file_classifier.print_classified_files(child_artefacts)
|
|
102
113
|
|
|
103
114
|
def list_data(
|
|
104
115
|
self,
|
|
@@ -107,21 +118,19 @@ class ArtefactLister:
|
|
|
107
118
|
list_filter: ListFilter | None = None
|
|
108
119
|
):
|
|
109
120
|
file_classifier = FileClassifier(os)
|
|
121
|
+
classified_artefact_info = file_classifier.classify_files()
|
|
122
|
+
artefact_info_dict = classified_artefact_info.get(classifier, [])
|
|
110
123
|
|
|
111
|
-
|
|
112
|
-
all_artefact_names = [artefact.file_name for artefact in classified_artefacts.get(classifier, [])]
|
|
113
|
-
if artefact_name not in all_artefact_names:
|
|
114
|
-
suggest_close_name_matches(artefact_name, all_artefact_names)
|
|
115
|
-
return
|
|
116
|
-
|
|
117
|
-
content, file_path = ArtefactReader.read_artefact(
|
|
118
|
-
classifier=classifier,
|
|
119
|
-
artefact_name=artefact_name
|
|
120
|
-
)
|
|
124
|
+
matching_info = [info for info in artefact_info_dict if info["title"] == artefact_name]
|
|
121
125
|
|
|
122
|
-
|
|
123
|
-
|
|
126
|
+
if not matching_info:
|
|
127
|
+
suggest_close_name_matches(
|
|
128
|
+
artefact_name,
|
|
129
|
+
[info["title"] for info in artefact_info_dict]
|
|
130
|
+
)
|
|
131
|
+
return
|
|
124
132
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
133
|
+
artefact_info = matching_info[0]
|
|
134
|
+
data_dir = os.path.splitext(artefact_info["file_path"])[0] + '.data'
|
|
135
|
+
if os.path.exists(data_dir):
|
|
136
|
+
list_files_in_directory(data_dir, list_filter)
|
|
@@ -11,6 +11,14 @@ import string
|
|
|
11
11
|
ALLOWED_STATUS_VALUES = ("to-do", "in-progress", "review", "done", "closed")
|
|
12
12
|
|
|
13
13
|
|
|
14
|
+
def replace_space_with_underscore(input: string):
|
|
15
|
+
return input.replace(' ', '_')
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def replace_underscore_with_space(input: string):
|
|
19
|
+
return input.replace('_', ' ')
|
|
20
|
+
|
|
21
|
+
|
|
14
22
|
class ArtefactType(str, Enum):
|
|
15
23
|
vision = "vision"
|
|
16
24
|
businessgoal = "businessgoal"
|
|
@@ -44,6 +52,12 @@ class Contribution(BaseModel):
|
|
|
44
52
|
classifier = self.classifier
|
|
45
53
|
rule = self.rule
|
|
46
54
|
|
|
55
|
+
if artefact_name:
|
|
56
|
+
artefact_name = replace_space_with_underscore(artefact_name)
|
|
57
|
+
if artefact_name and classifier:
|
|
58
|
+
artefact_name = artefact_name.removesuffix(f"_{classifier}")
|
|
59
|
+
artefact_name = artefact_name.removesuffix(f"_{classifier.capitalize()}")
|
|
60
|
+
self.artefact_name = artefact_name
|
|
47
61
|
if not artefact_name or not classifier:
|
|
48
62
|
self.artefact_name = None
|
|
49
63
|
self.classifier = None
|
|
@@ -60,7 +74,7 @@ class Contribution(BaseModel):
|
|
|
60
74
|
return value
|
|
61
75
|
if ' ' in value:
|
|
62
76
|
warnings.warn(message="artefact_name can not contain spaces. Replacing spaces with '_'")
|
|
63
|
-
value = value
|
|
77
|
+
value = replace_space_with_underscore(value)
|
|
64
78
|
return value
|
|
65
79
|
|
|
66
80
|
@field_validator('classifier', mode='after')
|
|
@@ -90,7 +104,7 @@ class Contribution(BaseModel):
|
|
|
90
104
|
rule = rule_text
|
|
91
105
|
parent_text_list = parent_text.split(' ')
|
|
92
106
|
classifier = parent_text_list[-1].lower()
|
|
93
|
-
artefact_name = '_'.join(parent_text_list[:-1])
|
|
107
|
+
artefact_name = '_'.join([s for s in parent_text_list if s][:-1])
|
|
94
108
|
|
|
95
109
|
return cls(
|
|
96
110
|
artefact_name=artefact_name,
|
|
@@ -102,7 +116,7 @@ class Contribution(BaseModel):
|
|
|
102
116
|
if not self.classifier or not self.artefact_name:
|
|
103
117
|
return ""
|
|
104
118
|
artefact_type = Classifier.get_artefact_title(self.classifier)
|
|
105
|
-
artefact_name = self.artefact_name
|
|
119
|
+
artefact_name = replace_underscore_with_space(self.artefact_name)
|
|
106
120
|
contribution = f"{artefact_name} {artefact_type}"
|
|
107
121
|
if self.rule:
|
|
108
122
|
contribution = f"{contribution} using rule {self.rule}"
|
|
@@ -136,6 +150,7 @@ class Artefact(BaseModel, ABC):
|
|
|
136
150
|
"validate_assignment": True
|
|
137
151
|
}
|
|
138
152
|
|
|
153
|
+
_file_path: Optional[str] = None
|
|
139
154
|
artefact_type: ArtefactType = Field(
|
|
140
155
|
...,
|
|
141
156
|
description=f"Artefact classifier (mandatory). Allowed classifiers are {', '.join(ArtefactType)}"
|
|
@@ -166,6 +181,13 @@ class Artefact(BaseModel, ABC):
|
|
|
166
181
|
description="Optional further description to understand the artefact. It is strongly recommended to add a description to every artefact."
|
|
167
182
|
)
|
|
168
183
|
|
|
184
|
+
@property
|
|
185
|
+
def file_path(self) -> str:
|
|
186
|
+
if self._file_path is not None:
|
|
187
|
+
return self._file_path
|
|
188
|
+
sub_dir = Classifier.get_sub_directory(self.artefact_type)
|
|
189
|
+
return f"{sub_dir}/{self.title}.{self.artefact_type}"
|
|
190
|
+
|
|
169
191
|
@field_validator('artefact_type')
|
|
170
192
|
def validate_artefact_type(cls, v):
|
|
171
193
|
if not isinstance(v, ArtefactType):
|
|
@@ -199,9 +221,7 @@ class Artefact(BaseModel, ABC):
|
|
|
199
221
|
def validate_title(cls, v):
|
|
200
222
|
if not v.strip():
|
|
201
223
|
raise ValueError("artefact_title must not be empty")
|
|
202
|
-
|
|
203
|
-
warnings.warn(message="artefact_name can not contain spaces. Replacing spaces with '_'")
|
|
204
|
-
v = v.replace(' ', '_')
|
|
224
|
+
v = replace_space_with_underscore(v)
|
|
205
225
|
|
|
206
226
|
letters = list(string.ascii_letters)
|
|
207
227
|
digits = list(string.digits)
|
|
@@ -230,12 +250,6 @@ class Artefact(BaseModel, ABC):
|
|
|
230
250
|
def _artefact_type(cls) -> ArtefactType: # pragma: no cover
|
|
231
251
|
pass
|
|
232
252
|
|
|
233
|
-
def file_location(self) -> str:
|
|
234
|
-
artefact_type = self.artefact_type
|
|
235
|
-
sub_directory = Classifier.get_sub_directory(artefact_type)
|
|
236
|
-
file_path = f"ara/{sub_directory}/{self.title}.{artefact_type}"
|
|
237
|
-
return file_path
|
|
238
|
-
|
|
239
253
|
@classmethod
|
|
240
254
|
def _deserialize_tags(cls, lines) -> (Dict[str, str], List[str]):
|
|
241
255
|
assert len(lines) > 0, "Empty lines given, can't extract tags"
|
|
@@ -279,7 +293,7 @@ class Artefact(BaseModel, ABC):
|
|
|
279
293
|
raise ValueError(
|
|
280
294
|
f"No title found in {cls._artefact_type()}. Expected '{title_prefix}' as start of the title in line '{title_line}'")
|
|
281
295
|
title = title_line[len(title_prefix):].strip()
|
|
282
|
-
title = title
|
|
296
|
+
title = replace_space_with_underscore(title)
|
|
283
297
|
return title, lines
|
|
284
298
|
|
|
285
299
|
@classmethod
|
|
@@ -349,7 +363,7 @@ class Artefact(BaseModel, ABC):
|
|
|
349
363
|
pass
|
|
350
364
|
|
|
351
365
|
def _serialize_title(self) -> str:
|
|
352
|
-
title = self.title
|
|
366
|
+
title = replace_underscore_with_space(self.title)
|
|
353
367
|
return f"{self._title_prefix()} {title}"
|
|
354
368
|
|
|
355
369
|
def _serialize_tags(self) -> (Optional[str]):
|
|
@@ -85,7 +85,7 @@ class Example(BaseModel):
|
|
|
85
85
|
def from_row(cls, headers: List[str], row: List[str]) -> 'Example':
|
|
86
86
|
if len(row) != len(headers):
|
|
87
87
|
raise ValueError(
|
|
88
|
-
f"Row has {len(row)} cells, but expected {len(headers)}")
|
|
88
|
+
f"Row has {len(row)} cells, but expected {len(headers)}.\nFound row: {row}")
|
|
89
89
|
values = {header: value.strip() for header, value in zip(headers, row)}
|
|
90
90
|
return cls(values=values)
|
|
91
91
|
|
|
@@ -175,6 +175,7 @@ class ScenarioOutline(BaseModel):
|
|
|
175
175
|
@classmethod
|
|
176
176
|
def from_lines(cls, lines: List[str], start_idx: int) -> Tuple['ScenarioOutline', int]:
|
|
177
177
|
"""Parse a ScenarioOutline from a list of lines starting at start_idx."""
|
|
178
|
+
|
|
178
179
|
if not lines[start_idx].startswith('Scenario Outline:'):
|
|
179
180
|
raise ValueError("Expected 'Scenario Outline:' at start index")
|
|
180
181
|
title = lines[start_idx][len('Scenario Outline:'):].strip()
|
|
@@ -190,6 +191,8 @@ class ScenarioOutline(BaseModel):
|
|
|
190
191
|
headers = [h.strip() for h in lines[idx].split('|') if h.strip()]
|
|
191
192
|
idx += 1
|
|
192
193
|
while idx < len(lines) and lines[idx].strip():
|
|
194
|
+
if lines[idx].strip().startswith("Scenario:") or lines[idx].strip().startswith("Scenario Outline:"):
|
|
195
|
+
break
|
|
193
196
|
row = [cell.strip()
|
|
194
197
|
for cell in lines[idx].split('|') if cell.strip()]
|
|
195
198
|
example = Example.from_row(headers, row)
|
ara_cli/artefact_reader.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
from ara_cli.directory_navigator import DirectoryNavigator
|
|
2
1
|
from ara_cli.classifier import Classifier
|
|
3
2
|
from ara_cli.file_classifier import FileClassifier
|
|
4
|
-
from ara_cli.
|
|
5
|
-
from ara_cli.artefact_fuzzy_search import
|
|
3
|
+
from ara_cli.artefact_models.artefact_model import Artefact
|
|
4
|
+
from ara_cli.artefact_fuzzy_search import suggest_close_name_matches_for_parent, suggest_close_name_matches
|
|
5
|
+
from typing import Dict, List
|
|
6
6
|
import os
|
|
7
7
|
import re
|
|
8
8
|
|
|
@@ -10,31 +10,29 @@ import re
|
|
|
10
10
|
class ArtefactReader:
|
|
11
11
|
@staticmethod
|
|
12
12
|
def read_artefact(artefact_name, classifier):
|
|
13
|
-
original_directory = os.getcwd()
|
|
14
|
-
navigator = DirectoryNavigator()
|
|
15
|
-
navigator.navigate_to_target()
|
|
16
|
-
|
|
17
13
|
if not Classifier.is_valid_classifier(classifier):
|
|
18
14
|
print("Invalid classifier provided. Please provide a valid classifier.")
|
|
19
|
-
os.chdir(original_directory)
|
|
20
15
|
return None, None
|
|
21
16
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
file_exists = os.path.exists(file_path)
|
|
26
|
-
|
|
27
|
-
if not file_exists:
|
|
28
|
-
print(f"File \"{file_path}\" not found")
|
|
29
|
-
os.chdir(original_directory)
|
|
30
|
-
return None, None
|
|
17
|
+
file_classifier = FileClassifier(os)
|
|
18
|
+
classified_file_info = file_classifier.classify_files()
|
|
19
|
+
artefact_info_of_classifier = classified_file_info.get(classifier, [])
|
|
31
20
|
|
|
32
|
-
|
|
33
|
-
|
|
21
|
+
for artefact_info in artefact_info_of_classifier:
|
|
22
|
+
file_path = artefact_info["file_path"]
|
|
23
|
+
artefact_title = artefact_info["title"]
|
|
24
|
+
if artefact_title == artefact_name:
|
|
25
|
+
with open(file_path, 'r') as file:
|
|
26
|
+
content = file.read()
|
|
27
|
+
return content, artefact_info
|
|
34
28
|
|
|
35
|
-
|
|
29
|
+
all_artefact_names = [info["title"] for info in artefact_info_of_classifier]
|
|
30
|
+
suggest_close_name_matches(
|
|
31
|
+
artefact_name,
|
|
32
|
+
all_artefact_names
|
|
33
|
+
)
|
|
36
34
|
|
|
37
|
-
return
|
|
35
|
+
return None, None
|
|
38
36
|
|
|
39
37
|
@staticmethod
|
|
40
38
|
def extract_parent_tree(artefact_content):
|
|
@@ -53,41 +51,77 @@ class ArtefactReader:
|
|
|
53
51
|
return parent_name, parent_type
|
|
54
52
|
|
|
55
53
|
@staticmethod
|
|
56
|
-
def
|
|
54
|
+
def merge_dicts(dict1, dict2):
|
|
55
|
+
from collections import defaultdict
|
|
57
56
|
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
merged = defaultdict(list)
|
|
58
|
+
for d in [dict1, dict2]:
|
|
59
|
+
for key, artefacts in d.items():
|
|
60
|
+
merged[key].extend(artefacts)
|
|
61
|
+
return dict(merged)
|
|
60
62
|
|
|
61
|
-
|
|
63
|
+
@staticmethod
|
|
64
|
+
def read_artefacts(classified_artefacts=None, file_system=os, tags=None) -> Dict[str, List[Artefact]]:
|
|
65
|
+
from ara_cli.artefact_models.artefact_load import artefact_from_content
|
|
62
66
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
if classified_artefacts is None:
|
|
68
|
+
file_classifier = FileClassifier(file_system)
|
|
69
|
+
classified_artefacts = file_classifier.classify_files()
|
|
66
70
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
71
|
+
artefacts = {artefact_type: []
|
|
72
|
+
for artefact_type in classified_artefacts.keys()}
|
|
73
|
+
for artefact_type, artefact_info_dicts in classified_artefacts.items():
|
|
74
|
+
for artefact_info in artefact_info_dicts:
|
|
75
|
+
try:
|
|
76
|
+
with open(artefact_info["file_path"], 'r') as file:
|
|
77
|
+
content = file.read()
|
|
78
|
+
artefact = artefact_from_content(content)
|
|
79
|
+
if not artefact:
|
|
80
|
+
continue
|
|
81
|
+
# Store the full file path in the artefact
|
|
82
|
+
artefact._file_path = artefact_info["file_path"]
|
|
83
|
+
artefacts[artefact_type].append(artefact)
|
|
84
|
+
# else:
|
|
85
|
+
# Include file path if deserialization fails
|
|
86
|
+
# FIXME: LOOK INTO IT
|
|
87
|
+
# artefacts[artefact_type].append(file_path)
|
|
88
|
+
except Exception:
|
|
89
|
+
# TODO: catch only specific exceptions
|
|
90
|
+
# TODO: implament error message for deserialization or "ara scan" or "ara autofix"
|
|
91
|
+
# print(f"Warning: Could not deserialize artefact at {artefact_info}: {e}")
|
|
92
|
+
# artefacts[artefact_type].append(file_path)
|
|
93
|
+
continue
|
|
94
|
+
return artefacts
|
|
70
95
|
|
|
71
|
-
|
|
96
|
+
@staticmethod
|
|
97
|
+
def find_children(artefact_name, classifier, artefacts_by_classifier={}, classified_artefacts=None):
|
|
98
|
+
artefacts_by_classifier = artefacts_by_classifier or {}
|
|
99
|
+
filtered_artefacts = {k: [] for k in artefacts_by_classifier.keys()}
|
|
72
100
|
|
|
73
101
|
if classified_artefacts is None:
|
|
74
|
-
|
|
75
|
-
classified_artefacts = file_classifier.classify_files()
|
|
102
|
+
classified_artefacts = ArtefactReader.read_artefacts()
|
|
76
103
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if artefact.parent and
|
|
82
|
-
artefact.parent.name == artefact_name and
|
|
83
|
-
artefact.parent.classifier == classifier
|
|
84
|
-
]
|
|
85
|
-
if filtered_list:
|
|
86
|
-
filtered_artefacts[key] = filtered_list
|
|
104
|
+
for artefact_classifier, artefacts in classified_artefacts.items():
|
|
105
|
+
for artefact in artefacts:
|
|
106
|
+
if not isinstance(artefact, Artefact):
|
|
107
|
+
continue
|
|
87
108
|
|
|
88
|
-
|
|
109
|
+
try:
|
|
110
|
+
contribution = artefact.contribution
|
|
111
|
+
if (contribution and
|
|
112
|
+
contribution.artefact_name == artefact_name and
|
|
113
|
+
contribution.classifier == classifier):
|
|
89
114
|
|
|
90
|
-
|
|
115
|
+
file_classifier = artefact._file_path.split('.')[-1]
|
|
116
|
+
|
|
117
|
+
if file_classifier not in filtered_artefacts:
|
|
118
|
+
filtered_artefacts[file_classifier] = []
|
|
119
|
+
filtered_artefacts[file_classifier].append(artefact)
|
|
120
|
+
|
|
121
|
+
except AttributeError as e:
|
|
122
|
+
continue
|
|
123
|
+
|
|
124
|
+
return ArtefactReader.merge_dicts(artefacts_by_classifier, filtered_artefacts)
|
|
91
125
|
|
|
92
126
|
@staticmethod
|
|
93
127
|
def step_through_value_chain(
|
|
@@ -95,34 +129,41 @@ class ArtefactReader:
|
|
|
95
129
|
classifier,
|
|
96
130
|
artefacts_by_classifier={},
|
|
97
131
|
classified_artefacts: dict[str, list[Artefact]] | None = None
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
file_classifier = FileClassifier(os)
|
|
101
|
-
classified_artefacts = file_classifier.classify_files()
|
|
102
|
-
|
|
103
|
-
content, file_path = ArtefactReader.read_artefact(artefact_name, classifier)
|
|
104
|
-
|
|
105
|
-
artefact = Artefact.from_content(content)
|
|
106
|
-
artefact_path = next((classified_artefact.file_path for classified_artefact in classified_artefacts.get(classifier, []) if classified_artefact.name == artefact.name), artefact.file_path)
|
|
107
|
-
artefact._file_path = artefact_path
|
|
132
|
+
):
|
|
133
|
+
from ara_cli.artefact_models.artefact_load import artefact_from_content
|
|
108
134
|
|
|
135
|
+
if classified_artefacts is None:
|
|
136
|
+
classified_artefacts = ArtefactReader.read_artefacts()
|
|
109
137
|
|
|
110
138
|
if classifier not in artefacts_by_classifier:
|
|
111
139
|
artefacts_by_classifier[classifier] = []
|
|
112
140
|
|
|
113
|
-
|
|
114
|
-
|
|
141
|
+
artefact = next(filter(
|
|
142
|
+
lambda x: x.title == artefact_name, classified_artefacts[classifier]
|
|
143
|
+
))
|
|
144
|
+
|
|
145
|
+
if not artefact:
|
|
146
|
+
return
|
|
147
|
+
if artefact in artefacts_by_classifier[classifier]:
|
|
115
148
|
return
|
|
116
149
|
|
|
117
150
|
artefacts_by_classifier[classifier].append(artefact)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
151
|
+
|
|
152
|
+
parent = artefact.contribution
|
|
153
|
+
if parent and parent.artefact_name and parent.classifier:
|
|
154
|
+
parent_name = parent.artefact_name
|
|
121
155
|
parent_classifier = parent.classifier
|
|
122
156
|
|
|
123
|
-
|
|
157
|
+
parent_classifier_artefacts = classified_artefacts[parent_classifier]
|
|
158
|
+
all_artefact_names = [x.title for x in parent_classifier_artefacts]
|
|
159
|
+
|
|
124
160
|
if parent_name not in all_artefact_names:
|
|
125
|
-
|
|
161
|
+
if parent_name is not None:
|
|
162
|
+
suggest_close_name_matches_for_parent(
|
|
163
|
+
artefact_name,
|
|
164
|
+
all_artefact_names,
|
|
165
|
+
parent_name
|
|
166
|
+
)
|
|
126
167
|
print()
|
|
127
168
|
return
|
|
128
169
|
|
ara_cli/artefact_renamer.py
CHANGED
|
@@ -12,16 +12,17 @@ class ArtefactRenamer:
|
|
|
12
12
|
self.link_updater = ArtefactLinkUpdater()
|
|
13
13
|
|
|
14
14
|
@lru_cache(maxsize=None)
|
|
15
|
-
def navigate_to_target(self):
|
|
15
|
+
def navigate_to_target(self) -> str:
|
|
16
16
|
navigator = DirectoryNavigator()
|
|
17
|
-
navigator.navigate_to_target()
|
|
17
|
+
original_directory = navigator.navigate_to_target()
|
|
18
|
+
return original_directory
|
|
18
19
|
|
|
19
20
|
@lru_cache(maxsize=None)
|
|
20
21
|
def compile_pattern(self, pattern):
|
|
21
22
|
return re.compile(pattern)
|
|
22
23
|
|
|
23
24
|
def rename(self, old_name, new_name, classifier):
|
|
24
|
-
self.navigate_to_target()
|
|
25
|
+
original_directory = self.navigate_to_target()
|
|
25
26
|
|
|
26
27
|
if not new_name:
|
|
27
28
|
raise ValueError("New name must be provided for renaming.")
|
|
@@ -50,13 +51,10 @@ class ArtefactRenamer:
|
|
|
50
51
|
|
|
51
52
|
# Perform the renaming of the file and directory
|
|
52
53
|
self.file_system.rename(old_file_path, new_file_path)
|
|
54
|
+
print(f"Renamed file: {old_file_path} to {new_file_path}")
|
|
53
55
|
if old_dir_exists:
|
|
54
56
|
self.file_system.rename(old_dir_path, new_dir_path)
|
|
55
|
-
|
|
56
|
-
os.makedirs(new_dir_path, exist_ok=True)
|
|
57
|
-
|
|
58
|
-
print(f"Renamed file: {old_file_path} to {new_file_path}")
|
|
59
|
-
print(f"Renamed directory: {old_dir_path} to {new_dir_path}")
|
|
57
|
+
print(f"Renamed directory: {old_dir_path} to {new_dir_path}")
|
|
60
58
|
|
|
61
59
|
# Update the title within the artefact file
|
|
62
60
|
self._update_title_in_artefact(new_file_path, new_name, classifier)
|
|
@@ -64,6 +62,8 @@ class ArtefactRenamer:
|
|
|
64
62
|
# Update links in related artefact files
|
|
65
63
|
self.link_updater.update_links_in_related_artefacts(old_name, new_name)
|
|
66
64
|
|
|
65
|
+
os.chdir(original_directory)
|
|
66
|
+
|
|
67
67
|
def _update_title_in_artefact(self, artefact_path, new_title, classifier):
|
|
68
68
|
# Format the new title: replace underscores with spaces
|
|
69
69
|
formatted_new_title = new_title.replace('_', ' ')
|
ara_cli/artefact_scan.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
def check_file(file_path, artefact_class):
|
|
2
|
+
from pydantic import ValidationError
|
|
3
|
+
try:
|
|
4
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
5
|
+
content = f.read()
|
|
6
|
+
except OSError as e:
|
|
7
|
+
return False, f"File error: {e}"
|
|
8
|
+
try:
|
|
9
|
+
artefact_class.deserialize(content)
|
|
10
|
+
return True, None
|
|
11
|
+
except (ValidationError, ValueError, AssertionError) as e:
|
|
12
|
+
return False, str(e)
|
|
13
|
+
except Exception as e:
|
|
14
|
+
return False, f"Unexpected error: {e!r}"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def find_invalid_files(classified_artefact_info, classifier):
|
|
18
|
+
from ara_cli.artefact_models.artefact_mapping import artefact_type_mapping
|
|
19
|
+
|
|
20
|
+
artefact_class = artefact_type_mapping[classifier]
|
|
21
|
+
invalid_files = []
|
|
22
|
+
for artefact_info in classified_artefact_info[classifier]:
|
|
23
|
+
if "templates/" in artefact_info["file_path"]:
|
|
24
|
+
continue
|
|
25
|
+
is_valid, reason = check_file(artefact_info["file_path"], artefact_class)
|
|
26
|
+
if not is_valid:
|
|
27
|
+
invalid_files.append((artefact_info["file_path"], reason))
|
|
28
|
+
return invalid_files
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def show_results(invalid_artefacts):
|
|
32
|
+
has_issues = False
|
|
33
|
+
with open("incompatible_artefacts_report.md", "w") as report:
|
|
34
|
+
report.write("# Artefact Check Report\n\n")
|
|
35
|
+
for classifier, files in invalid_artefacts.items():
|
|
36
|
+
if files:
|
|
37
|
+
has_issues = True
|
|
38
|
+
print(f"\nIncompatible {classifier} Files:")
|
|
39
|
+
report.write(f"## {classifier}\n")
|
|
40
|
+
for file, reason in files:
|
|
41
|
+
print(f"\t- {file}")
|
|
42
|
+
report.write(f"- `{file}`: {reason}\n")
|
|
43
|
+
report.write("\n")
|
|
44
|
+
if not has_issues:
|
|
45
|
+
print("All files are good!")
|
|
46
|
+
report.write("No problems found.\n")
|