flamapy-fm 2.1.0.dev0__tar.gz → 2.1.0.dev2__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 (64) hide show
  1. flamapy_fm-2.1.0.dev2/LICENSE +165 -0
  2. flamapy_fm-2.1.0.dev2/PKG-INFO +32 -0
  3. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/models/__init__.py +4 -2
  4. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/models/feature_model.py +57 -5
  5. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/flat_fm.py +22 -0
  6. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/glencoe_writer.py +5 -2
  7. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/refactorings/feature_cardinality_refactoring.py +5 -1
  8. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/uvl_reader.py +158 -210
  9. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/xml_reader.py +7 -5
  10. flamapy_fm-2.1.0.dev2/flamapy_fm.egg-info/PKG-INFO +32 -0
  11. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy_fm.egg-info/SOURCES.txt +1 -0
  12. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy_fm.egg-info/requires.txt +2 -2
  13. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/pyproject.toml +38 -2
  14. flamapy_fm-2.1.0.dev2/setup.py +3 -0
  15. flamapy-fm-2.1.0.dev0/PKG-INFO +0 -28
  16. flamapy-fm-2.1.0.dev0/flamapy_fm.egg-info/PKG-INFO +0 -28
  17. flamapy-fm-2.1.0.dev0/setup.py +0 -37
  18. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/README.md +0 -0
  19. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/__init__.py +0 -0
  20. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/operations/__init__.py +0 -0
  21. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/operations/fm_atomic_sets.py +0 -0
  22. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/operations/fm_average_branching_factor.py +0 -0
  23. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/operations/fm_core_features.py +0 -0
  24. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/operations/fm_count_leafs.py +0 -0
  25. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/operations/fm_estimated_configurations_number.py +0 -0
  26. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/operations/fm_feature_ancestors.py +0 -0
  27. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/operations/fm_generate_random_attribute.py +0 -0
  28. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/operations/fm_language_level.py +0 -0
  29. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/operations/fm_leaf_features.py +0 -0
  30. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/operations/fm_max_depth_tree.py +0 -0
  31. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/operations/fm_metrics.py +0 -0
  32. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/operations/fm_variation_points.py +0 -0
  33. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/operations/interfaces/__init__.py +0 -0
  34. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/operations/interfaces/variation_points.py +0 -0
  35. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/__init__.py +0 -0
  36. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/afm_reader.py +0 -0
  37. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/afm_writer.py +0 -0
  38. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/clafer_writer.py +0 -0
  39. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/featureide_reader.py +0 -0
  40. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/featureide_writer.py +0 -0
  41. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/fm_secure_features_names.py +0 -0
  42. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/glencoe_reader.py +0 -0
  43. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/json_reader.py +0 -0
  44. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/json_writer.py +0 -0
  45. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/pl_writer.py +0 -0
  46. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/pysat_to_fm.py +0 -0
  47. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/refactorings/__init__.py +0 -0
  48. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/refactorings/cardinality_group_refactoring.py +0 -0
  49. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/refactorings/commitment_feature.py +0 -0
  50. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/refactorings/deletion_feature.py +0 -0
  51. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/refactorings/feature_cardinality_refactoring_paper.py +0 -0
  52. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/refactorings/multiple_group_decomposition_refactoring.py +0 -0
  53. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/refactorings/mutex_group_refactoring.py +0 -0
  54. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/refactorings/or_mandatory_refactoring.py +0 -0
  55. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/refactorings/pseudocomplex_constraint_refactoring.py +0 -0
  56. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/refactorings/refactoring_exception.py +0 -0
  57. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/refactorings/refactoring_interface.py +0 -0
  58. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/refactorings/strictcomplex_constraint_refactoring.py +0 -0
  59. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/refactorings/xor_mandatory_refactoring.py +0 -0
  60. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/splot_writer.py +0 -0
  61. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy/metamodels/fm_metamodel/transformations/uvl_writer.py +0 -0
  62. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy_fm.egg-info/dependency_links.txt +0 -0
  63. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/flamapy_fm.egg-info/top_level.txt +0 -0
  64. {flamapy-fm-2.1.0.dev0 → flamapy_fm-2.1.0.dev2}/setup.cfg +0 -0
