kspl 1.1.1__py3-none-any.whl → 1.3.0__py3-none-any.whl

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.
kspl/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "1.1.1"
1
+ __version__ = "1.3.0"
kspl/_run.py CHANGED
@@ -1,9 +1,13 @@
1
- """Used to run main from the command line when run from this repository.
2
- This is required because kspl module is not visible when running
3
- from the repository."""
4
- import runpy
5
- import sys
6
- from pathlib import Path
7
-
8
- sys.path.insert(0, Path(__file__).parent.parent.absolute().as_posix())
9
- runpy.run_module("kspl.main", run_name="__main__")
1
+ """
2
+ Used to run main from the command line when run from this repository.
3
+
4
+ This is required because kspl module is not visible when running
5
+ from the repository.
6
+ """
7
+
8
+ import runpy
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ sys.path.insert(0, Path(__file__).parent.parent.absolute().as_posix())
13
+ runpy.run_module("kspl.main", run_name="__main__")
kspl/config_slurper.py CHANGED
@@ -1,85 +1,88 @@
1
- from dataclasses import dataclass
2
- from pathlib import Path
3
- from typing import Any, Dict, List, Optional
4
-
5
- from py_app_dev.core.exceptions import UserNotificationException
6
- from py_app_dev.core.logging import logger
7
-
8
- from kspl.kconfig import EditableConfigElement, KConfig
9
-
10
-
11
- @dataclass
12
- class VariantViewData:
13
- """A variant is a set of configuration values for a KConfig model."""
14
-
15
- name: str
16
- config_dict: Dict[str, Any]
17
-
18
-
19
- @dataclass
20
- class VariantData:
21
- name: str
22
- config: KConfig
23
-
24
- def find_element(self, element_name: str) -> Optional[EditableConfigElement]:
25
- return self.config.find_element(element_name)
26
-
27
-
28
- class SPLKConfigData:
29
- def __init__(self, project_root_dir: Path) -> None:
30
- self.project_root_dir = project_root_dir.absolute()
31
- variant_config_files = self._search_variant_config_file(self.project_root_dir)
32
- if not self.kconfig_model_file.is_file():
33
- raise UserNotificationException(
34
- f"File {self.kconfig_model_file} does not exist."
35
- )
36
- self.model = KConfig(self.kconfig_model_file)
37
- if variant_config_files:
38
- self.variant_configs: List[VariantData] = [
39
- VariantData(
40
- self._get_variant_name(file), KConfig(self.kconfig_model_file, file)
41
- )
42
- for file in variant_config_files
43
- ]
44
- else:
45
- self.variant_configs = [VariantData("Default", self.model)]
46
- self.logger = logger.bind()
47
-
48
- @property
49
- def kconfig_model_file(self) -> Path:
50
- return self.project_root_dir / "KConfig"
51
-
52
- def get_elements(self) -> List[EditableConfigElement]:
53
- return self.model.elements
54
-
55
- def get_variants(self) -> List[VariantViewData]:
56
- variants = []
57
-
58
- for variant in self.variant_configs:
59
- variants.append(
60
- VariantViewData(
61
- variant.name,
62
- {
63
- config_elem.name: config_elem.value
64
- for config_elem in variant.config.elements
65
- if not config_elem.is_menu
66
- },
67
- )
68
- )
69
- return variants
70
-
71
- def _get_variant_name(self, file: Path) -> str:
72
- return file.relative_to(self.project_root_dir / "variants").parent.as_posix()
73
-
74
- def _search_variant_config_file(self, project_dir: Path) -> List[Path]:
75
- """
76
- Finds all files called 'config.txt' in the variants directory
77
- and returns a list with their paths.
78
- """
79
- return list((project_dir / "variants").glob("**/config.txt"))
80
-
81
- def find_variant_config(self, variant_name: str) -> Optional[VariantData]:
82
- for variant in self.variant_configs:
83
- if variant.name == variant_name:
84
- return variant
85
- return None
1
+ from dataclasses import dataclass
2
+ from pathlib import Path
3
+ from typing import Any
4
+
5
+ from py_app_dev.core.exceptions import UserNotificationException
6
+ from py_app_dev.core.logging import logger
7
+
8
+ from kspl.kconfig import EditableConfigElement, KConfig
9
+
10
+
11
+ @dataclass
12
+ class VariantViewData:
13
+ """A variant is a set of configuration values for a KConfig model."""
14
+
15
+ name: str
16
+ config_dict: dict[str, Any]
17
+
18
+
19
+ @dataclass
20
+ class VariantData:
21
+ name: str
22
+ config: KConfig
23
+
24
+ def find_element(self, element_name: str) -> EditableConfigElement | None:
25
+ return self.config.find_element(element_name)
26
+
27
+
28
+ class SPLKConfigData:
29
+ def __init__(self, project_root_dir: Path) -> None:
30
+ self.project_root_dir = project_root_dir.absolute()
31
+ variant_config_files = self._search_variant_config_file(self.project_root_dir)
32
+ if not self.kconfig_model_file.is_file():
33
+ raise UserNotificationException(f"File {self.kconfig_model_file} does not exist.")
34
+ self.model = KConfig(self.kconfig_model_file)
35
+ if variant_config_files:
36
+ self.variant_configs: list[VariantData] = [VariantData(self._get_variant_name(file), KConfig(self.kconfig_model_file, file)) for file in variant_config_files]
37
+ else:
38
+ self.variant_configs = [VariantData("Default", self.model)]
39
+ self.logger = logger.bind()
40
+
41
+ @property
42
+ def kconfig_model_file(self) -> Path:
43
+ return self.project_root_dir / "KConfig"
44
+
45
+ def get_elements(self) -> list[EditableConfigElement]:
46
+ return self.model.elements
47
+
48
+ def get_variants(self) -> list[VariantViewData]:
49
+ variants = []
50
+
51
+ for variant in self.variant_configs:
52
+ variants.append(
53
+ VariantViewData(
54
+ variant.name,
55
+ {config_elem.name: config_elem.value for config_elem in variant.config.elements if not config_elem.is_menu},
56
+ )
57
+ )
58
+ return variants
59
+
60
+ def _get_variant_name(self, file: Path) -> str:
61
+ return file.relative_to(self.project_root_dir / "variants").parent.as_posix()
62
+
63
+ def _search_variant_config_file(self, project_dir: Path) -> list[Path]:
64
+ """Finds all files called 'config.txt' in the variants directory and returns a list with their paths."""
65
+ return list((project_dir / "variants").glob("**/config.txt"))
66
+
67
+ def find_variant_config(self, variant_name: str) -> VariantData | None:
68
+ for variant in self.variant_configs:
69
+ if variant.name == variant_name:
70
+ return variant
71
+ return None
72
+
73
+ def refresh_data(self) -> None:
74
+ """Refresh the KConfig data by reloading all configuration files."""
75
+ variant_config_files = self._search_variant_config_file(self.project_root_dir)
76
+ if not self.kconfig_model_file.is_file():
77
+ raise UserNotificationException(f"File {self.kconfig_model_file} does not exist.")
78
+
79
+ # Reload the model
80
+ self.model = KConfig(self.kconfig_model_file)
81
+
82
+ # Reload variant configurations
83
+ if variant_config_files:
84
+ self.variant_configs = [VariantData(self._get_variant_name(file), KConfig(self.kconfig_model_file, file)) for file in variant_config_files]
85
+ else:
86
+ self.variant_configs = [VariantData("Default", self.model)]
87
+
88
+ self.logger.info(f"Refreshed data: found {len(self.variant_configs)} variants")
kspl/edit.py CHANGED
@@ -1,84 +1,71 @@
1
- from argparse import ArgumentParser, Namespace
2
- from dataclasses import dataclass, field
3
- from pathlib import Path
4
- from typing import List, Optional
5
-
6
- from mashumaro import DataClassDictMixin
7
- from py_app_dev.core.cmd_line import Command, register_arguments_for_config_dataclass
8
- from py_app_dev.core.logging import logger, time_it
9
-
10
- from .config_slurper import SPLKConfigData
11
- from .kconfig import KConfig
12
-
13
-
14
- @dataclass
15
- class EditCommandConfig(DataClassDictMixin):
16
- project_dir: Path = field(
17
- default=Path(".").absolute(),
18
- metadata={
19
- "help": "Project root directory. "
20
- "Defaults to the current directory if not specified."
21
- },
22
- )
23
- kconfig_model_file: Optional[Path] = field(
24
- default=None, metadata={"help": "KConfig model file (KConfig)."}
25
- )
26
- kconfig_config_file: Optional[Path] = field(
27
- default=None, metadata={"help": "KConfig user configuration file (config.txt)."}
28
- )
29
-
30
- @classmethod
31
- def from_namespace(cls, namespace: Namespace) -> "EditCommandConfig":
32
- return cls.from_dict(vars(namespace))
33
-
34
-
35
- class EditCommand(Command):
36
- def __init__(self) -> None:
37
- super().__init__("edit", "Edit KConfig configuration.")
38
- self.logger = logger.bind()
39
-
40
- @time_it("Build")
41
- def run(self, args: Namespace) -> int:
42
- self.logger.info(f"Running {self.name} with args {args}")
43
- cmd_config = EditCommandConfig.from_namespace(args)
44
- if cmd_config.kconfig_model_file is None:
45
- kconfig_data = SPLKConfigData(cmd_config.project_dir)
46
- variants = kconfig_data.get_variants()
47
- variant_names = [variant.name for variant in variants]
48
- selected_variant = self._select_variant(variant_names)
49
- if selected_variant is not None:
50
- variant_data = kconfig_data.find_variant_config(selected_variant)
51
- if variant_data is not None:
52
- variant_data.config.menu_config()
53
- else:
54
- self.logger.error(f"Variant {selected_variant} not found.")
55
- else:
56
- KConfig(
57
- cmd_config.kconfig_model_file, cmd_config.kconfig_config_file
58
- ).menu_config()
59
- return 0
60
-
61
- def _select_variant(self, variant_names: List[str]) -> Optional[str]:
62
- """Print the list of variants to choose from and let the user choose one"""
63
- selected_variant = None
64
- self.logger.info("Select a variant:")
65
- for index, variant_name in enumerate(variant_names):
66
- self.logger.info(f" [{index + 1}] {variant_name}")
67
- while True:
68
- try:
69
- selected_variant = variant_names[
70
- int(input(f"Select a variant (1-{len(variant_names)}): ")) - 1
71
- ]
72
- break
73
- except (ValueError, IndexError):
74
- self.logger.error(
75
- "Invalid input. Please try again or press CTRL+C to abort."
76
- )
77
- # In case of KeyboardInterrupt (CTRL+C) we want to abort
78
- except KeyboardInterrupt:
79
- self.logger.warning("Aborted by user.")
80
- break
81
- return selected_variant
82
-
83
- def _register_arguments(self, parser: ArgumentParser) -> None:
84
- register_arguments_for_config_dataclass(parser, EditCommandConfig)
1
+ from argparse import ArgumentParser, Namespace
2
+ from dataclasses import dataclass, field
3
+ from pathlib import Path
4
+ from typing import Optional
5
+
6
+ from mashumaro import DataClassDictMixin
7
+ from py_app_dev.core.cmd_line import Command, register_arguments_for_config_dataclass
8
+ from py_app_dev.core.logging import logger, time_it
9
+
10
+ from .config_slurper import SPLKConfigData
11
+ from .kconfig import KConfig
12
+
13
+
14
+ @dataclass
15
+ class EditCommandConfig(DataClassDictMixin):
16
+ project_dir: Path = field(
17
+ default=Path(".").absolute(),
18
+ metadata={"help": "Project root directory. Defaults to the current directory if not specified."},
19
+ )
20
+ kconfig_model_file: Optional[Path] = field(default=None, metadata={"help": "KConfig model file (KConfig)."})
21
+ kconfig_config_file: Optional[Path] = field(default=None, metadata={"help": "KConfig user configuration file (config.txt)."})
22
+
23
+ @classmethod
24
+ def from_namespace(cls, namespace: Namespace) -> "EditCommandConfig":
25
+ return cls.from_dict(vars(namespace))
26
+
27
+
28
+ class EditCommand(Command):
29
+ def __init__(self) -> None:
30
+ super().__init__("edit", "Edit KConfig configuration.")
31
+ self.logger = logger.bind()
32
+
33
+ @time_it("Build")
34
+ def run(self, args: Namespace) -> int:
35
+ self.logger.info(f"Running {self.name} with args {args}")
36
+ cmd_config = EditCommandConfig.from_namespace(args)
37
+ if cmd_config.kconfig_model_file is None:
38
+ kconfig_data = SPLKConfigData(cmd_config.project_dir)
39
+ variants = kconfig_data.get_variants()
40
+ variant_names = [variant.name for variant in variants]
41
+ selected_variant = self._select_variant(variant_names)
42
+ if selected_variant is not None:
43
+ variant_data = kconfig_data.find_variant_config(selected_variant)
44
+ if variant_data is not None:
45
+ variant_data.config.menu_config()
46
+ else:
47
+ self.logger.error(f"Variant {selected_variant} not found.")
48
+ else:
49
+ KConfig(cmd_config.kconfig_model_file, cmd_config.kconfig_config_file).menu_config()
50
+ return 0
51
+
52
+ def _select_variant(self, variant_names: list[str]) -> str | None:
53
+ """Print the list of variants to choose from and let the user choose one."""
54
+ selected_variant = None
55
+ self.logger.info("Select a variant:")
56
+ for index, variant_name in enumerate(variant_names):
57
+ self.logger.info(f" [{index + 1}] {variant_name}")
58
+ while True:
59
+ try:
60
+ selected_variant = variant_names[int(input(f"Select a variant (1-{len(variant_names)}): ")) - 1]
61
+ break
62
+ except (ValueError, IndexError):
63
+ self.logger.error("Invalid input. Please try again or press CTRL+C to abort.")
64
+ # In case of KeyboardInterrupt (CTRL+C) we want to abort
65
+ except KeyboardInterrupt:
66
+ self.logger.warning("Aborted by user.")
67
+ break
68
+ return selected_variant
69
+
70
+ def _register_arguments(self, parser: ArgumentParser) -> None:
71
+ register_arguments_for_config_dataclass(parser, EditCommandConfig)
kspl/generate.py CHANGED
@@ -3,7 +3,7 @@ from abc import ABC, abstractmethod
3
3
  from argparse import ArgumentParser, Namespace
