flamapy-fm 2.0.0.dev8__tar.gz → 2.0.2.dev0__tar.gz

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 (46) hide show
  1. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/PKG-INFO +1 -1
  2. flamapy-fm-2.0.2.dev0/flamapy/metamodels/fm_metamodel/models/__init__.py +23 -0
  3. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy/metamodels/fm_metamodel/models/feature_model.py +87 -7
  4. flamapy-fm-2.0.2.dev0/flamapy/metamodels/fm_metamodel/operations/__init__.py +26 -0
  5. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy/metamodels/fm_metamodel/operations/fm_generate_random_attribute.py +6 -6
  6. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy/metamodels/fm_metamodel/operations/fm_metrics.py +11 -3
  7. flamapy-fm-2.0.2.dev0/flamapy/metamodels/fm_metamodel/operations/fm_variation_points.py +37 -0
  8. flamapy-fm-2.0.2.dev0/flamapy/metamodels/fm_metamodel/operations/interfaces/__init__.py +4 -0
  9. flamapy-fm-2.0.2.dev0/flamapy/metamodels/fm_metamodel/operations/interfaces/variation_points.py +21 -0
  10. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy/metamodels/fm_metamodel/transformations/__init__.py +7 -5
  11. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy/metamodels/fm_metamodel/transformations/afm_reader.py +4 -0
  12. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy/metamodels/fm_metamodel/transformations/clafer_writer.py +8 -3
  13. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy/metamodels/fm_metamodel/transformations/featureide_writer.py +21 -17
  14. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy/metamodels/fm_metamodel/transformations/glencoe_writer.py +6 -1
  15. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy/metamodels/fm_metamodel/transformations/json_reader.py +14 -11
  16. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy/metamodels/fm_metamodel/transformations/json_writer.py +6 -1
  17. flamapy-fm-2.0.2.dev0/flamapy/metamodels/fm_metamodel/transformations/pl_writer.py +170 -0
  18. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy/metamodels/fm_metamodel/transformations/splot_writer.py +9 -3
  19. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy/metamodels/fm_metamodel/transformations/uvl_reader.py +215 -93
  20. flamapy-fm-2.0.2.dev0/flamapy/metamodels/fm_metamodel/transformations/uvl_writer.py +169 -0
  21. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy/metamodels/fm_metamodel/transformations/xml_reader.py +2 -0
  22. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy_fm.egg-info/PKG-INFO +1 -1
  23. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy_fm.egg-info/SOURCES.txt +5 -0
  24. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy_fm.egg-info/requires.txt +3 -3
  25. flamapy-fm-2.0.2.dev0/pyproject.toml +51 -0
  26. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/setup.py +1 -1
  27. flamapy-fm-2.0.0.dev8/flamapy/metamodels/fm_metamodel/models/__init__.py +0 -17
  28. flamapy-fm-2.0.0.dev8/flamapy/metamodels/fm_metamodel/operations/__init__.py +0 -21
  29. flamapy-fm-2.0.0.dev8/flamapy/metamodels/fm_metamodel/transformations/uvl_writer.py +0 -138
  30. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/README.md +0 -0
  31. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy/metamodels/fm_metamodel/__init__.py +0 -0
  32. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy/metamodels/fm_metamodel/operations/fm_atomic_sets.py +0 -0
  33. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy/metamodels/fm_metamodel/operations/fm_average_branching_factor.py +0 -0
  34. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy/metamodels/fm_metamodel/operations/fm_core_features.py +0 -0
  35. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy/metamodels/fm_metamodel/operations/fm_count_leafs.py +0 -0
  36. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy/metamodels/fm_metamodel/operations/fm_estimated_configurations_number.py +0 -0
  37. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy/metamodels/fm_metamodel/operations/fm_feature_ancestors.py +0 -0
  38. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy/metamodels/fm_metamodel/operations/fm_leaf_features.py +0 -0
  39. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy/metamodels/fm_metamodel/operations/fm_max_depth_tree.py +0 -0
  40. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy/metamodels/fm_metamodel/transformations/afm_writer.py +0 -0
  41. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy/metamodels/fm_metamodel/transformations/featureide_reader.py +0 -0
  42. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy/metamodels/fm_metamodel/transformations/glencoe_reader.py +0 -0
  43. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy/metamodels/fm_metamodel/transformations/pysat_to_fm.py +0 -0
  44. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy_fm.egg-info/dependency_links.txt +0 -0
  45. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/flamapy_fm.egg-info/top_level.txt +0 -0
  46. {flamapy-fm-2.0.0.dev8 → flamapy-fm-2.0.2.dev0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flamapy-fm
3
- Version: 2.0.0.dev8
3
+ Version: 2.0.2.dev0
4
4
  Summary: flamapy-fm is a plugin to Flamapy module
5
5
  Home-page: https://github.com/flamapy/fm_metamodel
6
6
  Author: Flamapy
@@ -0,0 +1,23 @@
1
+ from .feature_model import (
2
+ Feature,
3
+ Constraint,
4
+ FeatureModel,
5
+ Relation,
6
+ Domain,
7
+ Range,
8
+ Attribute,
9
+ Cardinality,
10
+ FeatureType
11
+ )
12
+
13
+ __all__ = [
14
+ 'Attribute',
15
+ 'Cardinality',
16
+ 'Constraint',
17
+ 'Domain',
18
+ 'Feature',
19
+ 'FeatureModel',
20
+ 'FeatureType',
21
+ 'Range',
22
+ 'Relation',
23
+ ]
@@ -1,8 +1,10 @@
1
1
  from typing import Any, Optional
2
2
  from functools import total_ordering
3
+ from enum import Enum
3
4
 
4
5
  from flamapy.core.exceptions import FlamaException
5
6
  from flamapy.core.models import AST, VariabilityModel, VariabilityElement, ASTOperation
7
+ from flamapy.core.models.ast import LOGICAL_OPERATORS, ARITHMETIC_OPERATORS, AGGREGATION_OPERATORS
6
8
  from flamapy.core.models.ast import simplify_formula, propagate_negation, to_cnf
7
9
 
8
10
 
@@ -68,6 +70,8 @@ class Relation:
68
70
  else:
69
71
  relation_type = "Other"
70
72
  res = f"({relation_type}) " + res
73
+ if res.endswith(" "):
74
+ res = res[:-1]
71
75
  return res
72
76
 
73
77
  def __hash__(self) -> int:
@@ -88,21 +92,39 @@ class Relation:
88
92
  return str(self) < str(other)
89
93
 
90
94
 
95
+ class FeatureType(Enum):
96
+ BOOLEAN = 'Boolean'
97
+ INTEGER = 'Integer'
98
+ REAL = 'Real'
99
+ STRING = 'String'
100
+
101
+
102
+ class Cardinality:
103
+
104
+ def __init__(self, card_min: int = 1, card_max: int = 1):
105
+ self.min = card_min
106
+ self.max = card_max
107
+
108
+
91
109
  @total_ordering
92
110
  class Feature(VariabilityElement):
93
111
 
94
- def __init__(
112
+ def __init__( # noqa: PLR0913
95
113
  self,
96
114
  name: str,
97
115
  relations: Optional[list["Relation"]] = None,
98
116
  parent: Optional["Feature"] = None,
99
117
  is_abstract: bool = False,
118
+ feature_type: FeatureType = FeatureType.BOOLEAN,
119
+ feature_cardinality: Cardinality = Cardinality(1, 1)
100
120
  ):
101
121
  super().__init__(name)
102
122
  self.name = name
103
123
  self.relations = [] if relations is None else relations
104
124
  self.parent = self._get_parent() if parent is None else parent
105
125
  self.is_abstract = is_abstract
126
+ self.feature_type = feature_type
127
+ self.feature_cardinality = feature_cardinality
106
128
  self.attributes = list["Attribute"]([])
107
129
 
108
130
  def is_empty(self) -> bool:
@@ -170,6 +192,19 @@ class Feature(VariabilityElement):
170
192
  def is_leaf(self) -> bool:
171
193
  return len(self.get_relations()) == 0
172
194
 
195
+ def is_boolean(self) -> bool:
196
+ return self.feature_type == FeatureType.BOOLEAN
197
+
198
+ def is_numerical(self) -> bool:
199
+ return self.feature_type in [FeatureType.INTEGER, FeatureType.REAL]
200
+
201
+ def is_string(self) -> bool:
202
+ return self.feature_type == FeatureType.STRING
203
+
204
+ def is_multifeature(self) -> bool:
205
+ """Return true if the feature has a cardinality different from [1..1]."""
206
+ return self.feature_cardinality.min != 1 or self.feature_cardinality.max != 1
207
+
173
208
  def __str__(self) -> str:
174
209
  return self.name
175
210
 
@@ -205,7 +240,11 @@ class Constraint:
205
240
  stack = [self.ast.root]
206
241
  while stack:
207
242
  node = stack.pop()
243
+ if node is None:
244
+ continue
208
245
  if node.is_unique_term():
246
+ if (isinstance(node.data, (int, float)) or node.data.startswith("'")):
247
+ continue
209
248
  features.add(node.data)
210
249
  elif node.is_unary_op():
211
250
  stack.append(node.left)
@@ -214,6 +253,25 @@ class Constraint:
214
253
  stack.append(node.left)
215
254
  return list(features)
216
255
 
256
+ def is_logical_constraint(self) -> bool:
257
+ """Return true if the constraint contains only logical operators."""
258
+ return all(op in LOGICAL_OPERATORS for op in self.ast.get_operators())
259
+
260
+ def is_arithmetic_constraint(self) -> bool:
261
+ """Return true if the constraint contains at least one arithmetic operator."""
262
+ return any(op in ARITHMETIC_OPERATORS for op in self.ast.get_operators())
263
+
264
+ def is_aggregation_constraint(self) -> bool:
265
+ """Return true if the constraint contains at least one aggregation operator."""
266
+ return any(op in AGGREGATION_OPERATORS for op in self.ast.get_operators())
267
+
268
+ def is_single_feature_constraint(self) -> bool:
269
+ """Return true if the constraint is a single feature or its negation."""
270
+ root_op = self._ast.root
271
+ return (root_op.is_term() or
272
+ (root_op.data == ASTOperation.NOT and
273
+ (root_op.left.is_term() or root_op.right.is_term())))
274
+
217
275
  def is_simple_constraint(self) -> bool:
218
276
  """Return true if the constraint is a simple constraint (requires or excludes)."""
219
277
  return self.is_requires_constraint() or self.is_excludes_constraint()
@@ -221,7 +279,7 @@ class Constraint:
221
279
  def is_complex_constraint(self) -> bool:
222
280
  """Return true if the constraint is a complex constraint
223
281
  (i.e., it is not a simple constraint)."""
224
- return not self.is_simple_constraint()
282
+ return self.is_logical_constraint() and not self.is_simple_constraint()
225
283
 
226
284
  def is_requires_constraint(self) -> bool:
227
285
  """Return true if the constraint is a requires constraint."""
@@ -240,10 +298,10 @@ class Constraint:
240
298
  and root_op.right.left.is_term()
241
299
  )
242
300
  return (
243
- neg_left
244
- and root_op.right.is_term()
245
- or neg_right
246
- and root_op.left.is_term()
301
+ (neg_left
302
+ and root_op.right.is_term())
303
+ or (neg_right
304
+ and root_op.left.is_term())
247
305
  )
248
306
  return False
249
307
 
@@ -276,6 +334,8 @@ class Constraint:
276
334
  def is_pseudocomplex_constraint(self) -> bool:
277
335
  """Return true if the constraint is a pseudo-complex constraint
278
336
  (i.e., it can be transformed to a set of simple constraints)."""
337
+ if not self.is_logical_constraint():
338
+ return False
279
339
  split_ctcs = split_constraint(self)
280
340
  return len(split_ctcs) > 1 and all(
281
341
  ctc.is_simple_constraint() for ctc in split_ctcs
@@ -284,11 +344,13 @@ class Constraint:
284
344
  def is_strictcomplex_constraint(self) -> bool:
285
345
  """Return true if the constraint is a strict-complex constraint
286
346
  (i.e., it cannot be transformed to a set of simple constraints)."""
347
+ if not self.is_logical_constraint():
348
+ return False
287
349
  split_ctcs = split_constraint(self)
288
350
  return any(ctc.is_complex_constraint() for ctc in split_ctcs)
289
351
 
290
352
  def __str__(self) -> str:
291
- return f"({self.name}) {str(self.ast)}"
353
+ return f"({self.name}) {self.ast!s}"
292
354
 
293
355
  def __hash__(self) -> int:
294
356
  return hash(str(self.ast).lower())
@@ -334,6 +396,15 @@ class FeatureModel(VariabilityModel):
334
396
  features.extend(relation.children)
335
397
  return features
336
398
 
399
+ def get_boolean_features(self) -> list["Feature"]:
400
+ return [f for f in self.get_features() if f.is_boolean()]
401
+
402
+ def get_numerical_features(self) -> list["Feature"]:
403
+ return [f for f in self.get_features() if f.is_numerical()]
404
+
405
+ def get_string_features(self) -> list["Feature"]:
406
+ return [f for f in self.get_features() if f.is_string()]
407
+
337
408
  def get_constraints(self) -> list["Constraint"]:
338
409
  return self.ctcs
339
410
 
@@ -352,6 +423,15 @@ class FeatureModel(VariabilityModel):
352
423
  def get_feature_by_name(self, feature_name: str) -> Optional["Feature"]:
353
424
  return next((f for f in self.get_features() if f.name == feature_name), None)
354
425
 
426
+ def get_logical_constraints(self) -> list["Constraint"]:
427
+ return [c for c in self.get_constraints() if c.is_logical_constraint()]
428
+
429
+ def get_arithmetic_constraints(self) -> list["Constraint"]:
430
+ return [c for c in self.get_constraints() if c.is_arithmetic_constraint()]
431
+
432
+ def get_aggregations_constraints(self) -> list["Constraint"]:
433
+ return [c for c in self.get_constraints() if c.is_aggregation_constraint()]
434
+
355
435
  def get_complex_constraints(self) -> list["Constraint"]:
356
436
  return [c for c in self.get_constraints() if c.is_complex_constraint()]
357
437
 
@@ -0,0 +1,26 @@
1
+ from .fm_core_features import FMCoreFeatures
2
+ from .fm_count_leafs import FMCountLeafs
3
+ from .fm_leaf_features import FMLeafFeatures
4
+ from .fm_average_branching_factor import FMAverageBranchingFactor
5
+ from .fm_feature_ancestors import FMFeatureAncestors
6
+ from .fm_max_depth_tree import FMMaxDepthTree
7
+ from .fm_estimated_configurations_number import FMEstimatedConfigurationsNumber
8
+ from .fm_atomic_sets import FMAtomicSets
9
+ from .fm_metrics import FMMetrics
10
+ from .fm_generate_random_attribute import GenerateRandomAttribute
11
+ from .fm_variation_points import FMVariationPoints
12
+
13
+
14
+ __all__ = [
15
+ 'FMAtomicSets',
16
+ 'FMAverageBranchingFactor',
17
+ 'FMCoreFeatures',
18
+ 'FMCountLeafs',
19
+ 'FMEstimatedConfigurationsNumber',
20
+ 'FMFeatureAncestors',
21
+ 'FMLeafFeatures',
22
+ 'FMMaxDepthTree',
23
+ 'FMMetrics',
24
+ 'FMVariationPoints',
25
+ 'GenerateRandomAttribute',
26
+ ]
@@ -5,9 +5,9 @@ from flamapy.core.models import VariabilityModel
5
5
  from flamapy.core.operations import Operation
6
6
  from flamapy.core.exceptions import FlamaException
7
7
  from flamapy.metamodels.fm_metamodel.models import (
8
- FeatureModel,
8
+ FeatureModel,
9
9
  Range,
10
- Domain,
10
+ Domain,
11
11
  Attribute
12
12
  )
13
13
 
@@ -44,19 +44,19 @@ class GenerateRandomAttribute(Operation):
44
44
  if self._attribute_domain is None:
45
45
  raise FlamaException("Attribute's domain has not been provided.")
46
46
  fm_model = cast(FeatureModel, model)
47
- self.result = generate_random_attribute_values(fm_model,
48
- self._attribute_name,
47
+ self.result = generate_random_attribute_values(fm_model,
48
+ self._attribute_name,
49
49
  self._attribute_domain,
50
50
  self._only_leaf_features)
51
51
  return self
52
52
 
53
53
 
54
- def generate_random_attribute_values(feature_model: FeatureModel,
54
+ def generate_random_attribute_values(feature_model: FeatureModel,
55
55
  name: str,
56
56
  domain: Domain,
57
57
  only_leaf_features: bool) -> FeatureModel:
58
58
  for feature in feature_model.get_features():
59
- if not only_leaf_features or only_leaf_features and feature.is_leaf():
59
+ if not only_leaf_features or (only_leaf_features and feature.is_leaf()):
60
60
  if not any(name == attr.name for attr in feature.get_attributes()):
61
61
  random_value = get_random_value_from_domain(domain)
62
62
  new_attribute = Attribute(name, domain, random_value)
@@ -5,7 +5,7 @@ from flamapy.core.exceptions import FlamaException
5
5
  from flamapy.core.models.variability_model import VariabilityModel
6
6
  from flamapy.core.operations.metrics_operation import Metrics
7
7
  from flamapy.metamodels.fm_metamodel.models import FeatureModel, Feature
8
- from flamapy.metamodels.fm_metamodel import operations as fm_operations
8
+ from flamapy.metamodels.fm_metamodel.operations import FMAverageBranchingFactor
9
9
 
10
10
 
11
11
  def metric_method(func: Callable[..., dict[str, Any]]) -> Callable[..., dict[str, Any]]:
@@ -30,7 +30,7 @@ class FMMetrics(Metrics): # pylint: disable=too-many-instance-attributes
30
30
  super().__init__()
31
31
  self.model: Optional[FeatureModel] = None
32
32
  self.result: list[dict[str, Any]] = []
33
- self.model_type_extension = "fm"
33
+ self._model_type_extension = "fm"
34
34
  self._features: list[Feature] = []
35
35
  self._features_by_name: dict[str, Feature] = {}
36
36
  self._abstract_features: dict[str, Feature] = {}
@@ -39,6 +39,14 @@ class FMMetrics(Metrics): # pylint: disable=too-many-instance-attributes
39
39
  self._constraints_per_features: list[int] = []
40
40
  self._feature_ancestors: list[int] = []
41
41
 
42
+ @property
43
+ def model_type_extension(self) -> str:
44
+ return self._model_type_extension
45
+
46
+ @model_type_extension.setter
47
+ def model_type_extension(self, ext: str) -> None:
48
+ self._model_type_extension = ext
49
+
42
50
  def get_result(self) -> list[dict[str, Any]]:
43
51
  return self.result
44
52
 
@@ -516,7 +524,7 @@ class FMMetrics(Metrics): # pylint: disable=too-many-instance-attributes
516
524
  raise FlamaException("Feature model is not defined.")
517
525
 
518
526
  name = "Branching factor"
519
- _avg_branching_factor = fm_operations.average_branching_factor(self.model)
527
+ _avg_branching_factor = FMAverageBranchingFactor().execute(self.model).get_result()
520
528
  result = self.construct_result(
521
529
  name=name, doc=self.branching_factor.__doc__, result=_avg_branching_factor
522
530
  )
@@ -0,0 +1,37 @@
1
+ from typing import cast
2
+
3
+ from flamapy.core.models import VariabilityModel
4
+ from flamapy.metamodels.fm_metamodel.operations.interfaces import VariationPoints
5
+ from flamapy.metamodels.fm_metamodel.models import FeatureModel, Feature
6
+
7
+
8
+ class FMVariationPoints(VariationPoints):
9
+
10
+ def __init__(self) -> None:
11
+ self.result: dict[Feature, list[Feature]] = {}
12
+
13
+ def get_result(self) -> dict[Feature, list[Feature]]:
14
+ return self.result
15
+
16
+ def execute(self, model: VariabilityModel) -> 'FMVariationPoints':
17
+ fm_model = cast(FeatureModel, model)
18
+ self.result = variation_points(fm_model)
19
+ return self
20
+
21
+ def variation_points(self) -> dict[Feature, list[Feature]]:
22
+ return self.get_result()
23
+
24
+
25
+ def variation_points(feature_model: FeatureModel) -> dict[Feature, list[Feature]]:
26
+ vps: dict[Feature, list[Feature]] = {}
27
+ features = [feature_model.root]
28
+ while features:
29
+ feature = features.pop()
30
+ variants = []
31
+ for relation in feature.get_relations():
32
+ if not relation.is_mandatory():
33
+ variants.extend(relation.children)
34
+ if variants:
35
+ vps[feature] = variants
36
+ features.extend(feature.get_children())
37
+ return vps
@@ -0,0 +1,4 @@
1
+ from .variation_points import VariationPoints
2
+
3
+
4
+ __all__ = ['VariationPoints']
@@ -0,0 +1,21 @@
1
+ from abc import abstractmethod
2
+
3
+ from flamapy.core.operations import Operation
4
+
5
+ from flamapy.metamodels.fm_metamodel.models import Feature
6
+
7
+
8
+ class VariationPoints(Operation):
9
+ """The variation points of a feature model are those features that require to make a choice
10
+ (i.e., select a variant).
11
+
12
+ This operation returns the variation points and the variants of each variation point.
13
+ """
14
+
15
+ @abstractmethod
16
+ def __init__(self) -> None:
17
+ pass
18
+
19
+ @abstractmethod
20
+ def variation_points(self) -> dict[Feature, list[Feature]]:
21
+ pass
@@ -13,16 +13,18 @@ from .glencoe_writer import GlencoeWriter
13
13
  from .clafer_writer import ClaferWriter
14
14
 
15
15
 
16
- __all__ = ['AFMReader',
16
+ __all__ = [
17
+ 'AFMReader',
17
18
  'AFMWriter',
19
+ 'ClaferWriter',
18
20
  'FeatureIDEReader',
19
21
  'FeatureIDEWriter',
20
- 'JSONWriter',
22
+ 'GlencoeReader',
23
+ 'GlencoeWriter',
21
24
  'JSONReader',
25
+ 'JSONWriter',
22
26
  'SPLOTWriter',
23
27
  'UVLReader',
24
28
  'UVLWriter',
25
29
  'XMLReader',
26
- 'GlencoeReader',
27
- 'GlencoeWriter',
28
- 'ClaferWriter']
30
+ ]
@@ -128,6 +128,8 @@ class AFMReader(TextToModel):
128
128
  if attribute_feature is None:
129
129
  raise FlamaException("attribute_feature is not defined")
130
130
 
131
+ domain = None # type: Optional[Domain]
132
+
131
133
  discrete_domain_node = attribute_spec.attribute_domain().discrete_domain_spec()
132
134
  if discrete_domain_node is not None:
133
135
  values = []
@@ -179,6 +181,8 @@ class AFMReader(TextToModel):
179
181
  def build_ast_node(
180
182
  self, expression: AFMParser.ExpressionContext, prefix: str
181
183
  ) -> Node:
184
+ var_name = None # Ensure var_name is initialized
185
+
182
186
  if isinstance(expression, AFMParser.AtomContext):
183
187
  if expression.variable() is not None:
184
188
  var_name = prefix + expression.variable().getText()
@@ -1,4 +1,5 @@
1
1
  import re
2
+ import string
2
3
  from enum import Enum
3
4
  from typing import Any, Optional
4
5
 
@@ -128,8 +129,8 @@ def serialize_constraint(ctc: Constraint) -> str:
128
129
 
129
130
 
130
131
  def attributes_definition(feature_model: FeatureModel) -> str:
131
- attributes = {attribute.get_name(): parse_type_value(attribute.get_default_value())
132
- for feature in feature_model.get_features()
132
+ attributes = {attribute.get_name(): parse_type_value(attribute.get_default_value())
133
+ for feature in feature_model.get_features()
133
134
  for attribute in feature.get_attributes()}
134
135
  result = ''
135
136
  if attributes:
@@ -152,4 +153,8 @@ def parse_type_value(value: Any) -> str:
152
153
 
153
154
 
154
155
  def safename(name: str) -> str:
155
- return f'"{name}"' if ' ' in name else name
156
+ return f'"{name}"' if any(char not in safecharacters() for char in name) else name
157
+
158
+
159
+ def safecharacters() -> str:
160
+ return string.ascii_letters + string.digits + '_'
@@ -1,7 +1,8 @@
1
+ import string
1
2
  from xml.etree import ElementTree
2
3
  from xml.etree.ElementTree import Element
3
4
  from xml.dom import minidom
4
- from typing import Any
5
+ from typing import Any
5
6
 
6
7
  from flamapy.core.models.ast import Node, ASTOperation
7
8
  from flamapy.core.transformations import ModelToText
@@ -31,9 +32,9 @@ class FeatureIDEWriter(ModelToText):
31
32
 
32
33
  def transform(self) -> str:
33
34
  fm_tree = _to_featureidexml(self._source_model).getroot()
34
- xml_str = ElementTree.tostring(fm_tree,
35
- encoding='UTF-8',
36
- method='xml',
35
+ xml_str = ElementTree.tostring(fm_tree,
36
+ encoding='UTF-8',
37
+ method='xml',
37
38
  xml_declaration=True)
38
39
  xml_str = prettify(xml_str)
39
40
  if self._path is not None:
@@ -47,8 +48,8 @@ def _to_featureidexml(feature_model: FeatureModel) -> ElementTree.ElementTree:
47
48
  tree = ElementTree.ElementTree(fm_element)
48
49
  struct = ElementTree.SubElement(fm_element, FeatureIDEReader.TAG_STRUCT)
49
50
  constraints = ElementTree.SubElement(fm_element, FeatureIDEReader.TAG_CONSTRAINTS)
50
- root = ElementTree.SubElement(struct,
51
- _tag_element(feature_model.root),
51
+ root = ElementTree.SubElement(struct,
52
+ _tag_element(feature_model.root),
52
53
  _get_attributes(feature_model.root))
53
54
  _create_tree(root, feature_model.root.get_relations())
54
55
  _get_constraints(constraints, feature_model.get_constraints())
@@ -66,24 +67,23 @@ def _create_tree(parent_element: Element, relations: list[Relation]) -> None:
66
67
 
67
68
  def _get_attributes(feature: Feature) -> dict[str, str]:
68
69
  atributes = {}
69
- if feature.is_mandatory():
70
+ if feature.is_mandatory():
70
71
  atributes['mandatory'] = 'true'
71
- if feature.is_abstract:
72
+ if feature.is_abstract:
72
73
  atributes['abstract'] = 'true'
73
74
  atributes['name'] = safename(feature.name)
74
75
  return atributes
75
76
 
76
77
 
77
78
  def _tag_element(feature: Feature) -> str:
78
- if feature.is_leaf():
79
+ if feature.is_leaf():
79
80
  name = FeatureIDEReader.TAG_FEATURE
80
- else:
81
- if feature.is_or_group():
82
- name = FeatureIDEReader.TAG_OR
83
- elif feature.is_alternative_group():
84
- name = FeatureIDEReader.TAG_ALT
85
- else:
86
- name = FeatureIDEReader.TAG_AND
81
+ elif feature.is_or_group():
82
+ name = FeatureIDEReader.TAG_OR
83
+ elif feature.is_alternative_group():
84
+ name = FeatureIDEReader.TAG_ALT
85
+ else:
86
+ name = FeatureIDEReader.TAG_AND
87
87
  return name
88
88
 
89
89
 
@@ -139,4 +139,8 @@ def prettify(xml: str) -> bytes:
139
139
 
140
140
 
141
141
  def safename(name: str) -> str:
142
- return f'"{name}"' if ' ' in name else name
142
+ return f'"{name}"' if any(char not in safecharacters() for char in name) else name
143
+
144
+
145
+ def safecharacters() -> str:
146
+ return string.ascii_letters + string.digits + '_'
@@ -1,4 +1,5 @@
1
1
  import json
2
+ import string
2
3
  from typing import Any
3
4
 
4
5
  from flamapy.core.models.ast import Node, ASTOperation
@@ -106,4 +107,8 @@ def _get_ctc_info(ast_node: Node) -> dict[str, Any]:
106
107
 
107
108
 
108
109
  def safename(name: str) -> str:
109
- return f'"{name}"' if ' ' in name else name
110
+ return f'"{name}"' if any(char not in safecharacters() for char in name) else name
111
+
112
+
113
+ def safecharacters() -> str:
114
+ return string.ascii_letters + string.digits + '_'
@@ -7,10 +7,10 @@ from flamapy.core.exceptions import ParsingException
7
7
  from flamapy.core.transformations import TextToModel
8
8
 
9
9
  from flamapy.metamodels.fm_metamodel.models import (
10
- FeatureModel,
11
- Relation,
12
- Feature,
13
- Constraint,
10
+ FeatureModel,
11
+ Relation,
12
+ Feature,
13
+ Constraint,
14
14
  Attribute
15
15
  )
16
16
 
@@ -64,8 +64,7 @@ def parse_attributes(feature: Feature, feature_node: Dict[str, Any]) -> None:
64
64
  attr.set_parent(feature)
65
65
  feature.add_attribute(attr)
66
66
 
67
-
68
- def parse_relations(feature: Feature, feature_node: Dict[str, Any]) -> None:
67
+ def parse_relations(feature: Feature, feature_node: Dict[str, Any]) -> None: # noqa: C901
69
68
  if 'relations' in feature_node:
70
69
  for relation in feature_node['relations']:
71
70
  children = []
@@ -73,6 +72,7 @@ def parse_relations(feature: Feature, feature_node: Dict[str, Any]) -> None:
73
72
  child_feature = parse_tree(feature, child)
74
73
  children.append(child_feature)
75
74
  relation_type = relation['type']
75
+ new_relation: Optional[Relation] = None
76
76
  if relation_type == JSONFeatureType.OPTIONAL.value:
77
77
  new_relation = Relation(feature, children, 0, 1)
78
78
  elif relation_type == JSONFeatureType.MANDATORY.value:
@@ -87,7 +87,10 @@ def parse_relations(feature: Feature, feature_node: Dict[str, Any]) -> None:
87
87
  card_min = relation['card_min']
88
88
  card_max = relation['card_max']
89
89
  new_relation = Relation(feature, children, card_min, card_max)
90
- feature.add_relation(new_relation)
90
+ if new_relation is not None:
91
+ feature.add_relation(new_relation)
92
+ else:
93
+ raise ParsingException(f'Invalid relation in JSON: {relation}')
91
94
 
92
95
 
93
96
  def parse_constraints(constraints_info: List[Dict[str, Any]]) -> List[Constraint]:
@@ -129,13 +132,13 @@ def parse_ast_constraint(ctc_info: Dict[str, Any]) -> Node:
129
132
  node = Node(ASTOperation.EQUIVALENCE, left, right)
130
133
  elif ctc_type == ASTOperation.AND.value:
131
134
  op_list = [parse_ast_constraint(op) for op in ctc_operands]
132
- node = functools.reduce(lambda l, r: Node(ASTOperation.AND, l, r), op_list)
135
+ node = functools.reduce(lambda lambd, r: Node(ASTOperation.AND, lambd, r), op_list)
133
136
  elif ctc_type == ASTOperation.OR.value:
134
137
  op_list = [parse_ast_constraint(op) for op in ctc_operands]
135
- node = functools.reduce(lambda l, r: Node(ASTOperation.OR, l, r), op_list)
138
+ node = functools.reduce(lambda lambd, r: Node(ASTOperation.OR, lambd, r), op_list)
136
139
  elif ctc_type == ASTOperation.XOR.value:
137
140
  op_list = [parse_ast_constraint(op) for op in ctc_operands]
138
- node = functools.reduce(lambda l, r: Node(ASTOperation.XOR, l, r), op_list)
141
+ node = functools.reduce(lambda lambd, r: Node(ASTOperation.XOR, lambd, r), op_list)
139
142
  else:
140
143
  raise ParsingException(f'Invalid constraint in JSON: {ctc_info}')
141
- return node
144
+ return node
@@ -1,4 +1,5 @@
1
1
  import json
2
+ import string
2
3
  from enum import Enum
3
4
  from typing import Any, Dict, List
4
5
 
@@ -126,4 +127,8 @@ def get_ctc_info(ast_node: Node) -> Dict[str, Any]:
126
127
 
127
128
 
128
129
  def safename(name: str) -> str:
129
- return f'"{name}"' if ' ' in name else name
130
+ return f'"{name}"' if any(char not in safecharacters() for char in name) else name
131
+
132
+
133
+ def safecharacters() -> str:
134
+ return string.ascii_letters + string.digits + '_'