@@ -0,0 +1,165 @@
1
+ GNU LESSER GENERAL PUBLIC LICENSE
2
+ Version 3, 29 June 2007
3
+
4
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
5
+ Everyone is permitted to copy and distribute verbatim copies
6
+ of this license document, but changing it is not allowed.
7
+
8
+
9
+ This version of the GNU Lesser General Public License incorporates
10
+ the terms and conditions of version 3 of the GNU General Public
11
+ License, supplemented by the additional permissions listed below.
12
+
13
+ 0. Additional Definitions.
14
+
15
+ As used herein, "this License" refers to version 3 of the GNU Lesser
16
+ General Public License, and the "GNU GPL" refers to version 3 of the GNU
17
+ General Public License.
18
+
19
+ "The Library" refers to a covered work governed by this License,
20
+ other than an Application or a Combined Work as defined below.
21
+
22
+ An "Application" is any work that makes use of an interface provided
23
+ by the Library, but which is not otherwise based on the Library.
24
+ Defining a subclass of a class defined by the Library is deemed a mode
25
+ of using an interface provided by the Library.
26
+
27
+ A "Combined Work" is a work produced by combining or linking an
28
+ Application with the Library. The particular version of the Library
29
+ with which the Combined Work was made is also called the "Linked
30
+ Version".
31
+
32
+ The "Minimal Corresponding Source" for a Combined Work means the
33
+ Corresponding Source for the Combined Work, excluding any source code
34
+ for portions of the Combined Work that, considered in isolation, are
35
+ based on the Application, and not on the Linked Version.
36
+
37
+ The "Corresponding Application Code" for a Combined Work means the
38
+ object code and/or source code for the Application, including any data
39
+ and utility programs needed for reproducing the Combined Work from the
40
+ Application, but excluding the System Libraries of the Combined Work.
41
+
42
+ 1. Exception to Section 3 of the GNU GPL.
43
+
44
+ You may convey a covered work under sections 3 and 4 of this License
45
+ without being bound by section 3 of the GNU GPL.
46
+
47
+ 2. Conveying Modified Versions.
48
+
49
+ If you modify a copy of the Library, and, in your modifications, a
50
+ facility refers to a function or data to be supplied by an Application
51
+ that uses the facility (other than as an argument passed when the
52
+ facility is invoked), then you may convey a copy of the modified
53
+ version:
54
+
55
+ a) under this License, provided that you make a good faith effort to
56
+ ensure that, in the event an Application does not supply the
57
+ function or data, the facility still operates, and performs
58
+ whatever part of its purpose remains meaningful, or
59
+
60
+ b) under the GNU GPL, with none of the additional permissions of
61
+ this License applicable to that copy.
62
+
63
+ 3. Object Code Incorporating Material from Library Header Files.
64
+
65
+ The object code form of an Application may incorporate material from
66
+ a header file that is part of the Library. You may convey such object
67
+ code under terms of your choice, provided that, if the incorporated
68
+ material is not limited to numerical parameters, data structure
69
+ layouts and accessors, or small macros, inline functions and templates
70
+ (ten or fewer lines in length), you do both of the following:
71
+
72
+ a) Give prominent notice with each copy of the object code that the
73
+ Library is used in it and that the Library and its use are
74
+ covered by this License.
75
+
76
+ b) Accompany the object code with a copy of the GNU GPL and this license
77
+ document.
78
+
79
+ 4. Combined Works.
80
+
81
+ You may convey a Combined Work under terms of your choice that,
82
+ taken together, effectively do not restrict modification of the
83
+ portions of the Library contained in the Combined Work and reverse
84
+ engineering for debugging such modifications, if you also do each of
85
+ the following:
86
+
87
+ a) Give prominent notice with each copy of the Combined Work that
88
+ the Library is used in it and that the Library and its use are
89
+ covered by this License.
90
+
91
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
92
+ document.
93
+
94
+ c) For a Combined Work that displays copyright notices during
95
+ execution, include the copyright notice for the Library among
96
+ these notices, as well as a reference directing the user to the
97
+ copies of the GNU GPL and this license document.
98
+
99
+ d) Do one of the following:
100
+
101
+ 0) Convey the Minimal Corresponding Source under the terms of this
102
+ License, and the Corresponding Application Code in a form
103
+ suitable for, and under terms that permit, the user to
104
+ recombine or relink the Application with a modified version of
105
+ the Linked Version to produce a modified Combined Work, in the
106
+ manner specified by section 6 of the GNU GPL for conveying
107
+ Corresponding Source.
108
+
109
+ 1) Use a suitable shared library mechanism for linking with the
110
+ Library. A suitable mechanism is one that (a) uses at run time
111
+ a copy of the Library already present on the user's computer
112
+ system, and (b) will operate properly with a modified version
113
+ of the Library that is interface-compatible with the Linked
114
+ Version.
115
+
116
+ e) Provide Installation Information, but only if you would otherwise
117
+ be required to provide such information under section 6 of the
118
+ GNU GPL, and only to the extent that such information is
119
+ necessary to install and execute a modified version of the
120
+ Combined Work produced by recombining or relinking the
121
+ Application with a modified version of the Linked Version. (If
122
+ you use option 4d0, the Installation Information must accompany
123
+ the Minimal Corresponding Source and Corresponding Application
124
+ Code. If you use option 4d1, you must provide the Installation
125
+ Information in the manner specified by section 6 of the GNU GPL
126
+ for conveying Corresponding Source.)
127
+
128
+ 5. Combined Libraries.
129
+
130
+ You may place library facilities that are a work based on the
131
+ Library side by side in a single library together with other library
132
+ facilities that are not Applications and are not covered by this
133
+ License, and convey such a combined library under terms of your
134
+ choice, if you do both of the following:
135
+
136
+ a) Accompany the combined library with a copy of the same work based
137
+ on the Library, uncombined with any other library facilities,
138
+ conveyed under the terms of this License.
139
+
140
+ b) Give prominent notice with the combined library that part of it
141
+ is a work based on the Library, and explaining where to find the
142
+ accompanying uncombined form of the same work.
143
+
144
+ 6. Revised Versions of the GNU Lesser General Public License.
145
+
146
+ The Free Software Foundation may publish revised and/or new versions
147
+ of the GNU Lesser General Public License from time to time. Such new
148
+ versions will be similar in spirit to the present version, but may
149
+ differ in detail to address new problems or concerns.
150
+
151
+ Each version is given a distinguishing version number. If the
152
+ Library as you received it specifies that a certain numbered version
153
+ of the GNU Lesser General Public License "or any later version"
154
+ applies to it, you have the option of following the terms and
155
+ conditions either of that published version or of any later version
156
+ published by the Free Software Foundation. If the Library as you
157
+ received it does not specify a version number of the GNU Lesser
158
+ General Public License, you may choose any version of the GNU Lesser
159
+ General Public License ever published by the Free Software Foundation.
160
+
161
+ If the Library as you received it specifies that a proxy can decide
162
+ whether future versions of the GNU Lesser General Public License shall
163
+ apply, that proxy's public statement of acceptance of any version is
164
+ permanent authorization for you to choose that version for the
165
+ Library.
@@ -0,0 +1,32 @@
1
+ Metadata-Version: 2.4
2
+ Name: flamapy-fm
3
+ Version: 2.1.0.dev2
4
+ Summary: flamapy-fm is a plugin to Flamapy module
5
+ Author-email: Flamapy <flamapy@us.es>
6
+ License-Expression: GPL-3.0-or-later
7
+ Project-URL: Homepage, https://github.com/flamapy/fm_metamodel
8
+ Requires-Python: >=3.9
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: flamapy-fw~=2.1.0.dev2
12
+ Requires-Dist: uvlparser~=2.0.1.dev61
13
+ Requires-Dist: afmparser~=1.0.3
14
+ Provides-Extra: dev
15
+ Requires-Dist: pytest; extra == "dev"
16
+ Requires-Dist: pytest-mock; extra == "dev"
17
+ Requires-Dist: prospector; extra == "dev"
18
+ Requires-Dist: mypy; extra == "dev"
19
+ Requires-Dist: coverage; extra == "dev"
20
+ Requires-Dist: antlr4-tools; extra == "dev"
21
+ Dynamic: license-file
22
+
23
+ # fm_metamodel
24
+
25
+ This repo host the feature model concrete classes
26
+
27
+
28
+ ## Install for development
29
+
30
+ ```
31
+ pip install -e .
32
+ ```
@@ -7,11 +7,13 @@ from .feature_model import (
7
7
  Range,
8
8
  Attribute,
9
9
  Cardinality,
10
- FeatureType
10
+ FeatureType,
11
+ AttributeType
11
12
  )
12
13
 