4
4
  from dataclasses import dataclass, field
5
5
  from pathlib import Path
6
- from typing import List, Optional
6
+ from typing import Optional
7
7
 
8
8
  import kconfiglib
9
9
  from mashumaro import DataClassDictMixin
@@ -14,9 +14,7 @@ from kspl.kconfig import ConfigElementType, ConfigurationData, KConfig, TriState
14
14
 
15
15
 
16
16
  class GeneratedFile:
17
- def __init__(
18
- self, path: Path, content: str = "", skip_writing_if_unchanged: bool = False
19
- ) -> None:
17
+ def __init__(self, path: Path, content: str = "", skip_writing_if_unchanged: bool = False) -> None:
20
18
  self.path = path
21
19
 
22
20
  self.content = content
@@ -27,50 +25,43 @@ class GeneratedFile:
27
25
  return self.content
28
26
 
29
27
  def to_file(self) -> None:
30
- """Only write to file if the content has changed.
31
- The directory of the file is created if it does not exist."""
32
-
28
+ """Only write to file if the content has changed. The directory of the file is created if it does not exist."""
33
29
  content = self.to_string()
34
30
 
35
- if (
36
- not self.path.exists()
37
- or not self.skip_writing_if_unchanged
38
- or self.path.read_text() != content
39
- ):
31
+ if not self.path.exists() or not self.skip_writing_if_unchanged or self.path.read_text() != content:
40
32
  self.path.parent.mkdir(parents=True, exist_ok=True)
