Skip to content

Commit 2cda1e9

Browse files
committed
Feature: support for typed features in basic reader configuration
1 parent ce8b8b6 commit 2cda1e9

1 file changed

Lines changed: 72 additions & 15 deletions

File tree

Lines changed: 72 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,92 @@
11
import csv
2+
import ast
3+
from typing import Any
4+
import pathlib
25

36
from flamapy.core.transformations.text_to_model import TextToModel
7+
from flamapy.core.exceptions import ConfigurationNotFound
48

59
from flamapy.metamodels.configuration_metamodel.models.configuration import Configuration
6-
from flamapy.core.utils import file_exists
7-
from flamapy.core.exceptions import ConfigurationNotFound
810

911

1012
class ConfigurationBasicReader(TextToModel):
13+
"""Reads a configuration from a csvconf file and transforms it into a Configuration model.
14+
15+
The csvconf file should have two columns (separated by commas):
16+
- The first column contains the element names (e.g., features).
17+
- The second column contains the values for those elements.
18+
Values can be of various types, including Boolean, numeric, strings, lists, or dictionaries.
19+
20+
If the path is a directory, it will read all csvconf files in that directory and concatenate
21+
their configurations into a single Configuration object.
22+
This allows for managing multiple configurations associated with variability models split in
23+
several files (e.g., imports in UVL models).
24+
25+
If the csvconf file or directory does not exist, it raises a ConfigurationNotFound exception.
26+
"""
27+
1128
@staticmethod
1229
def get_source_extension() -> str:
1330
return 'csvconf'
1431

1532
def __init__(self, path: str) -> None:
16-
self._path = path
33+
self._path: str = path
1734

1835
def transform(self) -> Configuration:
19-
csv_reader = self.get_configuration_from_csv(self._path)
36+
path = pathlib.Path(self._path)
37+
if path.is_file():
38+
return self.get_configuration_from_csv(path)
39+
elif path.is_dir():
40+
return self.get_configuration_from_directory(path)
41+
else:
42+
raise ConfigurationNotFound
43+
44+
def get_configuration_from_csv(self, path: pathlib.Path) -> Configuration:
2045
elements = {}
21-
for row in csv_reader:
22-
# Assuming that row[1] is supposed to represent a boolean value
23-
# Convert 'true'/'false' strings to actual boolean values
24-
elements[row[0]] = row[1].lower() == 'true'
46+
with open(path, 'r', encoding='utf-8') as csvfile:
47+
csv_reader = list(csv.reader(csvfile))
48+
for row in csv_reader:
49+
element = row[0]
50+
value = convert(row[1])
51+
elements[element] = value
52+
return Configuration(elements)
53+
54+
def get_configuration_from_directory(self, directory: pathlib.Path) -> Configuration:
55+
"""Reads all CSV files in a directory and returns a Configuration object as
56+
result of concatenating all configurations."""
57+
elements = {}
58+
for filepath in directory.rglob('*.csvconf'):
59+
if filepath.is_file():
60+
config = self.get_configuration_from_csv(filepath)
61+
elements.update(config.elements)
2562
return Configuration(elements)
2663

27-
def get_configuration_from_csv(self, path: str) -> list[list[str]]:
28-
# Returns a list of list
29-
if not file_exists(path):
30-
raise ConfigurationNotFound
3164

32-
with open(path, 'r', encoding='utf-8') as csvfile:
33-
csvreader = list(csv.reader(csvfile))
65+
def convert(value: str) -> Any:
66+
"""
67+
Converts a string to the most appropriate Python type.
68+
69+
Handles:
70+
- Boolean values like 'true', 'FALSE', etc.
71+
- Numeric values (int, float)
72+
- Python literals (lists, dicts, tuples, None)
73+
- Returns the original string if no conversion is possible
74+
"""
75+
stripped = value.strip()
76+
# Empty or whitespace-only → None
77+
if stripped == '':
78+
return None
79+
80+
# Normalize and check for boolean values
81+
val_lower = value.strip().lower()
82+
if val_lower == 'true':
83+
return True
84+
if val_lower == 'false':
85+
return False
3486

35-
return csvreader
87+
try:
88+
# Try to evaluate safe Python literals (e.g. numbers, lists, tuples)
89+
return ast.literal_eval(value)
90+
except (ValueError, SyntaxError):
91+
# If evaluation fails, return the string itself
92+
return value

0 commit comments

Comments
 (0)