13
14
  __all__ = [
14
15
  'Attribute',
16
+ 'AttributeType',
15
17
  'Cardinality',
16
18
  'Constraint',
17
19
  'Domain',
@@ -19,5 +21,5 @@ __all__ = [
19
21
  'FeatureModel',
20
22
  'FeatureType',
21
23
  'Range',
22
- 'Relation',
24
+ 'Relation'
23
25
  ]
@@ -99,6 +99,15 @@ class FeatureType(Enum):
99
99
  STRING = 'String'
100
100
 
101
101
 
102
+ class AttributeType(Enum):
103
+ BOOLEAN = 'Boolean'
104
+ INTEGER = 'Integer'
105
+ REAL = 'Real'
106
+ STRING = 'String'
107
+ VECTOR = 'Vector' # Vector (list of values)
108
+ NESTED = 'Nested' # Nested attribute (i.e., list of attributes)
109
+
110
+
102
111
  class Cardinality:
103
112
 
104
113
  def __init__(self, card_min: int = 1, card_max: int = 1):
@@ -401,6 +410,20 @@ class FeatureModel(VariabilityModel):
401
410
  features.extend(relation.children)
402
411
  return features
403
412
 
413
+ def get_attributes(self) -> list["Attribute"]:
414
+ attributes_dict: dict[str, "Attribute"] = {}
415
+ for feature in self.get_features():
416
+ attributes_dict.update({attr.name: attr for attr in feature.get_attributes()})
417
+ attributes = []
418
+ for attr in attributes_dict.values():
419
+ attribute = Attribute(attr.name, attr.domain, None, attr.null_value)
420
+ attribute.attribute_type = attr.attribute_type
421
+ attributes.append(attribute)
422
+ return attributes
423
+
424
+ def get_attribute_by_name(self, attribute_name: str) -> Optional["Attribute"]:
425
+ return next((a for a in self.get_attributes() if a.name == attribute_name), None)
426
+
404
427
  def get_boolean_features(self) -> list["Feature"]:
405
428
  return [f for f in self.get_features() if f.is_boolean()]
406
429
 
@@ -468,14 +491,23 @@ class FeatureModel(VariabilityModel):
468
491
  res += "Relations:\r\n"
469
492
  for i, relation in enumerate(self.get_relations()):
470
493
  res += f"R{i}: {relation}\r\n"
494
+ res += "Constraints:\r\n"
471
495
  for i, ctc in enumerate(self.ctcs):
472
496
  res += f"CTC{i}: {ctc}\r\n"
473
- attributes_res = ""
497
+ res += "Attributes:\r\n"
498
+ attr_counter = 0
474
499
  for feature in self.get_features():
475
500
  for attribute in feature.get_attributes():
476
- attributes_res += f"{attribute}" + "\r\n"
477
- if attributes_res != "":
478
- res += "Attributes:\r\n" + attributes_res
501
+ res += f"ATTR{attr_counter}: {attribute}" + "\r\n"
502
+ attr_counter += 1
503
+ res += "Imports:\r\n"
504
+ for i, (name, imp) in enumerate(self.imports.items()):
505
+ alias = next((alias for alias, namespace in self.alias_namespace.items()
506
+ if namespace == name), None)
507
+ if alias is None:
508
+ res += f"IMP{i}: {name} -> {imp}\r\n"
509
+ else:
510
+ res += f"IMP{i}: {name} ({alias}) -> {imp}\r\n"
479
511
  return res
480
512
 
481
513
  def __hash__(self) -> int:
@@ -560,6 +592,22 @@ class Attribute:
560
592
  self.domain: Optional["Domain"] = domain
561
593
  self.default_value: "Any" = default_value
562
594
  self.null_value: Optional[Any] = null_value
595
+ self.attribute_type: Optional["AttributeType"] = self._infer_attribute_type(default_value)
596
+
597
+ def _infer_attribute_type(self, value: Any) -> Optional["AttributeType"]:
598
+ if isinstance(value, bool):
599
+ attr_type = AttributeType.BOOLEAN
600
+ elif isinstance(value, int):
601
+ attr_type = AttributeType.INTEGER
602
+ elif isinstance(value, float):
603
+ attr_type = AttributeType.REAL
604
+ elif isinstance(value, str):
605
+ attr_type = AttributeType.STRING
606
+ elif isinstance(value, list):
607
+ attr_type = AttributeType.VECTOR
608
+ else:
609
+ attr_type = AttributeType.NESTED
610
+ return attr_type
563
611
 
564
612
  def get_name(self) -> str:
565
613
  return self.name
@@ -601,7 +649,11 @@ class Attribute:
601
649
  if self.domain is not None:
602
650
  result = result + "Domain: " + str(self.domain)
603
651
  if self.default_value is not None:
604
- result = result + "Default value: " + str(self.default_value)
652
+ if isinstance(self.default_value, list):
653
+ default_value_str = '[' + ', '.join(str(v) for v in self.default_value) + ']'
654
+ else:
655
+ default_value_str = str(self.default_value)
656
+ result = result + "Default value: " + default_value_str
605
657
  if self.null_value is not None:
606
658
  result = result + "Null value: " + str(self.null_value)
607
659
 
@@ -39,6 +39,11 @@ class FlatFM(ModelToModel):
39
39
  # Copy the feature's attributes and relations from the referenced feature
40
40
  feature.relations = feature.reference.relations
41
41
  feature.attributes.extend(feature.reference.attributes)
42
+ # Process attribute constraints
43
+ feature.constraints_attributes.extend(feature.reference.constraints_attributes)
44
+ process_attribute_constraints(feature.reference,
45
+ new_feature_model.alias_namespace,
46
+ self._maintain_namespaces)
42
47
  if not self._maintain_namespaces:
43
48
  feature.name = feature.reference.name
44
49
  else:
@@ -46,6 +51,9 @@ class FlatFM(ModelToModel):
46
51
  put_namespace_to_features(feature.reference, namespace)
47
52
  feature.reference = None # Clear reference after copying
48
53
  features.extend(feature.get_children())
54
+ # Add contraints from imported models
55
+ for imported_fm in self.feature_model.imports.values():
56
+ new_feature_model.ctcs.extend(imported_fm.ctcs)
49
57
  if not self._maintain_namespaces:
50
58
  for ctcs in new_feature_model.ctcs:
51
59
  process_namespace_constraint(ctcs.ast, new_feature_model.alias_namespace)
@@ -63,6 +71,20 @@ def put_namespace_to_features(root: Feature, namespace: str) -> None:
63
71
  features.extend(feature.get_children())
64
72
 
65
73
 
74
+ def process_attribute_constraints(root: Feature,
75
+ alias_namespace: dict[str, str],
76
+ maintain_namespaces: bool) -> None:
77
+ """Put the namespace to the constraints of the attributes constraints and return them to
78
+ incorporate in the new feature model."""
79
+ features = [root]
80
+ while features:
81
+ feature = features.pop()
82
+ for ctc in feature.constraints_attributes:
83
+ if not maintain_namespaces:
84
+ process_namespace_constraint(ctc.ast, alias_namespace)
85
+ features.extend(feature.get_children())
86
+
87
+
66
88
  def process_namespace_constraint(ast: AST, alias_namespace: dict[str, str]) -> None:
67
89
  """Replace the namespace of the features in the constraints."""
68
90
  stack = [ast.root]
@@ -1,4 +1,5 @@
1
1
  import json
2
+ import logging
2
3
  import string
3
4
  from typing import Any
4
5
 
@@ -6,6 +7,8 @@ from flamapy.core.models.ast import Node, ASTOperation
6
7
  from flamapy.core.transformations import ModelToText
7
8
  from flamapy.metamodels.fm_metamodel.models import FeatureModel, Feature, Constraint
8
9
 
10
+ logger = logging.getLogger(__name__)
11
+
9
12
 
10
13
  class GlencoeWriter(ModelToText):
11
14
  CTC_TYPES = {
@@ -38,8 +41,8 @@ class GlencoeWriter(ModelToText):
38
41
 
39
42
  def _to_json(feature_model: FeatureModel) -> dict[str, Any]:
40
43
  result: dict[str, Any] = {}
41
- print(f'-{feature_model.root.name}-')
42
- print(f'-{safename(feature_model.root.name)}-')
44
+ logger.debug("Root name: %s", feature_model.root.name)
45
+ logger.debug("Safe root name: %s", safename(feature_model.root.name))
43
46
  result["id"] = f"FM_{safename(feature_model.root.name)}"
44
47
  result["name"] = f"FM_{safename(feature_model.root.name)}"
45
48
  result["features"] = _get_features_info(feature_model.get_features())
@@ -1,4 +1,5 @@
1
1
  import copy
2
+ import logging
2
3
  from dataclasses import dataclass
3
4
  from typing import Any, cast
4
5
 
@@ -16,6 +17,8 @@ from flamapy.metamodels.fm_metamodel.transformations.refactorings import (
16
17
  RefactoringException
17
18
  )
18
19
 
20
+ logger = logging.getLogger(__name__)
21
+
19
22
 
20
23
  @dataclass
21
24
  class CloneContext:
@@ -167,7 +170,8 @@ def contextualize_constraint(feature_model: FeatureModel,
167
170
  features_names_map: dict[str, str]) -> Constraint:
168
171
  """Create a contextualized constraint for the given constraints according to the provided
169
172
  feature clone."""
170
- print(f'Contextualizing constraint {constraint.name} for features {features_names_map}')
173
+ logger.debug("Contextualizing constraint %s for features %s",
174
+ constraint.name, features_names_map)
171
175
  # Create a copy of the constraint
172
176
  new_constraint = copy.deepcopy(constraint)
173
177
  # Rename the constraint's name
@@ -46,7 +46,7 @@ class UVLReader(TextToModel):
46
46
 
47
47
  def __init__(self, path: str) -> None:
48
48
  self.path: str = os.sep.join(path.split(os.sep)[:-1])
49
- self.file: str = path.split(os.sep)[-1]
49
+ self.file: str = path.rsplit(os.sep, maxsplit=1)[-1]
50
50
  self.namespace: str = ""
51
51
  self.parse_tree: Any = None
52
52
  self.model: Optional[FeatureModel] = None
@@ -182,9 +182,41 @@ class UVLReader(TextToModel):
182
182
  if key == "abstract" and (value is None or value):
183
183
  feature.is_abstract = True
184
184
  else:
185
- feature.add_attribute(Attribute(name=str(key), default_value=value))
185
+ # Handle attributes
186
+ if value is None: # for boolean values the value may be not provided
187
+ default_value = True
188
+ elif isinstance(value, dict): # it represents nested attributes
189
+ attributes_list = self._process_nested_attribute(feature, str(key), value)
190
+ for attr in attributes_list:
191
+ feature.add_attribute(attr)
192
+ default_value = None
193
+ else:
194
+ default_value = value
195
+ feature.add_attribute(Attribute(name=str(key), default_value=default_value))
186
196
  feature.constraints_attributes = self.constraints_attributes[feature]
187
197
 
198
+ def _process_nested_attribute(self,
199
+ parent: Feature,
200
+ parent_attribute_name: str,
201
+ nested_values: dict[Any, Any]) -> list[Attribute]:
202
+ attributes = []
203
+ for key, value in nested_values.items():
204
+ if value is None: # for boolean values the value may be not provided
205
+ default_value = True
206
+ elif isinstance(value, dict):
207
+ attributes_list = self._process_nested_attribute(parent,
208
+ f'{parent_attribute_name}.{key}',
209
+ value)
210
+ for attr in attributes_list:
211
+ attributes.append(attr)
212
+ default_value = None
213
+ else:
214
+ default_value = value
215
+ attribute = Attribute(name=f'{parent_attribute_name}.{key}',
216
+ default_value=default_value)
217
+ attribute.parent = parent
218
+ attributes.append(attribute)
219
+ return attributes
188
220
 
189
221
  def _process_imported_feature(self, feature: Feature) -> None:
190
222
  feature_reference = feature.name.split('.')
@@ -272,214 +304,130 @@ class UVLReader(TextToModel):
272
304
  list_features.append(feature)
273
305
  return list_features
274
306
 
275
- def process_constraints(
276
- self, constraint_node: UVLPythonParser.ConstraintContext
277
- ) -> Node:
278
- process = self.process_literal_constraint(constraint_node)
279
- if process is None:
280
- process = self.process_logical_constraint(constraint_node)
281
- if process is None:
282
- process = self.process_arithmetic_constraint(constraint_node)
283
- if process is None:
284
- if isinstance(constraint_node, UVLPythonParser.EquationConstraintContext):
285
- process = self.process_equation_constraint(constraint_node)
286
- elif isinstance(constraint_node, UVLPythonParser.ParenthesisConstraintContext):
287
- process = self.process_parenthesis_constraint(constraint_node)
288
- elif isinstance(constraint_node, UVLPythonParser.BracketExpressionContext):
289
- process = self.process_bracket_expression_constraint(constraint_node)
290
- elif isinstance(constraint_node, UVLPythonParser.AggregateFunctionExpressionContext):
291
- process = self.process_aggregate_function_constraint(constraint_node)
292
- if process is None: # Handle unexpected constraint types
293
- raise NotImplementedError(
294
- f"Constraint of type {type(constraint_node)} not handled"
295
- )
296
- return process
297
-
298
- def process_aggregate_function_constraint(
299
- self, aggregate_function_context: UVLPythonParser.AggregateFunctionExpressionContext
300
- ) -> Node:
301
- """Process an aggregate function constraint."""
302
- aggregation = aggregate_function_context.aggregateFunction()
303
- if isinstance(aggregation, UVLPythonParser.StringAggregateFunctionExpressionContext):
304
- string_function = aggregation.stringAggregateFunction()
305
- if isinstance(string_function, UVLPythonParser.LengthAggregateFunctionContext):
306
- literal = string_function.reference()
307
- return Node(ASTOperation.LEN, Node(literal.getText().replace('"', '')))
308
- raise NotImplementedError(f"String function {type(string_function)} not handled.")
309
- if isinstance(aggregation, UVLPythonParser.NumericAggregateFunctionExpressionContext):
310
- numeric_function = aggregation.numericAggregateFunction()
311
- if isinstance(numeric_function, UVLPythonParser.FloorAggregateFunctionContext):
312
- literal = numeric_function.reference()
313
- return Node(ASTOperation.FLOOR, Node(literal.getText().replace('"', '')))
314
- if isinstance(numeric_function, UVLPythonParser.CeilAggregateFunctionContext):
315
- literal = numeric_function.reference()
316
- return Node(ASTOperation.CEIL, Node(literal.getText().replace('"', '')))
317
- raise NotImplementedError(f"Numeric function {type(numeric_function)} not handled.")
318
- if isinstance(aggregation, UVLPythonParser.AvgAggregateFunctionContext):
319
- literals = aggregation.reference()
320
- attribute_literal = literals[0].getText().replace('"', '')
321
- if len(literals) > 1:
322
- feature_literal = literals[1].getText().replace('"', '')
323
- node = Node(ASTOperation.AVG, Node(attribute_literal), Node(feature_literal))
324
- else:
325
- node = Node(ASTOperation.AVG, Node(attribute_literal))
326
- return node
327
- if isinstance(aggregation, UVLPythonParser.SumAggregateFunctionContext):
328
- literals = aggregation.reference()
329
- attribute_literal = literals[0].getText().replace('"', '')
330
- if len(literals) > 1:
331
- feature_literal = literals[1].getText().replace('"', '')
332
- node = Node(ASTOperation.SUM, Node(attribute_literal), Node(feature_literal))
333
- else:
334
- node = Node(ASTOperation.SUM, Node(attribute_literal))
335
- return node
336
- raise NotImplementedError(f"Aggregate function {type(aggregation)} not handled.")
337
-
338
- def process_literal_constraint(self, ctc_node: UVLPythonParser.ConstraintContext) -> Node:
339
- """Process a literal constraint."""
340
- process = None
341
- if isinstance(ctc_node,
342
- (UVLPythonParser.LiteralConstraintContext,
343
- UVLPythonParser.LiteralExpressionContext)):
344
- process = self.process_literal(ctc_node)
345
- elif isinstance(ctc_node, UVLPythonParser.IntegerLiteralExpressionContext):
346
- process = self.process_integer_literal_constraint(ctc_node)
347
- elif isinstance(ctc_node, UVLPythonParser.FloatLiteralExpressionContext):
348
- process = self.process_float_literal_constraint(ctc_node)
349
- elif isinstance(ctc_node, UVLPythonParser.StringLiteralExpressionContext):
350
- process = self.process_string_literal_constraint(ctc_node)
351
- return process
352
-
353
- def process_logical_constraint(self, ctc_node: UVLPythonParser.ConstraintContext) -> Node:
354
- """Process a logical constraint."""
355
- process = None
356
- operator = None
357
- if isinstance(ctc_node, UVLPythonParser.NotConstraintContext):
358
- operator = ASTOperation.NOT
359
- process = self.process_unary_constraint(ctc_node, operator)
360
- elif isinstance(ctc_node, UVLPythonParser.AndConstraintContext):
361
- operator = ASTOperation.AND
362
- elif isinstance(ctc_node, UVLPythonParser.OrConstraintContext):
363
- operator = ASTOperation.OR
364
- elif isinstance(ctc_node, UVLPythonParser.ImplicationConstraintContext):
365
- operator = ASTOperation.IMPLIES
366
- elif isinstance(ctc_node, UVLPythonParser.EquivalenceConstraintContext):
367
- operator = ASTOperation.EQUIVALENCE
368
- if operator is None: # It is other type of constraint
369
- return None
370
- if process is None: # It is a binary constraint
371
- return self.process_binary_constraints(ctc_node, operator)
372
- return process
373
-
374
- def process_arithmetic_constraint(self, ctc_node: UVLPythonParser.ConstraintContext) -> Node:
375
- """Process an arithmetic constraint."""
376
- operator = None
377
- if isinstance(ctc_node, UVLPythonParser.AddExpressionContext):
378
- operator = ASTOperation.ADD
379
- elif isinstance(ctc_node, UVLPythonParser.SubExpressionContext):
380
- operator = ASTOperation.SUB
381
- elif isinstance(ctc_node, UVLPythonParser.DivExpressionContext):
382
- operator = ASTOperation.DIV
383
- elif isinstance(ctc_node, UVLPythonParser.MulExpressionContext):
384
- operator = ASTOperation.MUL
385
- if operator is None: # It is other type of constraint
386
- return None
387
- return self.process_binary_expression(ctc_node, operator)
388
-
389
- def process_equation_constraint(
390
- self, equation_context: UVLPythonParser.EquationConstraintContext
391
- ) -> Node:
392
- """Process an equation constraint."""
393
- equation = equation_context.equation()
394
- operator = None
395
- if isinstance(equation, UVLPythonParser.EqualEquationContext):
396
- operator = ASTOperation.EQUALS
397
- elif isinstance(equation, UVLPythonParser.LowerEquationContext):
398
- operator = ASTOperation.LOWER
399
- elif isinstance(equation, UVLPythonParser.LowerEqualsEquationContext):
400
- operator = ASTOperation.LOWER_EQUALS
401
- elif isinstance(equation, UVLPythonParser.GreaterEquationContext):
402
- operator = ASTOperation.GREATER
403
- elif isinstance(equation, UVLPythonParser.GreaterEqualsEquationContext):
404
- operator = ASTOperation.GREATER_EQUALS
405
- elif isinstance(equation, UVLPythonParser.NotEqualsEquationContext):
406
- operator = ASTOperation.NOT_EQUALS
407
- else:
408
- raise NotImplementedError(f"Expression of type {type(equation)} not handled.")
409
- return self.process_binary_expression(equation, operator)
410
-
411
- def process_integer_literal_constraint(
412
- self, literal_context: UVLPythonParser.IntegerLiteralExpressionContext
413
- ) -> Node:
414
- """Process an integer literal expression."""
415
- return Node(int(literal_context.getText()))
416
-
417
- def process_float_literal_constraint(
418
- self, literal_context: UVLPythonParser.IntegerLiteralExpressionContext
419
- ) -> Node:
420
- """Process a float literal expression."""
421
- return Node(float(literal_context.getText()))
422
-
423
- def process_string_literal_constraint(
424
- self, literal_context: UVLPythonParser.IntegerLiteralExpressionContext
425
- ) -> Node:
426
- """Process a string literal expression."""
427
- return Node(literal_context.getText())
428
-
429
- def process_binary_constraints(
430
- self, context: UVLPythonParser.ConstraintContext, operation: ASTOperation
431
- ) -> Node:
432
- """Process a binary constraint."""
433
- left_constraint = context.constraint(0)
434
- right_constraint = context.constraint(1)
435
- return Node(operation,
436
- self.process_constraints(left_constraint),
437
- self.process_constraints(right_constraint)
438
- )
439
-
440
- def process_unary_constraint(
441
- self, context: UVLPythonParser.ConstraintContext, operation: ASTOperation
442
- ) -> Node:
443
- """Process a unary constraint."""
444
- inner_constraint = context.constraint()
445
- return Node(operation, self.process_constraints(inner_constraint))
446
-
447
- def process_binary_expression(self, context: Any, operation: ASTOperation) -> Node:
448
- """Process a binary expression."""
449
- left_constraint = context.expression(0)
450
- right_constraint = context.expression(1)
451
- return Node(operation,
452
- self.process_constraints(left_constraint),
453
- self.process_constraints(right_constraint)
454
- )
455
-
456
- def process_literal(
457
- self, literal_context: UVLPythonParser.LiteralConstraintContext
458
- ) -> Node:
459
- """Process a literal constraint."""
460
- literal = literal_context.reference()
461
- return Node(literal.getText().replace('"', ''))
462
-
463
- def process_parenthesis_constraint(
464
- self, parenthesis_context: UVLPythonParser.ParenthesisConstraintContext
465
- ) -> Node:
466
- """Process a parenthesis constraint."""
467
- inner_constraint = parenthesis_context.constraint()
468
- return self.process_constraints(inner_constraint)
469
-
470
- def process_bracket_expression_constraint(
471
- self, bracket_context: UVLPythonParser.BracketExpressionContext
472
- ) -> Node:
473
- """Process a bracket (parenthesis) expression constraint."""
474
- inner_constraint = bracket_context.expression()
475
- return self.process_constraints(inner_constraint)
476
-
477
- def process_not_constraint(
478
- self, not_context: UVLPythonParser.NotConstraintContext
479
- ) -> Node:
480
- """Process a not constraint."""
481
- inner_constraint = not_context.constraint()
482
- return Node(ASTOperation.NOT, self.process_constraints(inner_constraint))
307
+ def process_constraints(self, ctx: UVLPythonParser.ConstraintContext) -> Node:
308
+ # Logical operators (binary)
309
+ # Map for binary logical operators
310
+ binary_ops = {
311
+ UVLPythonParser.AndConstraintContext: ASTOperation.AND,
312
+ UVLPythonParser.OrConstraintContext: ASTOperation.OR,
313
+ UVLPythonParser.ImplicationConstraintContext: ASTOperation.IMPLIES,
314
+ UVLPythonParser.EquivalenceConstraintContext: ASTOperation.EQUIVALENCE,
315
+ }
316
+
317
+ ctx_type = type(ctx)
318
+ if ctx_type in binary_ops:
319
+ return self._binary_ctc(ctx, binary_ops[ctx_type])
320
+
321
+ # Logical operators (unary) and parenthesis
322
+ if isinstance(ctx, UVLPythonParser.NotConstraintContext):
323
+ return Node(ASTOperation.NOT, self.process_constraints(ctx.constraint()))
324
+ if isinstance(ctx, UVLPythonParser.ParenthesisConstraintContext):
325
+ return self.process_constraints(ctx.constraint())
326
+
327
+ # Leafs: Equations or simple referencesEcuaciones o Referencias simples
328
+ if isinstance(ctx, UVLPythonParser.EquationConstraintContext):
329
+ return self.process_equation(ctx.equation())
330
+ if isinstance(ctx, UVLPythonParser.LiteralConstraintContext):
331
+ return Node(ctx.reference().getText().replace('"', '')) # procesar literal
332
+
333
+ raise NotImplementedError(f"Unknown type of constraint: {type(ctx)}")
334
+
335
+ def _binary_ctc(self, ctx: Any, op: ASTOperation) -> Node:
336
+ """Helper for binary operators"""
337
+ return Node(op, self.process_constraints(ctx.constraint(0)),
338
+ self.process_constraints(ctx.constraint(1)))
339
+
340
+ def process_equation(self, ctx: UVLPythonParser.EquationContext) -> Node:
341
+ # Operators map
342
+ ops = {
343
+ UVLPythonParser.EqualEquationContext: ASTOperation.EQUALS,
344
+ UVLPythonParser.LowerEquationContext: ASTOperation.LOWER,
345
+ UVLPythonParser.GreaterEquationContext: ASTOperation.GREATER,
346
+ UVLPythonParser.LowerEqualsEquationContext: ASTOperation.LOWER_EQUALS,
347
+ UVLPythonParser.GreaterEqualsEquationContext: ASTOperation.GREATER_EQUALS,
348
+ UVLPythonParser.NotEqualsEquationContext: ASTOperation.NOT_EQUALS,
349
+ }
350
+ operator = ops.get(type(ctx))
351
+ return Node(operator, self.process_expression(ctx.expression(0)),
352
+ self.process_expression(ctx.expression(1)))
353
+
354
+ def process_expression(self, ctx: Any) -> Node:
355
+ """Handle Additive, Multiplicative y Primary Expressions"""
356
+ # Binary expressions (arithmetics)
357
+ # 1. Handle Arithmetic Binary Expressions
358
+ arithmetic_ops = {
359
+ UVLPythonParser.AddExpressionContext: ASTOperation.ADD,
360
+ UVLPythonParser.SubExpressionContext: ASTOperation.SUB,
361
+ UVLPythonParser.MulExpressionContext: ASTOperation.MUL,
362
+ UVLPythonParser.DivExpressionContext: ASTOperation.DIV,
363
+ }
364
+
365
+ ctx_type = type(ctx)
366
+ if ctx_type in arithmetic_ops:
367
+ op = arithmetic_ops[ctx_type]
368
+ return Node(op, self.process_expression(ctx.expression(0)),
369
+ self.process_expression(ctx.expression(1)))
370
+
371
+ # 2. Handle single-child expression (grammar fall-through)
372
+ if hasattr(ctx, 'expression') and callable(ctx.expression):
373
+ child = ctx.expression()
374
+ if child is not None:
375
+ return self.process_expression(child)
376
+
377
+ # 3. Delegate leaves to a specialized helper to reduce complexity
378
+ return self._process_expression_leaves(ctx)
379
+
380
+ def _process_expression_leaves(self, ctx: Any) -> Node:
381
+ """Helper to process literal leaves and primary expressions."""
382
+ if isinstance(ctx, UVLPythonParser.FloatLiteralExpressionContext):
383
+ return Node(float(ctx.getText()))
384
+ if isinstance(ctx, UVLPythonParser.IntegerLiteralExpressionContext):
385
+ return Node(int(ctx.getText()))
386
+ if isinstance(ctx, (UVLPythonParser.StringLiteralExpressionContext,
387
+ UVLPythonParser.LiteralExpressionContext)):
388
+ return Node(ctx.getText().replace('"', '').replace("'", ''))
389
+ if isinstance(ctx, UVLPythonParser.BracketExpressionContext):
390
+ return self.process_expression(ctx.expression())
391
+ if isinstance(ctx, UVLPythonParser.AggregateFunctionExpressionContext):
392
+ return self.process_aggregate(ctx.aggregateFunction())
393
+
394
+ # Default fallback
395
+ return Node(ctx.getText().replace('"', '').replace("'", ''))
396
+
397
+ def process_aggregate(self, ctx: UVLPythonParser.AggregateFunctionContext) -> Node:
398
+ # 1. SUM: aggregateFunction -> sumAggregateFunction
399
+ if isinstance(ctx, UVLPythonParser.SumAggregateFunctionExpressionContext):
400
+ sub_ctx = ctx.sumAggregateFunction()
401
+ return self._build_aggregate_node(ASTOperation.SUM, sub_ctx.reference())
402
+
403
+ # 2. AVG: aggregateFunction -> avgAggregateFunction
404
+ if isinstance(ctx, UVLPythonParser.AvgAggregateFunctionExpressionContext):
405
+ sub_ctx = ctx.avgAggregateFunction()
406
+ return self._build_aggregate_node(ASTOperation.AVG, sub_ctx.reference())
407
+
408
+ # 3. STRING: aggregateFunction -> stringAggregateFunction
409
+ if isinstance(ctx, UVLPythonParser.StringAggregateFunctionExpressionContext):
410
+ string_func = ctx.stringAggregateFunction()
411
+ # Here we handle the tag # LengthAggregateFunction
412
+ if isinstance(string_func, UVLPythonParser.LengthAggregateFunctionContext):
413
+ return Node(ASTOperation.LEN, Node(string_func.reference().getText()))
414
+
415
+ # 4. NUMERIC: aggregateFunction -> numericAggregateFunction
416
+ if isinstance(ctx, UVLPythonParser.NumericAggregateFunctionExpressionContext):
417
+ num_func = ctx.numericAggregateFunction()
418
+ ref_node = Node(num_func.reference().getText())
419
+ # Handle tags # FloorAggregateFunction and # CeilAggregateFunction
420
+ if isinstance(num_func, UVLPythonParser.FloorAggregateFunctionContext):
421
+ return Node(ASTOperation.FLOOR, ref_node)
422
+ if isinstance(num_func, UVLPythonParser.CeilAggregateFunctionContext):
423
+ return Node(ASTOperation.CEIL, ref_node)
424
+
425
+ raise NotImplementedError(f"Aggregate function not supported: {type(ctx)}")
426
+
427
+ def _build_aggregate_node(self, operation: ASTOperation, references: list[Any]) -> Node:
428
+ """Helper for Sum y Avg that can take 1 or 2 references"""
429
+ nodes = [Node(r.getText().replace('"', '')) for r in references]
430
+ return Node(operation, *nodes)
483
431
 
484
432
  def process_includes(
485
433
  self, includes_node: UVLPythonParser.IncludesContext
@@ -1,4 +1,4 @@
1
- import sys
1
+ import logging
2
2
  from typing import Optional
3
3
  from xml.etree import ElementTree
4
4
 
@@ -12,6 +12,8 @@ from flamapy.metamodels.fm_metamodel.models import (
12
12
  Relation,
13
13
  )
14
14
 
15
+ logger = logging.getLogger(__name__)
16
+
15
17
 
16
18
  class XMLReader(TextToModel):
17
19
 
@@ -39,7 +41,7 @@ class XMLReader(TextToModel):
39
41
  ctc = self.parse_ctc(child)
40
42
  feature_model.ctcs.append(ctc)
41
43
  else:
42
- print("This XML contains non supported elements", file=sys.stderr)
44
+ logger.warning("This XML contains non supported elements")
43
45
 
44
46
  return feature_model
45
47
 
@@ -83,7 +85,7 @@ class XMLReader(TextToModel):
83
85
  feature = Feature(name, [], parent=parent)
84
86
 
85
87
  if name in self.name_feature:
86
- print("This XML contains duplicated feature names", file=sys.stderr)
88
+ logger.warning("This XML contains duplicated feature names")
87
89
  raise DuplicatedFeature
88
90
 
89
91
  self.name_feature[name] = feature
@@ -113,7 +115,7 @@ class XMLReader(TextToModel):
113
115
  relation.card_min = int(str(child.attrib.get('min')))
114
116
  relation.card_max = int(str(child.attrib.get('max')))
115
117
  else:
116
- print("This XML contains non supported elements", file=sys.stderr)
118
+ logger.warning("This XML contains non supported elements")
117
119
 
118
120
  elif element.tag.casefold() == 'setrelation':
119
121
  for child in element:
@@ -124,7 +126,7 @@ class XMLReader(TextToModel):
124
126
  relation.card_min = int(str(child.attrib.get('min')))
125
127
  relation.card_max = int(str(child.attrib.get('max')))
126
128
  else:
127
- print("This XML contains non supported elements", file=sys.stderr)
129
+ logger.warning("This XML contains non supported elements")
128
130
  else:
129
131
  raise RuntimeError("Something is wrong on the xml")
130
132
  return relation
@@ -0,0 +1,32 @@
1
+ Metadata-Version: 2.4
2
+ Name: flamapy-fm
3
+ Version: 2.1.0.dev2
4
+ Summary: flamapy-fm is a plugin to Flamapy module
5
+ Author-email: Flamapy <flamapy@us.es>
6
+ License-Expression: GPL-3.0-or-later
7
+ Project-URL: Homepage, https://github.com/flamapy/fm_metamodel
8
+ Requires-Python: >=3.9
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: flamapy-fw~=2.1.0.dev2
12
+ Requires-Dist: uvlparser~=2.0.1.dev61
13
+ Requires-Dist: afmparser~=1.0.3
14
+ Provides-Extra: dev
15
+ Requires-Dist: pytest; extra == "dev"
16
+ Requires-Dist: pytest-mock; extra == "dev"
17
+ Requires-Dist: prospector; extra == "dev"
18
+ Requires-Dist: mypy; extra == "dev"
19
+ Requires-Dist: coverage; extra == "dev"
20
+ Requires-Dist: antlr4-tools; extra == "dev"
21
+ Dynamic: license-file
22
+
23
+ # fm_metamodel
24
+
25
+ This repo host the feature model concrete classes
26
+
27
+
28
+ ## Install for development
29
+
30
+ ```
31
+ pip install -e .
32
+ ```
@@ -1,3 +1,4 @@
1
+ LICENSE
1
2
  README.md
2
3
  pyproject.toml
3
4
  setup.py
@@ -1,5 +1,5 @@
1
- flamapy-fw~=2.1.0.dev0
2
- uvlparser~=2.0.1
1
+ flamapy-fw~=2.1.0.dev2
2
+ uvlparser~=2.0.1.dev61
3
3
  afmparser~=1.0.3
4
4
 
5
5
  [dev]
@@ -1,11 +1,47 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "flamapy-fm"
7
+ version = "2.1.0.dev2"
8
+ description = "flamapy-fm is a plugin to Flamapy module"
9
+ readme = "README.md"
10
+ license = "GPL-3.0-or-later"
11
+ authors = [{ name = "Flamapy", email = "flamapy@us.es" }]
12
+ requires-python = ">=3.9"
13
+ dependencies = [
14
+ "flamapy-fw~=2.1.0.dev2",
15
+ "uvlparser~=2.0.1.dev61",
16
+ "afmparser~=1.0.3",
17
+ ]
18
+
19
+ [project.optional-dependencies]
20
+ dev = [
21
+ "pytest",
22
+ "pytest-mock",
23
+ "prospector",
24
+ "mypy",
25
+ "coverage",
26
+ "antlr4-tools",
27
+ ]
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/flamapy/fm_metamodel"
31
+
32
+ [tool.setuptools.packages.find]
33
+ include = ["flamapy.*"]
34
+
1
35
  [tool.ruff]
2
36
  line-length = 100 # Matches max-line-length from pycodestyle
3
- target-version = "py38" # Adjust if using another Python version
37
+ target-version = "py39"
4
38
 
5
39
  [tool.ruff.lint]
6
40
  select = ["E", "W", "F", "C", "PL", "RUF"] # Includes pycodestyle, pylint, and McCabe complexity
7
41
  ignore = [
8
- "RUF012", # Equivalent to Pylint's super-init-not-called
42
+ "RUF012",
43
+ "RUF002",
44
+ "RUF001",
9
45
  ]
10
46
 
11
47
  [tool.ruff.lint.mccabe]
@@ -0,0 +1,3 @@
1
+ from setuptools import setup
2
+
3
+ setup()
@@ -1,28 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: flamapy-fm
3
- Version: 2.1.0.dev0
4
- Summary: flamapy-fm is a plugin to Flamapy module
5
- Home-page: https://github.com/flamapy/fm_metamodel
6
- Author: Flamapy
7
- Author-email: flamapy@us.es
8
- License: UNKNOWN
9
- Platform: UNKNOWN
10
- Classifier: Programming Language :: Python :: 3
11
- Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
12
- Classifier: Operating System :: OS Independent
13
- Requires-Python: >=3.9
14
- Description-Content-Type: text/markdown
15
- Provides-Extra: dev
16
-
17
- # fm_metamodel
18
-
19
- This repo host the feature model concrete classes
20
-
21
-
22
- ## Install for development
23
-
24
- ```
25
- pip install -e .
26
- ```
27
-
28
-
@@ -1,28 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: flamapy-fm
3
- Version: 2.1.0.dev0
4
- Summary: flamapy-fm is a plugin to Flamapy module
5
- Home-page: https://github.com/flamapy/fm_metamodel
6
- Author: Flamapy
7
- Author-email: flamapy@us.es
8
- License: UNKNOWN
9
- Platform: UNKNOWN
10
- Classifier: Programming Language :: Python :: 3
11
- Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
12
- Classifier: Operating System :: OS Independent
13
- Requires-Python: >=3.9
14
- Description-Content-Type: text/markdown
15
- Provides-Extra: dev
16
-
17
- # fm_metamodel
18
-
19
- This repo host the feature model concrete classes
20
-
21
-
22
- ## Install for development
23
-
24
- ```
25
- pip install -e .
26
- ```
27
-
28
-
@@ -1,37 +0,0 @@
1
- import setuptools
2
-
3
-
4
- with open("README.md", "r") as fh:
5
- long_description = fh.read()
6
-
7
- def read_requirements(file):
8
- with open(file, "r") as fh:
9
- return fh.read().splitlines()
10
-
11
- # Read requirements from the requirements.txt file
12
- requirements = read_requirements("requirements.txt")
13
-
14
- # Read development requirements from the dev-requirements.txt file
15
- dev_requirements = read_requirements("requirements-dev.txt")
16
-
17
- setuptools.setup(
18
- name="flamapy-fm",
19
- version="2.1.0.dev0",
20
- author="Flamapy",
21
- author_email="flamapy@us.es",
22
- description="flamapy-fm is a plugin to Flamapy module",
23
- long_description=long_description,
24
- long_description_content_type="text/markdown",
25
- url="https://github.com/flamapy/fm_metamodel",
26
- packages=setuptools.find_namespace_packages(include=['flamapy.*']),
27
- classifiers=[
28
- "Programming Language :: Python :: 3",
29
- "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
30
- "Operating System :: OS Independent",
31
- ],
32
- python_requires='>=3.9',
33
- install_requires=requirements,
34
- extras_require={
35
- 'dev': dev_requirements
36
- },
37
- )