41
33
  self.path.write_text(content)
42
34
 
43
35
 
44
36
  class FileWriter(ABC):
45
- """- writes the ConfigurationData to a file"""
37
+ """Writes the ConfigurationData to a file."""
46
38
 
47
39
  def __init__(self, output_file: Path):
48
40
  self.output_file = output_file
49
41
 
50
42
  def write(self, configuration_data: ConfigurationData) -> None:
51
- """- writes the ConfigurationData to a file
52
- The file shall not be modified if the content is the same as the existing one"""
43
+ """Writes the ConfigurationData to a file. The file shall not be modified if the content is the same as the existing one."""
53
44
  content = self.generate_content(configuration_data)
54
- GeneratedFile(
55
- self.output_file, content, skip_writing_if_unchanged=True
56
- ).to_file()
45
+ GeneratedFile(self.output_file, content, skip_writing_if_unchanged=True).to_file()
57
46
 
58
47
  @abstractmethod
59
48
  def generate_content(self, configuration_data: ConfigurationData) -> str:
60
- """- generates the content of the file from the ConfigurationData"""
49
+ """- generates the content of the file from the ConfigurationData."""
61
50
 
62
51
 
63
52
  class HeaderWriter(FileWriter):
64
- """Writes the ConfigurationData as pre-processor defines in a C Header file"""
53
+ """Writes the ConfigurationData as pre-processor defines in a C Header file."""
65
54
 
