flamapy-fm 2.5.0.dev0__tar.gz → 2.6.0.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.
- {flamapy_fm-2.5.0.dev0/flamapy_fm.egg-info → flamapy_fm-2.6.0.dev0}/PKG-INFO +11 -8
- flamapy_fm-2.6.0.dev0/README.md +13 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/models/feature_model.py +16 -7
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/__init__.py +2 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/uvl_reader.py +26 -16
- flamapy_fm-2.6.0.dev0/flamapy/metamodels/fm_metamodel/transformations/xml_writer.py +140 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0/flamapy_fm.egg-info}/PKG-INFO +11 -8
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy_fm.egg-info/SOURCES.txt +1 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy_fm.egg-info/requires.txt +2 -2
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/pyproject.toml +3 -3
- flamapy_fm-2.5.0.dev0/README.md +0 -10
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/LICENSE +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/__init__.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/models/__init__.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/operations/__init__.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/operations/fm_atomic_sets.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/operations/fm_average_branching_factor.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/operations/fm_core_features.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/operations/fm_count_leafs.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/operations/fm_estimated_configurations_number.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/operations/fm_feature_ancestors.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/operations/fm_generate_random_attribute.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/operations/fm_language_level.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/operations/fm_leaf_features.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/operations/fm_max_depth_tree.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/operations/fm_metrics.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/operations/fm_variation_points.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/operations/interfaces/__init__.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/operations/interfaces/variation_points.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/afm_reader.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/afm_writer.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/clafer_writer.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/featureide_reader.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/featureide_writer.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/flat_fm.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/fm_secure_features_names.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/glencoe_reader.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/glencoe_writer.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/json_reader.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/json_writer.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/pl_writer.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/pysat_to_fm.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/refactorings/__init__.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/refactorings/cardinality_group_refactoring.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/refactorings/commitment_feature.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/refactorings/deletion_feature.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/refactorings/feature_cardinality_refactoring.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/refactorings/feature_cardinality_refactoring_paper.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/refactorings/multiple_group_decomposition_refactoring.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/refactorings/mutex_group_refactoring.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/refactorings/or_mandatory_refactoring.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/refactorings/pseudocomplex_constraint_refactoring.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/refactorings/refactoring_exception.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/refactorings/refactoring_interface.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/refactorings/strictcomplex_constraint_refactoring.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/refactorings/xor_mandatory_refactoring.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/splot_writer.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/uvl_writer.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/transformations/xml_reader.py +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy_fm.egg-info/dependency_links.txt +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy_fm.egg-info/top_level.txt +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/setup.cfg +0 -0
- {flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flamapy-fm
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6.0.dev0
|
|
4
4
|
Summary: flamapy-fm is a plugin to Flamapy module
|
|
5
5
|
Author-email: Flamapy <flamapy@us.es>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -8,8 +8,8 @@ Project-URL: Homepage, https://github.com/flamapy/fm_metamodel
|
|
|
8
8
|
Requires-Python: >=3.9
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE
|
|
11
|
-
Requires-Dist: flamapy-fw~=2.
|
|
12
|
-
Requires-Dist: uvlparser~=2.5.0
|
|
11
|
+
Requires-Dist: flamapy-fw~=2.6.0.dev0
|
|
12
|
+
Requires-Dist: uvlparser~=2.5.0
|
|
13
13
|
Requires-Dist: afmparser~=1.0.3
|
|
14
14
|
Provides-Extra: dev
|
|
15
15
|
Requires-Dist: pytest; extra == "dev"
|
|
@@ -20,13 +20,16 @@ Requires-Dist: coverage; extra == "dev"
|
|
|
20
20
|
Requires-Dist: antlr4-tools; extra == "dev"
|
|
21
21
|
Dynamic: license-file
|
|
22
22
|
|
|
23
|
-
#
|
|
23
|
+
# flamapy-fm
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
The feature model metamodel plugin for [flamapy](https://flamapy.org): the
|
|
26
|
+
feature model classes plus readers and writers for many formats (UVL,
|
|
27
|
+
FeatureIDE, Glencoe, AFM, SPLOT, JSON, …).
|
|
26
28
|
|
|
29
|
+
**Documentation:** https://docs.flamapy.org/framework/plugins/feature_model_plugin
|
|
27
30
|
|
|
28
|
-
##
|
|
31
|
+
## Installation
|
|
29
32
|
|
|
30
|
-
```
|
|
31
|
-
pip install -
|
|
33
|
+
```bash
|
|
34
|
+
pip install flamapy-fm
|
|
32
35
|
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# flamapy-fm
|
|
2
|
+
|
|
3
|
+
The feature model metamodel plugin for [flamapy](https://flamapy.org): the
|
|
4
|
+
feature model classes plus readers and writers for many formats (UVL,
|
|
5
|
+
FeatureIDE, Glencoe, AFM, SPLOT, JSON, …).
|
|
6
|
+
|
|
7
|
+
**Documentation:** https://docs.flamapy.org/framework/plugins/feature_model_plugin
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install flamapy-fm
|
|
13
|
+
```
|
|
@@ -3,7 +3,7 @@ from functools import total_ordering
|
|
|
3
3
|
from enum import Enum
|
|
4
4
|
|
|
5
5
|
from flamapy.core.exceptions import FlamaException
|
|
6
|
-
from flamapy.core.models import AST, VariabilityModel, VariabilityElement, ASTOperation
|
|
6
|
+
from flamapy.core.models import AST, VariabilityModel, VariabilityElement, ASTOperation, NodeType
|
|
7
7
|
from flamapy.core.models.ast import LOGICAL_OPERATORS, ARITHMETIC_OPERATORS, AGGREGATION_OPERATORS
|
|
8
8
|
from flamapy.core.models.ast import simplify_formula, propagate_negation, to_cnf
|
|
9
9
|
|
|
@@ -247,7 +247,11 @@ class Constraint:
|
|
|
247
247
|
self._ast = ast
|
|
248
248
|
|
|
249
249
|
def get_features(self) -> list[str]:
|
|
250
|
-
"""List of features' names involved in the constraint.
|
|
250
|
+
"""List of features' names involved in the constraint.
|
|
251
|
+
|
|
252
|
+
Uses node_type information when available (set by readers that support it).
|
|
253
|
+
Falls back to heuristics for nodes without explicit type info.
|
|
254
|
+
"""
|
|
251
255
|
features = set()
|
|
252
256
|
stack = [self.ast.root]
|
|
253
257
|
while stack:
|
|
@@ -255,9 +259,15 @@ class Constraint:
|
|
|
255
259
|
if node is None:
|
|
256
260
|
continue
|
|
257
261
|
if node.is_unique_term():
|
|
258
|
-
if
|
|
259
|
-
|
|
260
|
-
|
|
262
|
+
if node.node_type is not None:
|
|
263
|
+
if node.node_type == NodeType.FEATURE:
|
|
264
|
+
features.add(node.data)
|
|
265
|
+
# NodeType.LITERAL: skip
|
|
266
|
+
# Fallback heuristics for nodes without explicit type (backward compatibility)
|
|
267
|
+
elif not isinstance(node.data, (int, float)) and not (
|
|
268
|
+
isinstance(node.data, str) and node.data.startswith("'")
|
|
269
|
+
):
|
|
270
|
+
features.add(node.data)
|
|
261
271
|
elif node.is_unary_op():
|
|
262
272
|
stack.append(node.left)
|
|
263
273
|
elif node.is_binary_op():
|
|
@@ -281,8 +291,7 @@ class Constraint:
|
|
|
281
291
|
"""Return true if the constraint is a single feature or its negation."""
|
|
282
292
|
root_op = self._ast.root
|
|
283
293
|
return (root_op.is_term() or
|
|
284
|
-
(root_op.data == ASTOperation.NOT and
|
|
285
|
-
(root_op.left.is_term() or root_op.right.is_term())))
|
|
294
|
+
(root_op.data == ASTOperation.NOT and root_op.left.is_term()))
|
|
286
295
|
|
|
287
296
|
def is_simple_constraint(self) -> bool:
|
|
288
297
|
"""Return true if the constraint is a simple constraint (requires or excludes)."""
|
|
@@ -8,6 +8,7 @@ from .splot_writer import SPLOTWriter
|
|
|
8
8
|
from .uvl_reader import UVLReader
|
|
9
9
|
from .uvl_writer import UVLWriter
|
|
10
10
|
from .xml_reader import XMLReader
|
|
11
|
+
from .xml_writer import XMLWriter
|
|
11
12
|
from .glencoe_reader import GlencoeReader
|
|
12
13
|
from .glencoe_writer import GlencoeWriter
|
|
13
14
|
from .clafer_writer import ClaferWriter
|
|
@@ -33,4 +34,5 @@ __all__ = [
|
|
|
33
34
|
'UVLReader',
|
|
34
35
|
'UVLWriter',
|
|
35
36
|
'XMLReader',
|
|
37
|
+
'XMLWriter',
|
|
36
38
|
]
|
|
@@ -9,7 +9,7 @@ from uvl.UVLPythonParser import UVLPythonParser
|
|
|
9
9
|
|
|
10
10
|
from flamapy.core.exceptions import FlamaException
|
|
11
11
|
from flamapy.core.transformations import TextToModel
|
|
12
|
-
from flamapy.core.models.ast import AST, ASTOperation, Node
|
|
12
|
+
from flamapy.core.models.ast import AST, ASTOperation, Node, NodeType
|
|
13
13
|
from flamapy.metamodels.fm_metamodel.models import (
|
|
14
14
|
Constraint,
|
|
15
15
|
Feature,
|
|
@@ -169,7 +169,7 @@ class UVLReader(TextToModel):
|
|
|
169
169
|
feature_type = FeatureType.REAL
|
|
170
170
|
else:
|
|
171
171
|
raise FlamaException('Error: unknow feature type for '
|
|
172
|
-
f'{typed_text} of feature {feature.name}.')
|
|
172
|
+
f'{typed_text} of feature "{feature.name}".')
|
|
173
173
|
feature.feature_type = feature_type
|
|
174
174
|
|
|
175
175
|
def _check_attributes(
|
|
@@ -233,7 +233,7 @@ class UVLReader(TextToModel):
|
|
|
233
233
|
raise FlamaException(f'Feature {feature_reference_name} not found in '
|
|
234
234
|
f'imported model {namespace}.')
|
|
235
235
|
|
|
236
|
-
def process_relationship_type(self,
|
|
236
|
+
def process_relationship_type(self, # noqa: C901 - flat dispatch over UVL group types
|
|
237
237
|
feature: Feature,
|
|
238
238
|
feature_node: UVLPythonParser.FeatureContext) -> None:
|
|
239
239
|
for relationship in feature_node.group():
|
|
@@ -255,7 +255,13 @@ class UVLReader(TextToModel):
|
|
|
255
255
|
feature.add_relation(Relation(feature, childs, min_value, max_value))
|
|
256
256
|
if max_value > len(childs):
|
|
257
257
|
logging.warning(
|
|
258
|
-
|
|
258
|
+
f'Cardinality error in feature "{feature.name}": '
|
|
259
|
+
'max value is greater than the number of childs'
|
|
260
|
+
)
|
|
261
|
+
if min_value < 0:
|
|
262
|
+
logging.warning(
|
|
263
|
+
f'Cardinality error in feature "{feature.name}": '
|
|
264
|
+
'min value cannot be negative'
|
|
259
265
|
)
|
|
260
266
|
|
|
261
267
|
def process_feature(
|
|
@@ -328,7 +334,7 @@ class UVLReader(TextToModel):
|
|
|
328
334
|
if isinstance(ctx, UVLPythonParser.EquationConstraintContext):
|
|
329
335
|
return self.process_equation(ctx.equation())
|
|
330
336
|
if isinstance(ctx, UVLPythonParser.LiteralConstraintContext):
|
|
331
|
-
return Node(ctx.reference().getText().replace('"', ''))
|
|
337
|
+
return Node(ctx.reference().getText().replace('"', ''), node_type=NodeType.FEATURE)
|
|
332
338
|
|
|
333
339
|
raise NotImplementedError(f"Unknown type of constraint: {type(ctx)}")
|
|
334
340
|
|
|
@@ -388,22 +394,23 @@ class UVLReader(TextToModel):
|
|
|
388
394
|
self.process_expression(ctx.multiplicativeExpression()),
|
|
389
395
|
self.process_expression(ctx.primaryExpression()))
|
|
390
396
|
|
|
391
|
-
def _process_expression_leaves(self, ctx: Any) -> Node:
|
|
397
|
+
def _process_expression_leaves(self, ctx: Any) -> Node: # noqa: PLR0911 - one return per UVL leaf type
|
|
392
398
|
"""Helper to process literal leaves and primary expressions."""
|
|
393
399
|
if isinstance(ctx, UVLPythonParser.FloatLiteralExpressionContext):
|
|
394
|
-
return Node(float(ctx.getText()))
|
|
400
|
+
return Node(float(ctx.getText()), node_type=NodeType.LITERAL)
|
|
395
401
|
if isinstance(ctx, UVLPythonParser.IntegerLiteralExpressionContext):
|
|
396
|
-
return Node(int(ctx.getText()))
|
|
397
|
-
if isinstance(ctx,
|
|
398
|
-
|
|
399
|
-
|
|
402
|
+
return Node(int(ctx.getText()), node_type=NodeType.LITERAL)
|
|
403
|
+
if isinstance(ctx, UVLPythonParser.StringLiteralExpressionContext):
|
|
404
|
+
return Node(ctx.getText().replace('"', '').replace("'", ''), node_type=NodeType.LITERAL)
|
|
405
|
+
if isinstance(ctx, UVLPythonParser.LiteralExpressionContext):
|
|
406
|
+
return Node(ctx.getText().replace('"', '').replace("'", ''), node_type=NodeType.FEATURE)
|
|
400
407
|
if isinstance(ctx, UVLPythonParser.BracketExpressionContext):
|
|
401
408
|
return self.process_expression(ctx.expression())
|
|
402
409
|
if isinstance(ctx, UVLPythonParser.AggregateFunctionExpressionContext):
|
|
403
410
|
return self.process_aggregate(ctx.aggregateFunction())
|
|
404
411
|
|
|
405
|
-
# Default fallback
|
|
406
|
-
return Node(ctx.getText().replace('"', '').replace("'", ''))
|
|
412
|
+
# Default fallback: treat as feature reference
|
|
413
|
+
return Node(ctx.getText().replace('"', '').replace("'", ''), node_type=NodeType.FEATURE)
|
|
407
414
|
|
|
408
415
|
def process_aggregate(self, ctx: UVLPythonParser.AggregateFunctionContext) -> Node:
|
|
409
416
|
# 1. SUM: aggregateFunction -> sumAggregateFunction
|
|
@@ -419,14 +426,17 @@ class UVLReader(TextToModel):
|
|
|
419
426
|
# 3. STRING: aggregateFunction -> stringAggregateFunction
|
|
420
427
|
if isinstance(ctx, UVLPythonParser.StringAggregateFunctionExpressionContext):
|
|
421
428
|
string_func = ctx.stringAggregateFunction()
|
|
429
|
+
ref_node = Node(string_func.reference().getText().replace('"', ''),
|
|
430
|
+
node_type=NodeType.FEATURE)
|
|
422
431
|
# Here we handle the tag # LengthAggregateFunction
|
|
423
432
|
if isinstance(string_func, UVLPythonParser.LengthAggregateFunctionContext):
|
|
424
|
-
return Node(ASTOperation.LEN,
|
|
433
|
+
return Node(ASTOperation.LEN, ref_node)
|
|
425
434
|
|
|
426
435
|
# 4. NUMERIC: aggregateFunction -> numericAggregateFunction
|
|
427
436
|
if isinstance(ctx, UVLPythonParser.NumericAggregateFunctionExpressionContext):
|
|
428
437
|
num_func = ctx.numericAggregateFunction()
|
|
429
|
-
ref_node = Node(num_func.reference().getText())
|
|
438
|
+
ref_node = Node(num_func.reference().getText().replace('"', ''),
|
|
439
|
+
node_type=NodeType.FEATURE)
|
|
430
440
|
# Handle tags # FloorAggregateFunction and # CeilAggregateFunction
|
|
431
441
|
if isinstance(num_func, UVLPythonParser.FloorAggregateFunctionContext):
|
|
432
442
|
return Node(ASTOperation.FLOOR, ref_node)
|
|
@@ -437,7 +447,7 @@ class UVLReader(TextToModel):
|
|
|
437
447
|
|
|
438
448
|
def _build_aggregate_node(self, operation: ASTOperation, references: list[Any]) -> Node:
|
|
439
449
|
"""Helper for Sum y Avg that can take 1 or 2 references"""
|
|
440
|
-
nodes = [Node(r.getText().replace('"', '')) for r in references]
|
|
450
|
+
nodes = [Node(r.getText().replace('"', ''), node_type=NodeType.FEATURE) for r in references]
|
|
441
451
|
return Node(operation, *nodes)
|
|
442
452
|
|
|
443
453
|
def process_includes(
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from xml.dom import minidom
|
|
3
|
+
import xml.etree.ElementTree as ET
|
|
4
|
+
|
|
5
|
+
from flamapy.core.models.ast import ASTOperation
|
|
6
|
+
from flamapy.core.transformations import ModelToText
|
|
7
|
+
|
|
8
|
+
from flamapy.metamodels.fm_metamodel.models import Feature, FeatureModel, Relation
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class XMLWriter(ModelToText):
|
|
14
|
+
"""Serialize a FeatureModel to FAMA XML format.
|
|
15
|
+
|
|
16
|
+
This is the counterpart of XMLReader. The output is readable by XMLReader
|
|
17
|
+
and compatible with the FaMA analysis tool.
|
|
18
|
+
|
|
19
|
+
Only requires/excludes cross-tree constraints are supported by this format.
|
|
20
|
+
Complex constraints are skipped; inspect ``skipped_constraints`` after
|
|
21
|
+
calling ``transform()`` to retrieve the ones that were dropped.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def get_destination_extension() -> str:
|
|
26
|
+
return 'xml'
|
|
27
|
+
|
|
28
|
+
def __init__(self, path: str, source_model: FeatureModel) -> None:
|
|
29
|
+
self._path = path
|
|
30
|
+
self._source_model = source_model
|
|
31
|
+
self.skipped_constraints: list[str] = []
|
|
32
|
+
|
|
33
|
+
def transform(self) -> str:
|
|
34
|
+
self.skipped_constraints = []
|
|
35
|
+
|
|
36
|
+
fm_el = ET.Element('feature-model')
|
|
37
|
+
fm_el.set('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance')
|
|
38
|
+
fm_el.set('xsi:noNamespaceSchemaLocation', 'feature-model-schema.xsd')
|
|
39
|
+
|
|
40
|
+
root_feature = self._source_model.root
|
|
41
|
+
feat_el = ET.SubElement(fm_el, 'feature')
|
|
42
|
+
feat_el.set('name', root_feature.name)
|
|
43
|
+
|
|
44
|
+
counters = [0, 0] # [br_count, sr_count]
|
|
45
|
+
_write_feature(root_feature, feat_el, counters)
|
|
46
|
+
|
|
47
|
+
requires_list, excludes_list = self._extract_binary_constraints()
|
|
48
|
+
|
|
49
|
+
re_count = 0
|
|
50
|
+
for feat_a, feat_b in requires_list:
|
|
51
|
+
re_count += 1
|
|
52
|
+
req_el = ET.SubElement(fm_el, 'requires')
|
|
53
|
+
req_el.set('name', f'Re-{re_count}')
|
|
54
|
+
req_el.set('feature', feat_a)
|
|
55
|
+
req_el.set('requires', feat_b)
|
|
56
|
+
|
|
57
|
+
ex_count = 0
|
|
58
|
+
for feat_a, feat_b in excludes_list:
|
|
59
|
+
ex_count += 1
|
|
60
|
+
exc_el = ET.SubElement(fm_el, 'excludes')
|
|
61
|
+
exc_el.set('name', f'Ex-{ex_count}')
|
|
62
|
+
exc_el.set('feature', feat_a)
|
|
63
|
+
exc_el.set('excludes', feat_b)
|
|
64
|
+
|
|
65
|
+
rough_str = ET.tostring(fm_el, encoding='unicode')
|
|
66
|
+
dom = minidom.parseString(rough_str)
|
|
67
|
+
lines = dom.toprettyxml(indent='\t').splitlines()
|
|
68
|
+
if lines and lines[0].startswith('<?xml'):
|
|
69
|
+
lines[0] = '<?xml version="1.0" encoding="UTF-8"?>'
|
|
70
|
+
xml_str = '\n'.join(lines)
|
|
71
|
+
|
|
72
|
+
if self._path is not None:
|
|
73
|
+
with open(self._path, 'w', encoding='utf-8') as fh:
|
|
74
|
+
fh.write(xml_str)
|
|
75
|
+
|
|
76
|
+
return xml_str
|
|
77
|
+
|
|
78
|
+
def _extract_binary_constraints(
|
|
79
|
+
self,
|
|
80
|
+
) -> tuple[list[tuple[str, str]], list[tuple[str, str]]]:
|
|
81
|
+
requires_list: list[tuple[str, str]] = []
|
|
82
|
+
excludes_list: list[tuple[str, str]] = []
|
|
83
|
+
for constraint in self._source_model.get_constraints():
|
|
84
|
+
raw = str(constraint)
|
|
85
|
+
ast_root = constraint.ast.root
|
|
86
|
+
op = getattr(ast_root.data, 'value', str(ast_root.data)).upper()
|
|
87
|
+
left = ast_root.left
|
|
88
|
+
right = ast_root.right
|
|
89
|
+
if (
|
|
90
|
+
left is not None
|
|
91
|
+
and right is not None
|
|
92
|
+
and left.is_term()
|
|
93
|
+
and right.is_term()
|
|
94
|
+
):
|
|
95
|
+
if op in ('REQUIRES', 'IMPLIES', '=>') or ast_root.data is ASTOperation.REQUIRES:
|
|
96
|
+
requires_list.append((str(left.data), str(right.data)))
|
|
97
|
+
continue
|
|
98
|
+
if op == 'EXCLUDES' or ast_root.data is ASTOperation.EXCLUDES:
|
|
99
|
+
excludes_list.append((str(left.data), str(right.data)))
|
|
100
|
+
continue
|
|
101
|
+
logger.warning('Constraint skipped (unsupported in XML format): %s', raw)
|
|
102
|
+
self.skipped_constraints.append(raw)
|
|
103
|
+
return requires_list, excludes_list
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _write_feature(feature: Feature, parent_el: ET.Element, counters: list[int]) -> None:
|
|
107
|
+
"""Recursively append FAMA XML child elements to *parent_el*."""
|
|
108
|
+
for relation in feature.get_relations():
|
|
109
|
+
_write_relation(relation, parent_el, counters)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _write_relation(relation: Relation, parent_el: ET.Element, counters: list[int]) -> None:
|
|
113
|
+
children = list(relation.children)
|
|
114
|
+
if len(children) == 1:
|
|
115
|
+
counters[0] += 1
|
|
116
|
+
br_el = ET.SubElement(parent_el, 'binaryRelation')
|
|
117
|
+
br_el.set('name', f'BR-{counters[0]}')
|
|
118
|
+
|
|
119
|
+
card_el = ET.SubElement(br_el, 'cardinality')
|
|
120
|
+
card_el.set('min', str(relation.card_min))
|
|
121
|
+
card_el.set('max', str(relation.card_max))
|
|
122
|
+
|
|
123
|
+
child = children[0]
|
|
124
|
+
sol_el = ET.SubElement(br_el, 'solitaryFeature')
|
|
125
|
+
sol_el.set('name', child.name)
|
|
126
|
+
_write_feature(child, sol_el, counters)
|
|
127
|
+
|
|
128
|
+
elif len(children) > 1:
|
|
129
|
+
counters[1] += 1
|
|
130
|
+
sr_el = ET.SubElement(parent_el, 'setRelation')
|
|
131
|
+
sr_el.set('name', f'SR-{counters[1]}')
|
|
132
|
+
|
|
133
|
+
card_el = ET.SubElement(sr_el, 'cardinality')
|
|
134
|
+
card_el.set('min', str(relation.card_min))
|
|
135
|
+
card_el.set('max', str(relation.card_max))
|
|
136
|
+
|
|
137
|
+
for child in children:
|
|
138
|
+
gf_el = ET.SubElement(sr_el, 'groupedFeature')
|
|
139
|
+
gf_el.set('name', child.name)
|
|
140
|
+
_write_feature(child, gf_el, counters)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flamapy-fm
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6.0.dev0
|
|
4
4
|
Summary: flamapy-fm is a plugin to Flamapy module
|
|
5
5
|
Author-email: Flamapy <flamapy@us.es>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -8,8 +8,8 @@ Project-URL: Homepage, https://github.com/flamapy/fm_metamodel
|
|
|
8
8
|
Requires-Python: >=3.9
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE
|
|
11
|
-
Requires-Dist: flamapy-fw~=2.
|
|
12
|
-
Requires-Dist: uvlparser~=2.5.0
|
|
11
|
+
Requires-Dist: flamapy-fw~=2.6.0.dev0
|
|
12
|
+
Requires-Dist: uvlparser~=2.5.0
|
|
13
13
|
Requires-Dist: afmparser~=1.0.3
|
|
14
14
|
Provides-Extra: dev
|
|
15
15
|
Requires-Dist: pytest; extra == "dev"
|
|
@@ -20,13 +20,16 @@ Requires-Dist: coverage; extra == "dev"
|
|
|
20
20
|
Requires-Dist: antlr4-tools; extra == "dev"
|
|
21
21
|
Dynamic: license-file
|
|
22
22
|
|
|
23
|
-
#
|
|
23
|
+
# flamapy-fm
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
The feature model metamodel plugin for [flamapy](https://flamapy.org): the
|
|
26
|
+
feature model classes plus readers and writers for many formats (UVL,
|
|
27
|
+
FeatureIDE, Glencoe, AFM, SPLOT, JSON, …).
|
|
26
28
|
|
|
29
|
+
**Documentation:** https://docs.flamapy.org/framework/plugins/feature_model_plugin
|
|
27
30
|
|
|
28
|
-
##
|
|
31
|
+
## Installation
|
|
29
32
|
|
|
30
|
-
```
|
|
31
|
-
pip install -
|
|
33
|
+
```bash
|
|
34
|
+
pip install flamapy-fm
|
|
32
35
|
```
|
|
@@ -38,6 +38,7 @@ flamapy/metamodels/fm_metamodel/transformations/splot_writer.py
|
|
|
38
38
|
flamapy/metamodels/fm_metamodel/transformations/uvl_reader.py
|
|
39
39
|
flamapy/metamodels/fm_metamodel/transformations/uvl_writer.py
|
|
40
40
|
flamapy/metamodels/fm_metamodel/transformations/xml_reader.py
|
|
41
|
+
flamapy/metamodels/fm_metamodel/transformations/xml_writer.py
|
|
41
42
|
flamapy/metamodels/fm_metamodel/transformations/refactorings/__init__.py
|
|
42
43
|
flamapy/metamodels/fm_metamodel/transformations/refactorings/cardinality_group_refactoring.py
|
|
43
44
|
flamapy/metamodels/fm_metamodel/transformations/refactorings/commitment_feature.py
|
|
@@ -4,15 +4,15 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "flamapy-fm"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.6.0.dev0"
|
|
8
8
|
description = "flamapy-fm is a plugin to Flamapy module"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "GPL-3.0-or-later"
|
|
11
11
|
authors = [{ name = "Flamapy", email = "flamapy@us.es" }]
|
|
12
12
|
requires-python = ">=3.9"
|
|
13
13
|
dependencies = [
|
|
14
|
-
"flamapy-fw~=2.
|
|
15
|
-
"uvlparser~=2.5.0
|
|
14
|
+
"flamapy-fw~=2.6.0.dev0",
|
|
15
|
+
"uvlparser~=2.5.0",
|
|
16
16
|
"afmparser~=1.0.3",
|
|
17
17
|
]
|
|
18
18
|
|
flamapy_fm-2.5.0.dev0/README.md
DELETED
|
File without changes
|
|
File without changes
|
{flamapy_fm-2.5.0.dev0 → flamapy_fm-2.6.0.dev0}/flamapy/metamodels/fm_metamodel/models/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|