flamapy 2.0.0.dev4__tar.gz → 2.1.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-2.0.0.dev4 → flamapy-2.1.0.dev0}/PKG-INFO +1 -1
- {flamapy-2.0.0.dev4 → flamapy-2.1.0.dev0}/flamapy/commands/__init__.py +41 -30
- flamapy-2.1.0.dev0/flamapy/interfaces/python/__init__.py +3 -0
- {flamapy-2.0.0.dev4 → flamapy-2.1.0.dev0}/flamapy/interfaces/python/flamapy_feature_model.py +112 -98
- {flamapy-2.0.0.dev4 → flamapy-2.1.0.dev0}/flamapy.egg-info/PKG-INFO +1 -1
- {flamapy-2.0.0.dev4 → flamapy-2.1.0.dev0}/flamapy.egg-info/SOURCES.txt +1 -0
- flamapy-2.1.0.dev0/flamapy.egg-info/requires.txt +12 -0
- flamapy-2.1.0.dev0/pyproject.toml +55 -0
- {flamapy-2.0.0.dev4 → flamapy-2.1.0.dev0}/setup.py +12 -9
- flamapy-2.0.0.dev4/flamapy/interfaces/python/__init__.py +0 -5
- flamapy-2.0.0.dev4/flamapy.egg-info/requires.txt +0 -12
- {flamapy-2.0.0.dev4 → flamapy-2.1.0.dev0}/README.md +0 -0
- {flamapy-2.0.0.dev4 → flamapy-2.1.0.dev0}/flamapy.egg-info/dependency_links.txt +0 -0
- {flamapy-2.0.0.dev4 → flamapy-2.1.0.dev0}/flamapy.egg-info/entry_points.txt +0 -0
- {flamapy-2.0.0.dev4 → flamapy-2.1.0.dev0}/flamapy.egg-info/top_level.txt +0 -0
- {flamapy-2.0.0.dev4 → flamapy-2.1.0.dev0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: flamapy
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.0.dev0
|
|
4
4
|
Summary: Flamapy feature model is a distribution of the flama framework containing all plugins required to analyze feature models. It also offers a richier API and a complete command line interface and documentation.
|
|
5
5
|
Home-page: https://github.com/flamapy/flamapy-feature-model
|
|
6
6
|
Author: Flamapy
|
|
@@ -9,19 +9,21 @@ from typing import List, Tuple, Optional
|
|
|
9
9
|
from types import FunctionType
|
|
10
10
|
|
|
11
11
|
from flamapy.interfaces.python.flamapy_feature_model import FLAMAFeatureModel
|
|
12
|
+
|
|
12
13
|
# List to store registered commands and their arguments
|
|
13
14
|
MANUAL_COMMANDS = []
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
def command(name, description, *args): # type: ignore
|
|
17
|
-
|
|
18
18
|
def decorator(func): # type: ignore
|
|
19
19
|
MANUAL_COMMANDS.append((name, description, func, args))
|
|
20
20
|
|
|
21
21
|
@wraps(func)
|
|
22
22
|
def wrapper(*func_args, **func_kwargs): # type: ignore
|
|
23
23
|
return func(*func_args, **func_kwargs)
|
|
24
|
+
|
|
24
25
|
return wrapper
|
|
26
|
+
|
|
25
27
|
return decorator
|
|
26
28
|
|
|
27
29
|
|
|
@@ -38,17 +40,20 @@ def extract_commands(cls: type) -> List[Tuple[str, str, FunctionType, List[inspe
|
|
|
38
40
|
return commands
|
|
39
41
|
|
|
40
42
|
|
|
41
|
-
@command(
|
|
43
|
+
@command(
|
|
44
|
+
"generate_plugin",
|
|
45
|
+
"""This command generates a new plugin to implement your
|
|
42
46
|
cusom operations. To execute it you should set yourself in the path of the
|
|
43
|
-
flamapy src directory""",
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
flamapy src directory""",
|
|
48
|
+
("name", str, "The pluggins name"),
|
|
49
|
+
("extension", str, "The extansion to be registered with the flamapy ecosystem"),
|
|
50
|
+
("path", str, "The path to generate it"),
|
|
51
|
+
)
|
|
47
52
|
def generate_plugin(args): # type: ignore
|
|
48
53
|
name = args.name
|
|
49
54
|
ext = args.extension
|
|
50
55
|
dst = args.path
|
|
51
|
-
src =
|
|
56
|
+
src = "skel_metamodel/"
|
|
52
57
|
|
|
53
58
|
# Check DST exist
|
|
54
59
|
if not os.path.isdir(dst):
|
|
@@ -70,42 +75,48 @@ def generate_plugin(args): # type: ignore
|
|
|
70
75
|
|
|
71
76
|
copy_files = copytree(src, dst, dirs_exist_ok=True)
|
|
72
77
|
|
|
73
|
-
for copy_file in Path(copy_files).glob(
|
|
78
|
+
for copy_file in Path(copy_files).glob("**/*"):
|
|
74
79
|
if copy_file.is_dir():
|
|
75
80
|
continue
|
|
76
81
|
with open(copy_file, "r", encoding="utf-8") as file:
|
|
77
82
|
lines = file.readlines()
|
|
78
83
|
with open(copy_file, "w", encoding="utf-8") as filewrite:
|
|
79
84
|
for line in lines:
|
|
80
|
-
out_line = line.replace(
|
|
85
|
+
out_line = line.replace("__NAME__", name.capitalize()).replace("__EXT__", ext)
|
|
81
86
|
filewrite.write(out_line)
|
|
82
87
|
|
|
83
|
-
os.rename(
|
|
84
|
-
|
|
88
|
+
os.rename(
|
|
89
|
+
os.path.join(dst, "flamapy/metamodels/__NAME__"),
|
|
90
|
+
os.path.join(dst, f"flamapy/metamodels/{name}"),
|
|
91
|
+
)
|
|
85
92
|
print("Plugin generated!")
|
|
86
93
|
|
|
87
94
|
|
|
88
95
|
def setup_dynamic_commands(subparsers, dynamic_commands): # type: ignore
|
|
89
96
|
for name, docstring, method, parameters in dynamic_commands:
|
|
90
97
|
subparser = subparsers.add_parser(name, help=docstring)
|
|
91
|
-
subparser.add_argument(
|
|
92
|
-
if
|
|
93
|
-
subparser.add_argument(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
98
|
+
subparser.add_argument("model_path", type=str, help="Path to the feature model file")
|
|
99
|
+
if "configuration_path" in [param.name for param in parameters]:
|
|
100
|
+
subparser.add_argument(
|
|
101
|
+
"--configuration_path",
|
|
102
|
+
type=str,
|
|
103
|
+
help="Path to the configuration file",
|
|
104
|
+
required=False,
|
|
105
|
+
)
|
|
97
106
|
for param in parameters:
|
|
98
107
|
arg_name = param.name
|
|
99
|
-
if arg_name not in [
|
|
108
|
+
if arg_name not in ["model_path"]: # Avoid duplicates
|
|
100
109
|
if param.default == param.empty: # Positional argument
|
|
101
|
-
subparser.add_argument(
|
|
102
|
-
|
|
103
|
-
|
|
110
|
+
subparser.add_argument(
|
|
111
|
+
arg_name, type=param.annotation, help=param.annotation.__name__
|
|
112
|
+
)
|
|
104
113
|
else: # Optional argument
|
|
105
|
-
subparser.add_argument(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
114
|
+
subparser.add_argument(
|
|
115
|
+
f"--{arg_name}",
|
|
116
|
+
type=param.annotation,
|
|
117
|
+
default=param.default,
|
|
118
|
+
help=f"Optional {param.annotation.__name__}",
|
|
119
|
+
)
|
|
109
120
|
subparser.set_defaults(func=method, method_name=name, parameters=parameters)
|
|
110
121
|
|
|
111
122
|
|
|
@@ -120,7 +131,7 @@ def setup_manual_commands(subparsers, manual_commands): # type: ignore
|
|
|
120
131
|
|
|
121
132
|
def execute_command(args: argparse.Namespace) -> None:
|
|
122
133
|
try:
|
|
123
|
-
if hasattr(args,
|
|
134
|
+
if hasattr(args, "method_name"):
|
|
124
135
|
cls_instance = FLAMAFeatureModel(args.model_path)
|
|
125
136
|
method_parameters = [param.name for param in args.parameters]
|
|
126
137
|
command_args = {k: v for k, v in vars(args).items() if k in method_parameters}
|
|
@@ -130,7 +141,7 @@ def execute_command(args: argparse.Namespace) -> None:
|
|
|
130
141
|
print(result)
|
|
131
142
|
else:
|
|
132
143
|
func = args.func
|
|
133
|
-
command_args = {k: v for k, v in vars(args).items() if k !=
|
|
144
|
+
command_args = {k: v for k, v in vars(args).items() if k != "func"}
|
|
134
145
|
result = func(args)
|
|
135
146
|
if result is not None:
|
|
136
147
|
print(result)
|
|
@@ -147,8 +158,8 @@ def execute_command(args: argparse.Namespace) -> None:
|
|
|
147
158
|
|
|
148
159
|
|
|
149
160
|
def flamapy_cli() -> None:
|
|
150
|
-
parser = argparse.ArgumentParser(description=
|
|
151
|
-
subparsers = parser.add_subparsers(dest=
|
|
161
|
+
parser = argparse.ArgumentParser(description="FLAMA Feature Model CLI")
|
|
162
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
152
163
|
|
|
153
164
|
dynamic_commands = extract_commands(FLAMAFeatureModel)
|
|
154
165
|
setup_dynamic_commands(subparsers, dynamic_commands)
|
|
@@ -165,4 +176,4 @@ def flamapy_cli() -> None:
|
|
|
165
176
|
print("Framework developers operations:")
|
|
166
177
|
for name, description, _, _ in MANUAL_COMMANDS:
|
|
167
178
|
print(f" {name}: {description}")
|
|
168
|
-
print("Execute flamapy --help for more information")
|
|
179
|
+
print("Execute flamapy --help for more information")
|
{flamapy-2.0.0.dev4 → flamapy-2.1.0.dev0}/flamapy/interfaces/python/flamapy_feature_model.py
RENAMED
|
@@ -5,11 +5,10 @@ from flamapy.core.exceptions import FlamaException
|
|
|
5
5
|
from flamapy.metamodels.configuration_metamodel.models import Configuration
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
class FLAMAFeatureModel:
|
|
9
|
-
|
|
8
|
+
class FLAMAFeatureModel:
|
|
10
9
|
def __init__(self, model_path: str):
|
|
11
10
|
"""
|
|
12
|
-
This is the path in the filesystem where the model is located.
|
|
11
|
+
This is the path in the filesystem where the model is located.
|
|
13
12
|
Any model in UVL, FaMaXML or FeatureIDE format are accepted
|
|
14
13
|
"""
|
|
15
14
|
self.model_path = model_path
|
|
@@ -28,7 +27,7 @@ class FLAMAFeatureModel:
|
|
|
28
27
|
self.bdd_model = None
|
|
29
28
|
|
|
30
29
|
def _read(self, model_path: str) -> FeatureModel:
|
|
31
|
-
return self.discover_metamodel.use_transformation_t2m(model_path,
|
|
30
|
+
return self.discover_metamodel.use_transformation_t2m(model_path, "fm")
|
|
32
31
|
|
|
33
32
|
def _transform_to_sat(self) -> None:
|
|
34
33
|
if self.sat_model is None:
|
|
@@ -39,17 +38,18 @@ class FLAMAFeatureModel:
|
|
|
39
38
|
self.bdd_model = self.discover_metamodel.use_transformation_m2m(self.fm_model, "bdd")
|
|
40
39
|
|
|
41
40
|
def atomic_sets(self) -> Union[None, List[List[Any]]]:
|
|
42
|
-
"""
|
|
41
|
+
"""
|
|
43
42
|
This operation is used to find the atomic sets in a model:
|
|
44
|
-
It returns the atomic sets if they are found in the model.
|
|
45
|
-
If the model does not follow the UVL specification, an
|
|
43
|
+
It returns the atomic sets if they are found in the model.
|
|
44
|
+
If the model does not follow the UVL specification, an
|
|
46
45
|
exception is raised and the operation returns False.
|
|
47
46
|
"""
|
|
48
47
|
|
|
49
48
|
# Try to use the Find operation, which returns the atomic sets if they are found
|
|
50
49
|
try:
|
|
51
|
-
atomic_sets = self.discover_metamodel.use_operation(
|
|
52
|
-
|
|
50
|
+
atomic_sets = self.discover_metamodel.use_operation(
|
|
51
|
+
self.fm_model, "FMAtomicSets"
|
|
52
|
+
).get_result()
|
|
53
53
|
result = []
|
|
54
54
|
for atomic_set in atomic_sets:
|
|
55
55
|
partial_set = []
|
|
@@ -62,66 +62,69 @@ class FLAMAFeatureModel:
|
|
|
62
62
|
return None
|
|
63
63
|
|
|
64
64
|
def average_branching_factor(self) -> Union[None, float]:
|
|
65
|
-
"""
|
|
66
|
-
This refers to the average number of child features that a parent feature has in a
|
|
67
|
-
feature model. It's calculated by dividing the total number of child features by the
|
|
68
|
-
total number of parent features. A high average branching factor indicates a complex
|
|
69
|
-
feature model with many options, while a low average branching factor indicates a
|
|
65
|
+
"""
|
|
66
|
+
This refers to the average number of child features that a parent feature has in a
|
|
67
|
+
feature model. It's calculated by dividing the total number of child features by the
|
|
68
|
+
total number of parent features. A high average branching factor indicates a complex
|
|
69
|
+
feature model with many options, while a low average branching factor indicates a
|
|
70
70
|
simpler model.
|
|
71
71
|
"""
|
|
72
72
|
|
|
73
73
|
# Try to use the Find operation, which returns the atomic sets if they are found
|
|
74
74
|
try:
|
|
75
|
-
result = self.discover_metamodel.use_operation(
|
|
76
|
-
|
|
75
|
+
result = self.discover_metamodel.use_operation(
|
|
76
|
+
self.fm_model, "FMAverageBranchingFactor"
|
|
77
|
+
).get_result()
|
|
77
78
|
return result
|
|
78
79
|
except FlamaException as exception:
|
|
79
80
|
print(f"Error: {exception}")
|
|
80
81
|
return None
|
|
81
82
|
|
|
82
83
|
def count_leafs(self) -> Union[None, int]:
|
|
83
|
-
"""
|
|
84
|
-
This operation counts the number of leaf features in a feature model. Leaf features
|
|
85
|
-
are those that do not have any child features. They represent the most specific
|
|
84
|
+
"""
|
|
85
|
+
This operation counts the number of leaf features in a feature model. Leaf features
|
|
86
|
+
are those that do not have any child features. They represent the most specific
|
|
86
87
|
options in a product line.
|
|
87
88
|
"""
|
|
88
89
|
|
|
89
90
|
# Try to use the Find operation, which returns the atomic sets if they are found
|
|
90
91
|
try:
|
|
91
|
-
result = self.discover_metamodel.use_operation(
|
|
92
|
-
|
|
92
|
+
result = self.discover_metamodel.use_operation(
|
|
93
|
+
self.fm_model, "FMCountLeafs"
|
|
94
|
+
).get_result()
|
|
93
95
|
return result
|
|
94
96
|
except FlamaException as exception:
|
|
95
97
|
print(f"Error: {exception}")
|
|
96
98
|
return None
|
|
97
99
|
|
|
98
100
|
def estimated_number_of_configurations(self) -> Union[None, int]:
|
|
99
|
-
"""
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
101
|
+
"""
|
|
102
|
+
This is an estimate of the total number of different products that can be produced
|
|
103
|
+
from a feature model. It's calculated by considering all possible combinations of
|
|
104
|
+
features. This can be a simple multiplication if all features are independent, but
|
|
105
|
+
in most cases, constraints and dependencies between features need to be taken
|
|
106
|
+
into account.
|
|
105
107
|
"""
|
|
106
108
|
|
|
107
109
|
# Try to use the Find operation, which returns the atomic sets if they are found
|
|
108
110
|
try:
|
|
109
111
|
result = self.discover_metamodel.use_operation(
|
|
110
|
-
self.fm_model,
|
|
112
|
+
self.fm_model, "FMEstimatedConfigurationsNumber"
|
|
113
|
+
).get_result()
|
|
111
114
|
return result
|
|
112
115
|
except FlamaException as exception:
|
|
113
116
|
print(f"Error: {exception}")
|
|
114
117
|
return None
|
|
115
118
|
|
|
116
119
|
def feature_ancestors(self, feature_name: str) -> Union[None, List[str]]:
|
|
117
|
-
|
|
118
|
-
These are the features that are directly or indirectly the parent of a given feature in
|
|
119
|
-
a feature model. Ancestors of a feature are found by traversing up the feature hierarchy.
|
|
120
|
+
"""
|
|
121
|
+
These are the features that are directly or indirectly the parent of a given feature in
|
|
122
|
+
a feature model. Ancestors of a feature are found by traversing up the feature hierarchy.
|
|
120
123
|
This information can be useful to understand the context and dependencies of a feature.
|
|
121
|
-
|
|
124
|
+
"""
|
|
122
125
|
# Try to use the Find operation, which returns the atomic sets if they are found
|
|
123
126
|
try:
|
|
124
|
-
operation = self.discover_metamodel.get_operation(self.fm_model,
|
|
127
|
+
operation = self.discover_metamodel.get_operation(self.fm_model, "FMFeatureAncestors")
|
|
125
128
|
operation.set_feature(self.fm_model.get_feature_by_name(feature_name))
|
|
126
129
|
operation.execute(self.fm_model)
|
|
127
130
|
flama_result = operation.get_result()
|
|
@@ -134,22 +137,23 @@ class FLAMAFeatureModel:
|
|
|
134
137
|
return None
|
|
135
138
|
|
|
136
139
|
def leaf_features(self) -> Union[None, List[str]]:
|
|
137
|
-
"""
|
|
140
|
+
"""
|
|
138
141
|
This operation is used to find leaf features in a model:
|
|
139
|
-
It returns the leaf features if they are found in the model.
|
|
140
|
-
If the model does not follow the UVL specification, an
|
|
142
|
+
It returns the leaf features if they are found in the model.
|
|
143
|
+
If the model does not follow the UVL specification, an
|
|
141
144
|
exception is raised and the operation returns False.
|
|
142
145
|
|
|
143
|
-
Traditionally you would use the flama tool by
|
|
146
|
+
Traditionally you would use the flama tool by
|
|
144
147
|
features = discover_metamodel.use_operation_from_file('OperationString', model)
|
|
145
|
-
however, in this tool we know that this operation is from the fm metamodel,
|
|
148
|
+
however, in this tool we know that this operation is from the fm metamodel,
|
|
146
149
|
so we avoid to execute the transformation if possible
|
|
147
150
|
"""
|
|
148
151
|
|
|
149
152
|
# Try to use the operation, which returns the leaf features if they are found
|
|
150
153
|
try:
|
|
151
|
-
features = self.discover_metamodel.use_operation(
|
|
152
|
-
|
|
154
|
+
features = self.discover_metamodel.use_operation(
|
|
155
|
+
self.fm_model, "FMLeafFeatures"
|
|
156
|
+
).get_result()
|
|
153
157
|
leaf_features = []
|
|
154
158
|
for feature in features:
|
|
155
159
|
leaf_features.append(feature.name)
|
|
@@ -159,34 +163,36 @@ class FLAMAFeatureModel:
|
|
|
159
163
|
return None
|
|
160
164
|
|
|
161
165
|
def max_depth(self) -> Union[None, int]:
|
|
162
|
-
"""
|
|
166
|
+
"""
|
|
163
167
|
This operation is used to find the max depth of the tree in a model:
|
|
164
|
-
It returns the max depth of the tree.
|
|
165
|
-
If the model does not follow the UVL specification, an
|
|
168
|
+
It returns the max depth of the tree.
|
|
169
|
+
If the model does not follow the UVL specification, an
|
|
166
170
|
exception is raised and the operation returns False.
|
|
167
171
|
"""
|
|
168
172
|
|
|
169
173
|
# Try to use the Find operation, which returns the max depth of the tree
|
|
170
174
|
try:
|
|
171
|
-
return self.discover_metamodel.use_operation(
|
|
172
|
-
|
|
175
|
+
return self.discover_metamodel.use_operation(
|
|
176
|
+
self.fm_model, "FMMaxDepthTree"
|
|
177
|
+
).get_result()
|
|
173
178
|
except FlamaException as exception:
|
|
174
179
|
print(f"Error: {exception}")
|
|
175
180
|
return None
|
|
176
181
|
|
|
177
|
-
#The methods above rely on sat to be executed.
|
|
182
|
+
# The methods above rely on sat to be executed.
|
|
178
183
|
def core_features(self) -> Union[None, List[str]]:
|
|
179
184
|
"""
|
|
180
|
-
These are the features that are present in all products of a product line.
|
|
181
|
-
In a feature model, they are the features that are mandatory and not optional.
|
|
182
|
-
Core features define the commonality among all products in a product line.
|
|
185
|
+
These are the features that are present in all products of a product line.
|
|
186
|
+
In a feature model, they are the features that are mandatory and not optional.
|
|
187
|
+
Core features define the commonality among all products in a product line.
|
|
183
188
|
This call requires sat to be called, however, there is an implementation within
|
|
184
|
-
flamapy that does not requires sat. please use the framework in case of needing it.
|
|
189
|
+
flamapy that does not requires sat. please use the framework in case of needing it.
|
|
185
190
|
"""
|
|
186
191
|
try:
|
|
187
192
|
self._transform_to_sat()
|
|
188
|
-
features = self.discover_metamodel.use_operation(
|
|
189
|
-
|
|
193
|
+
features = self.discover_metamodel.use_operation(
|
|
194
|
+
self.sat_model, "PySATCoreFeatures"
|
|
195
|
+
).get_result()
|
|
190
196
|
return features
|
|
191
197
|
except FlamaException as exception:
|
|
192
198
|
print(f"Error: {exception}")
|
|
@@ -194,14 +200,15 @@ class FLAMAFeatureModel:
|
|
|
194
200
|
|
|
195
201
|
def dead_features(self) -> Union[None, List[str]]:
|
|
196
202
|
"""
|
|
197
|
-
These are features that, due to the constraints and dependencies in the
|
|
198
|
-
feature model, cannot be included in any valid product. Dead features are usually
|
|
203
|
+
These are features that, due to the constraints and dependencies in the
|
|
204
|
+
feature model, cannot be included in any valid product. Dead features are usually
|
|
199
205
|
a sign of an error in the feature model.
|
|
200
206
|
"""
|
|
201
207
|
try:
|
|
202
208
|
self._transform_to_sat()
|
|
203
|
-
features = self.discover_metamodel.use_operation(
|
|
204
|
-
|
|
209
|
+
features = self.discover_metamodel.use_operation(
|
|
210
|
+
self.sat_model, "PySATDeadFeatures"
|
|
211
|
+
).get_result()
|
|
205
212
|
return features
|
|
206
213
|
except FlamaException as exception:
|
|
207
214
|
print(f"Error: {exception}")
|
|
@@ -209,14 +216,15 @@ class FLAMAFeatureModel:
|
|
|
209
216
|
|
|
210
217
|
def false_optional_features(self) -> Union[None, List[str]]:
|
|
211
218
|
"""
|
|
212
|
-
These are features that appear to be optional in the feature model, but due to the
|
|
213
|
-
constraints and dependencies, must be included in every valid product. Like dead features,
|
|
219
|
+
These are features that appear to be optional in the feature model, but due to the
|
|
220
|
+
constraints and dependencies, must be included in every valid product. Like dead features,
|
|
214
221
|
false optional features are usually a sign of an error in the feature model.
|
|
215
222
|
"""
|
|
216
223
|
try:
|
|
217
224
|
self._transform_to_sat()
|
|
218
|
-
operation = self.discover_metamodel.get_operation(
|
|
219
|
-
|
|
225
|
+
operation = self.discover_metamodel.get_operation(
|
|
226
|
+
self.sat_model, "PySATFalseOptionalFeatures"
|
|
227
|
+
)
|
|
220
228
|
operation.feature_model = self.fm_model
|
|
221
229
|
operation.execute(self.sat_model)
|
|
222
230
|
features = operation.get_result()
|
|
@@ -227,15 +235,16 @@ class FLAMAFeatureModel:
|
|
|
227
235
|
|
|
228
236
|
def filter(self, configuration_path: str) -> Union[None, List[Configuration]]:
|
|
229
237
|
"""
|
|
230
|
-
This operation selects a subset of the products of a product line based on certain
|
|
231
|
-
criteria. For example, you might filter the products to only include those that
|
|
238
|
+
This operation selects a subset of the products of a product line based on certain
|
|
239
|
+
criteria. For example, you might filter the products to only include those that
|
|
232
240
|
contain a certain feature.
|
|
233
241
|
"""
|
|
234
242
|
try:
|
|
235
243
|
self._transform_to_sat()
|
|
236
|
-
configuration = self.discover_metamodel.use_transformation_t2m(
|
|
237
|
-
|
|
238
|
-
|
|
244
|
+
configuration = self.discover_metamodel.use_transformation_t2m(
|
|
245
|
+
configuration_path, "configuration"
|
|
246
|
+
)
|
|
247
|
+
operation = self.discover_metamodel.get_operation(self.sat_model, "PySATFilter")
|
|
239
248
|
operation.set_configuration(configuration)
|
|
240
249
|
operation.execute(self.sat_model)
|
|
241
250
|
result = operation.get_result()
|
|
@@ -246,9 +255,9 @@ class FLAMAFeatureModel:
|
|
|
246
255
|
|
|
247
256
|
def configurations_number(self, with_sat: bool = False) -> Union[None, int]:
|
|
248
257
|
"""
|
|
249
|
-
This is the total number of different full configurations that can be
|
|
250
|
-
produced from a feature model. It's calculated by considering all possible
|
|
251
|
-
combinations of features, taking into account the constraints and
|
|
258
|
+
This is the total number of different full configurations that can be
|
|
259
|
+
produced from a feature model. It's calculated by considering all possible
|
|
260
|
+
combinations of features, taking into account the constraints and
|
|
252
261
|
dependencies between features.
|
|
253
262
|
"""
|
|
254
263
|
try:
|
|
@@ -256,12 +265,12 @@ class FLAMAFeatureModel:
|
|
|
256
265
|
if with_sat:
|
|
257
266
|
self._transform_to_sat()
|
|
258
267
|
nop = self.discover_metamodel.use_operation(
|
|
259
|
-
self.sat_model,
|
|
268
|
+
self.sat_model, "PySATConfigurationsNumber"
|
|
260
269
|
).get_result()
|
|
261
270
|
else:
|
|
262
271
|
self._transform_to_bdd()
|
|
263
272
|
nop = self.discover_metamodel.use_operation(
|
|
264
|
-
self.bdd_model,
|
|
273
|
+
self.bdd_model, "BDDConfigurationsNumber"
|
|
265
274
|
).get_result()
|
|
266
275
|
return nop
|
|
267
276
|
except FlamaException as exception:
|
|
@@ -270,22 +279,22 @@ class FLAMAFeatureModel:
|
|
|
270
279
|
|
|
271
280
|
def configurations(self, with_sat: bool = False) -> Union[None, List[Configuration]]:
|
|
272
281
|
"""
|
|
273
|
-
These are the individual outcomes that can be produced from a feature model. Each product
|
|
274
|
-
is a combination of features that satisfies all the constraints and dependencies in the
|
|
282
|
+
These are the individual outcomes that can be produced from a feature model. Each product
|
|
283
|
+
is a combination of features that satisfies all the constraints and dependencies in the
|
|
275
284
|
feature model.
|
|
276
285
|
"""
|
|
277
286
|
try:
|
|
278
287
|
products = []
|
|
279
288
|
if with_sat:
|
|
280
289
|
self._transform_to_sat()
|
|
281
|
-
products = self.discover_metamodel.use_operation(
|
|
282
|
-
|
|
283
|
-
|
|
290
|
+
products = self.discover_metamodel.use_operation(
|
|
291
|
+
self.sat_model, "PySATConfigurations"
|
|
292
|
+
).get_result()
|
|
284
293
|
else:
|
|
285
294
|
self._transform_to_bdd()
|
|
286
|
-
products = self.discover_metamodel.use_operation(
|
|
287
|
-
|
|
288
|
-
|
|
295
|
+
products = self.discover_metamodel.use_operation(
|
|
296
|
+
self.bdd_model, "BDDConfigurations"
|
|
297
|
+
).get_result()
|
|
289
298
|
return products
|
|
290
299
|
except FlamaException as exception:
|
|
291
300
|
print(f"Error: {exception}")
|
|
@@ -293,16 +302,17 @@ class FLAMAFeatureModel:
|
|
|
293
302
|
|
|
294
303
|
def commonality(self, configuration_path: str) -> Union[None, float]:
|
|
295
304
|
"""
|
|
296
|
-
This is a measure of how often a feature appears in the products of a
|
|
297
|
-
product line. It's usually expressed as a percentage. A feature with
|
|
305
|
+
This is a measure of how often a feature appears in the products of a
|
|
306
|
+
product line. It's usually expressed as a percentage. A feature with
|
|
298
307
|
100 per cent commonality is a core feature, as it appears in all products.
|
|
299
308
|
"""
|
|
300
309
|
try:
|
|
301
310
|
self._transform_to_sat()
|
|
302
|
-
configuration = self.discover_metamodel.use_transformation_t2m(
|
|
303
|
-
|
|
311
|
+
configuration = self.discover_metamodel.use_transformation_t2m(
|
|
312
|
+
configuration_path, "configuration"
|
|
313
|
+
)
|
|
304
314
|
|
|
305
|
-
operation = self.discover_metamodel.get_operation(self.sat_model,
|
|
315
|
+
operation = self.discover_metamodel.get_operation(self.sat_model, "PySATCommonality")
|
|
306
316
|
operation.set_configuration(configuration)
|
|
307
317
|
operation.execute(self.sat_model)
|
|
308
318
|
return operation.get_result()
|
|
@@ -310,25 +320,28 @@ class FLAMAFeatureModel:
|
|
|
310
320
|
print(f"Error: {exception}")
|
|
311
321
|
return None
|
|
312
322
|
|
|
313
|
-
def satisfiable_configuration(
|
|
314
|
-
|
|
315
|
-
|
|
323
|
+
def satisfiable_configuration(
|
|
324
|
+
self, configuration_path: str, full_configuration: bool = False
|
|
325
|
+
) -> Union[None, bool]:
|
|
316
326
|
"""
|
|
317
|
-
This is a product that is produced from a valid configuration of features. A valid
|
|
327
|
+
This is a product that is produced from a valid configuration of features. A valid
|
|
318
328
|
product satisfies all the constraints and dependencies in the feature model.
|
|
319
329
|
"""
|
|
320
330
|
try:
|
|
321
331
|
self._transform_to_sat()
|
|
322
|
-
configuration = self.discover_metamodel.use_transformation_t2m(
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
332
|
+
configuration = self.discover_metamodel.use_transformation_t2m(
|
|
333
|
+
configuration_path, "configuration"
|
|
334
|
+
)
|
|
335
|
+
operation = self.discover_metamodel.get_operation(
|
|
336
|
+
self.sat_model, "PySATSatisfiableConfiguration"
|
|
337
|
+
)
|
|
326
338
|
|
|
327
339
|
if full_configuration:
|
|
328
|
-
|
|
340
|
+
configuration.is_full = True
|
|
329
341
|
else:
|
|
330
|
-
|
|
342
|
+
configuration.is_full = False
|
|
331
343
|
|
|
344
|
+
operation.set_configuration(configuration)
|
|
332
345
|
operation.execute(self.sat_model)
|
|
333
346
|
result = operation.get_result()
|
|
334
347
|
return result
|
|
@@ -338,15 +351,16 @@ class FLAMAFeatureModel:
|
|
|
338
351
|
|
|
339
352
|
def satisfiable(self) -> Union[None, bool]:
|
|
340
353
|
"""
|
|
341
|
-
In the context of feature models, this usually refers to whether the feature model itself
|
|
342
|
-
satisfies all the constraints and dependencies. A a valid feature model is one that
|
|
354
|
+
In the context of feature models, this usually refers to whether the feature model itself
|
|
355
|
+
satisfies all the constraints and dependencies. A a valid feature model is one that
|
|
343
356
|
does encodes at least a single valid product.
|
|
344
357
|
"""
|
|
345
358
|
try:
|
|
346
359
|
self._transform_to_sat()
|
|
347
|
-
result = self.discover_metamodel.use_operation(
|
|
348
|
-
|
|
360
|
+
result = self.discover_metamodel.use_operation(
|
|
361
|
+
self.sat_model, "PySATSatisfiable"
|
|
362
|
+
).get_result()
|
|
349
363
|
return result
|
|
350
364
|
except FlamaException as exception:
|
|
351
365
|
print(f"Error: {exception}")
|
|
352
|
-
return None
|
|
366
|
+
return None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: flamapy
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.0.dev0
|
|
4
4
|
Summary: Flamapy feature model is a distribution of the flama framework containing all plugins required to analyze feature models. It also offers a richier API and a complete command line interface and documentation.
|
|
5
5
|
Home-page: https://github.com/flamapy/flamapy-feature-model
|
|
6
6
|
Author: Flamapy
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[tool.ruff]
|
|
2
|
+
line-length = 100 # Matches max-line-length from pycodestyle
|
|
3
|
+
target-version = "py38" # Adjust if using another Python version
|
|
4
|
+
|
|
5
|
+
[tool.ruff.lint]
|
|
6
|
+
select = ["E", "W", "F", "C", "PL", "RUF"] # Includes pycodestyle, pylint, and McCabe complexity
|
|
7
|
+
ignore = [
|
|
8
|
+
"RUF012", # Equivalent to Pylint's super-init-not-called
|
|
9
|
+
"RUF002", # Weird symbols on docstring
|
|
10
|
+
"RUF001", # Weird symbols string
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[tool.ruff.lint.mccabe]
|
|
14
|
+
max-complexity = 10 # Equivalent to enabling mccabe in Prospector
|
|
15
|
+
|
|
16
|
+
[tool.ruff.lint.per-file-ignores]
|
|
17
|
+
"flamapy/metamodels/fm_metamodel/transformations/pysat_to_fm.py" = ["ALL"] # Ignore this file entirely
|
|
18
|
+
"tests/*" = ["ALL"] # Ignore all test files
|
|
19
|
+
"build/*" = ["ALL"]
|
|
20
|
+
"resources/*" = ["ALL"]
|
|
21
|
+
".mypy_cache/*" = ["ALL"]
|
|
22
|
+
"venv/*" = ["ALL"]
|
|
23
|
+
".venv/*" = ["ALL"]
|
|
24
|
+
"env/*" = ["ALL"]
|
|
25
|
+
"flamapy_fm.egg-info/*" = ["ALL"]
|
|
26
|
+
"__pycache__/*" = ["ALL"]
|
|
27
|
+
"skel_metamodel/*" = ["ALL"]
|
|
28
|
+
"flamapy/__init__.py" = ["ALL"]
|
|
29
|
+
|
|
30
|
+
[tool.ruff.lint.pycodestyle]
|
|
31
|
+
max-line-length = 100 # Matches your existing pycodestyle setting
|
|
32
|
+
|
|
33
|
+
# MYPY CONFIGURATION
|
|
34
|
+
[tool.mypy]
|
|
35
|
+
scripts_are_modules = true
|
|
36
|
+
show_traceback = true
|
|
37
|
+
|
|
38
|
+
# Strict checking
|
|
39
|
+
check_untyped_defs = true
|
|
40
|
+
disallow_untyped_defs = true
|
|
41
|
+
disallow_any_generics = true
|
|
42
|
+
warn_no_return = true
|
|
43
|
+
strict_optional = true
|
|
44
|
+
no_implicit_optional = true
|
|
45
|
+
warn_redundant_casts = true
|
|
46
|
+
warn_unused_ignores = true
|
|
47
|
+
|
|
48
|
+
# Show error codes for type: ignore comments
|
|
49
|
+
show_error_codes = true
|
|
50
|
+
|
|
51
|
+
# Suppress missing imports errors
|
|
52
|
+
ignore_missing_imports = true
|
|
53
|
+
|
|
54
|
+
# Warn about unreachable or redundant code
|
|
55
|
+
warn_unreachable = true
|
|
@@ -4,10 +4,12 @@ import setuptools
|
|
|
4
4
|
with open("README.md", "r") as fh:
|
|
5
5
|
long_description = fh.read()
|
|
6
6
|
|
|
7
|
+
|
|
7
8
|
def read_requirements(file):
|
|
8
9
|
with open(file, "r") as fh:
|
|
9
10
|
return fh.read().splitlines()
|
|
10
11
|
|
|
12
|
+
|
|
11
13
|
# Read requirements from the requirements.txt file
|
|
12
14
|
requirements = read_requirements("requirements.txt")
|
|
13
15
|
|
|
@@ -16,27 +18,28 @@ dev_requirements = read_requirements("requirements-dev.txt")
|
|
|
16
18
|
|
|
17
19
|
setuptools.setup(
|
|
18
20
|
name="flamapy",
|
|
19
|
-
version="2.
|
|
21
|
+
version="2.1.0.dev0",
|
|
20
22
|
author="Flamapy",
|
|
21
23
|
author_email="flamapy@us.es",
|
|
22
|
-
description="Flamapy feature model is a distribution of the
|
|
24
|
+
description="Flamapy feature model is a distribution of the " \
|
|
25
|
+
"flama framework containing all plugins required to analyze " \
|
|
26
|
+
"feature models. It also offers a richier API and a complete " \
|
|
27
|
+
"command line interface and documentation.",
|
|
23
28
|
long_description=long_description,
|
|
24
29
|
long_description_content_type="text/markdown",
|
|
25
30
|
url="https://github.com/flamapy/flamapy-feature-model",
|
|
26
|
-
packages=setuptools.find_namespace_packages(include=[
|
|
31
|
+
packages=setuptools.find_namespace_packages(include=["flamapy.*"]),
|
|
27
32
|
classifiers=[
|
|
28
33
|
"Programming Language :: Python :: 3",
|
|
29
34
|
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
|
|
30
35
|
"Operating System :: OS Independent",
|
|
31
36
|
],
|
|
32
|
-
python_requires=
|
|
37
|
+
python_requires=">=3.9",
|
|
33
38
|
install_requires=requirements,
|
|
34
|
-
extras_require={
|
|
35
|
-
'dev': dev_requirements
|
|
36
|
-
},
|
|
39
|
+
extras_require={"dev": dev_requirements},
|
|
37
40
|
entry_points={
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
"console_scripts": [
|
|
42
|
+
"flamapy = flamapy.commands:flamapy_cli",
|
|
40
43
|
],
|
|
41
44
|
},
|
|
42
45
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|