66
55
  config_prefix = "CONFIG_" # Prefix for all configuration defines
67
56
 
68
57
  def generate_content(self, configuration_data: ConfigurationData) -> str:
69
- """This method does exactly what the kconfiglib.write_autoconf() method does.
58
+ """
59
+ Does exactly what the kconfiglib.write_autoconf() method does.
60
+
70
61
  We had to implemented here because we refactor the file writers to use the ConfigurationData
71
62
  instead of the KConfig configuration. ConfigurationData has variable substitution already done.
72
63
  """
73
- result: List[str] = [
64
+ result: list[str] = [
74
65
  "/** @file */",
75
66
  "#ifndef __autoconf_h__",
76
67
  "#define __autoconf_h__",
@@ -91,17 +82,13 @@ class HeaderWriter(FileWriter):
91
82
  )
92
83
  elif val == TriState.M:
93
84
  add_define(
94
- "#define {}{}_MODULE 1".format(
95
- self.config_prefix, element.name
96
- ),
85
+ f"#define {self.config_prefix}{element.name}_MODULE 1",
97
86
  element.name,
98
87
  )
99
88
 
100
89
  elif element.type is ConfigElementType.STRING:
101
90
  add_define(
102
- '#define {}{} "{}"'.format(
103
- self.config_prefix, element.name, kconfiglib.escape(val)
104
- ),
91
+ f'#define {self.config_prefix}{element.name} "{kconfiglib.escape(val)}"',
105
92
  element.name,
106
93
  )
107
94
 
@@ -117,7 +104,7 @@ class HeaderWriter(FileWriter):
117
104
 
118
105
 
119
106
  class JsonWriter(FileWriter):
120
- """Writes the ConfigurationData in json format"""
107
+ """Writes the ConfigurationData in json format."""
121
108
 
122
109
  def generate_content(self, configuration_data: ConfigurationData) -> str:
123
110
  result = {}
@@ -130,10 +117,10 @@ class JsonWriter(FileWriter):
130
117
 
131
118
 
132
119
  class CMakeWriter(FileWriter):
133
- """Writes the ConfigurationData as CMake variables"""
120
+ """Writes the ConfigurationData as CMake variables."""
134
121
 
135
122
  def generate_content(self, configuration_data: ConfigurationData) -> str:
136
- result: List[str] = []
123
+ result: list[str] = []
137
124
  add = result.append
138
125
  for element in configuration_data.elements:
139
126
  val = element.value
@@ -147,12 +134,8 @@ class CMakeWriter(FileWriter):
147
134
  @dataclass
148
135
  class GenerateCommandConfig(DataClassDictMixin):
149
136
  kconfig_model_file: Path = field(metadata={"help": "KConfig model file (KConfig)."})
150
- kconfig_config_file: Optional[Path] = field(
151
- default=None, metadata={"help": "KConfig user configuration file (config.txt)."}
152
- )
153
- out_header_file: Optional[Path] = field(
154
- default=None, metadata={"help": "File to write the configuration as C header."}
155
- )
137
+ kconfig_config_file: Optional[Path] = field(default=None, metadata={"help": "KConfig user configuration file (config.txt)."})
138
+ out_header_file: Optional[Path] = field(default=None, metadata={"help": "File to write the configuration as C header."})
156
139
  out_json_file: Optional[Path] = field(
157
140
  default=None,
158
141
  metadata={"help": "File to write the configuration in JSON format."},
@@ -169,18 +152,14 @@ class GenerateCommandConfig(DataClassDictMixin):
169
152
 
170
153
  class GenerateCommand(Command):
171
154
  def __init__(self) -> None:
172
- super().__init__(
173
- "generate", "Generate the KConfig configuration in the specified formats."
174
- )
155
+ super().__init__("generate", "Generate the KConfig configuration in the specified formats.")
175
156
  self.logger = logger.bind()
176
157
 
177
158
  @time_it("Build")
178
159
  def run(self, args: Namespace) -> int:
179
160
  self.logger.info(f"Running {self.name} with args {args}")
180
161
  cmd_config = GenerateCommandConfig.from_namespace(args)
181
- config = KConfig(
182
- cmd_config.kconfig_model_file, cmd_config.kconfig_config_file
183
- ).collect_config_data()
162
+ config = KConfig(cmd_config.kconfig_model_file, cmd_config.kconfig_config_file).collect_config_data()
184
163
 
185
164
  if cmd_config.out_header_file:
186
165
  HeaderWriter(cmd_config.out_header_file).write(config)