config-cli-gui 0.0.2__tar.gz → 0.1.1__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 (85) hide show
  1. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.idea/runConfigurations/config_generate.xml +2 -2
  2. config_cli_gui-0.1.1/HISTORY.md +16 -0
  3. {config_cli_gui-0.0.2/src/config_cli_gui.egg-info → config_cli_gui-0.1.1}/PKG-INFO +5 -7
  4. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/README.md +4 -2
  5. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/docs/index.md +4 -2
  6. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/pyproject.toml +0 -4
  7. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/src/config_cli_gui/_version.py +2 -2
  8. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/src/config_cli_gui/cli_generator.py +7 -5
  9. config_cli_gui-0.1.1/src/config_cli_gui/config_framework.py +217 -0
  10. config_cli_gui-0.1.1/src/config_cli_gui/docs_generator.py +190 -0
  11. config_cli_gui-0.1.1/src/config_cli_gui/gui_generator.py +541 -0
  12. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1/src/config_cli_gui.egg-info}/PKG-INFO +5 -7
  13. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/src/config_cli_gui.egg-info/SOURCES.txt +15 -15
  14. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/src/config_cli_gui.egg-info/requires.txt +0 -4
  15. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/src/config_cli_gui.egg-info/top_level.txt +0 -1
  16. {config_cli_gui-0.0.2/src → config_cli_gui-0.1.1/tests}/example_project/config/config.py +10 -26
  17. config_cli_gui-0.1.1/tests/example_project/core/base.py +53 -0
  18. {config_cli_gui-0.0.2/src → config_cli_gui-0.1.1/tests}/example_project/gui/gui.py +4 -4
  19. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/tests/test_generic_cli.py +9 -13
  20. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/uv.lock +51 -293
  21. config_cli_gui-0.0.2/.idea/runConfigurations/module_cli.xml +0 -25
  22. config_cli_gui-0.0.2/HISTORY.md +0 -4
  23. config_cli_gui-0.0.2/src/config_cli_gui/config_framework.py +0 -362
  24. config_cli_gui-0.0.2/src/config_cli_gui/gui_generator.py +0 -225
  25. config_cli_gui-0.0.2/src/example_project/core/base.py +0 -634
  26. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.github/FUNDING.yml +0 -0
  27. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  28. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  29. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  30. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.github/actions/setup-environment/action.yml +0 -0
  31. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.github/dependabot.yml +0 -0
  32. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.github/init.sh +0 -0
  33. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.github/release_message.sh +0 -0
  34. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.github/update_funding.py +0 -0
  35. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.github/workflows/build-macos.yml +0 -0
  36. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.github/workflows/build.yml +0 -0
  37. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.github/workflows/main.yml +0 -0
  38. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.github/workflows/release.yml +0 -0
  39. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.github/workflows/update_readme.yml +0 -0
  40. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.gitignore +0 -0
  41. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.idea/runConfigurations/module_gui.xml +0 -0
  42. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.pre-commit-config.yaml +0 -0
  43. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.readthedocs.yaml +0 -0
  44. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/LICENSE +0 -0
  45. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/Makefile +0 -0
  46. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/config.yaml +0 -0
  47. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/docs/.nav.yml +0 -0
  48. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/docs/_static/img/favicon.png +0 -0
  49. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/docs/_static/img/logo.png +0 -0
  50. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/docs/css/custom.css +0 -0
  51. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/docs/develop/contributing.md +0 -0
  52. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/docs/develop/make_windows.md +0 -0
  53. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/docs/develop/naming_convention.md +0 -0
  54. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/docs/develop/pypi_release.md +0 -0
  55. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/docs/funding/funding.md +0 -0
  56. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/docs/getting-started/install.md +0 -0
  57. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/docs/getting-started/virtual-environment.md +0 -0
  58. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/docs/usage/cli.md +0 -0
  59. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/docs/usage/config.md +0 -0
  60. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/examples/kuhkopfsteig.gpx +0 -0
  61. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/examples/rother_lilienstein.gpx +0 -0
  62. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/examples/teneriffa.gpx +0 -0
  63. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/mkdocs.yml +0 -0
  64. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/scripts/show_filelist.ps1 +0 -0
  65. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/scripts/show_tree.ps1 +0 -0
  66. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/scripts/show_tree.py +0 -0
  67. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/scripts/update_readme.py +0 -0
  68. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/setup.cfg +0 -0
  69. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/src/__init__.py +0 -0
  70. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/src/config_cli_gui/__init__.py +0 -0
  71. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/src/config_cli_gui.egg-info/dependency_links.txt +0 -0
  72. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/src/config_cli_gui.egg-info/entry_points.txt +0 -0
  73. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/src/main.py +0 -0
  74. {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/template.yml.url +0 -0
  75. {config_cli_gui-0.0.2/src/example_project → config_cli_gui-0.1.1/tests}/__init__.py +0 -0
  76. {config_cli_gui-0.0.2/src/example_project/cli → config_cli_gui-0.1.1/tests/example_project}/__init__.py +0 -0
  77. {config_cli_gui-0.0.2/src → config_cli_gui-0.1.1/tests}/example_project/__main__.py +0 -0
  78. {config_cli_gui-0.0.2/src/example_project/config → config_cli_gui-0.1.1/tests/example_project/cli}/__init__.py +0 -0
  79. {config_cli_gui-0.0.2/src → config_cli_gui-0.1.1/tests}/example_project/cli/__main__.py +0 -0
  80. {config_cli_gui-0.0.2/src → config_cli_gui-0.1.1/tests}/example_project/cli/cli.py +0 -0
  81. {config_cli_gui-0.0.2/src/example_project/core → config_cli_gui-0.1.1/tests/example_project/config}/__init__.py +0 -0
  82. {config_cli_gui-0.0.2/src/example_project/gui → config_cli_gui-0.1.1/tests/example_project/core}/__init__.py +0 -0
  83. {config_cli_gui-0.0.2/src → config_cli_gui-0.1.1/tests}/example_project/core/logging.py +0 -0
  84. {config_cli_gui-0.0.2/tests → config_cli_gui-0.1.1/tests/example_project/gui}/__init__.py +0 -0
  85. {config_cli_gui-0.0.2/src → config_cli_gui-0.1.1/tests}/example_project/gui/__main__.py +0 -0
@@ -8,12 +8,12 @@
8
8
  <env name="PYTHONUNBUFFERED" value="1" />
9
9
  </envs>
10
10
  <option name="SDK_HOME" value="" />
11
- <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/src/config_cli_gui" />
11
+ <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/tests/example_project/config" />
12
12
  <option name="IS_MODULE_SDK" value="true" />
13
13
  <option name="ADD_CONTENT_ROOTS" value="true" />
14
14
  <option name="ADD_SOURCE_ROOTS" value="true" />
15
15
  <EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
16
- <option name="SCRIPT_NAME" value="$PROJECT_DIR$/src/config_cli_gui/config/config.py" />
16
+ <option name="SCRIPT_NAME" value="$PROJECT_DIR$/tests/example_project/config/config.py" />
17
17
  <option name="PARAMETERS" value="" />
18
18
  <option name="SHOW_COMMAND_LINE" value="false" />
19
19
  <option name="EMULATE_TERMINAL" value="false" />
@@ -0,0 +1,16 @@
1
+ Changelog
2
+ =========
3
+
4
+
5
+ (unreleased)
6
+ ------------
7
+ - Remove unnecessary example files and deps. [Paul Magister]
8
+ - Update README.md from docs/index.md. [github-actions]
9
+ - Fix doc: formatting. [Paul Magister]
10
+
11
+
12
+ 0.0.2 (2025-06-22)
13
+ ------------------
14
+ - Remove _version.py. [Paul Magister]
15
+
16
+
@@ -1,17 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: config-cli-gui
3
- Version: 0.0.2
3
+ Version: 0.1.1
4
4
  Summary: Feature-rich Python project template for config-cli-gui.
5
5
  Author: pamagister
6
6
  Requires-Python: <3.12,>=3.10
7
7
  Description-Content-Type: text/markdown
8
8
  License-File: LICENSE
9
- Requires-Dist: fastkml>=1.1.0
10
- Requires-Dist: gpxpy>=1.6.2
11
9
  Requires-Dist: pydantic>=2.11.7
12
10
  Requires-Dist: pyyaml>=6.0.2
13
- Requires-Dist: shapely>=2.1.1
14
- Requires-Dist: srtm-py>=0.3.7
15
11
  Provides-Extra: dev
16
12
  Requires-Dist: pytest>=8.4.0; extra == "dev"
17
13
  Requires-Dist: pytest-mock>=3.14.1; extra == "dev"
@@ -31,7 +27,9 @@ Requires-Dist: mkdocs-awesome-nav>=2.6.1; extra == "docs"
31
27
  Requires-Dist: pygments>=2.19.1; extra == "docs"
32
28
  Dynamic: license-file
33
29
 
34
- # config-cli-gui: Unified Configuration and Interface Management
30
+ # Welcome to config-cli-gui
31
+
32
+ **Unified Configuration and Interface Management**
35
33
 
36
34
  Provides a generic configuration framework that automatically generates both command-line interfaces and GUI settings dialogs from configuration parameters.
37
35
 
@@ -57,7 +55,7 @@ You can install `config-cli-gui` using pip:
57
55
 
58
56
  ```bash
59
57
  pip install config-cli-gui
60
- ````
58
+ ```
61
59
 
62
60
  ---
63
61
 
@@ -1,6 +1,8 @@
1
1
  <!-- This README.md is auto-generated from docs/index.md -->
2
2
 
3
- # config-cli-gui: Unified Configuration and Interface Management
3
+ # Welcome to config-cli-gui
4
+
5
+ **Unified Configuration and Interface Management**
4
6
 
5
7
  Provides a generic configuration framework that automatically generates both command-line interfaces and GUI settings dialogs from configuration parameters.
6
8
 
@@ -26,7 +28,7 @@ You can install `config-cli-gui` using pip:
26
28
 
27
29
  ```bash
28
30
  pip install config-cli-gui
29
- ````
31
+ ```
30
32
 
31
33
  ---
32
34
 
@@ -1,4 +1,6 @@
1
- # config-cli-gui: Unified Configuration and Interface Management
1
+ # Welcome to config-cli-gui
2
+
3
+ **Unified Configuration and Interface Management**
2
4
 
3
5
  Provides a generic configuration framework that automatically generates both command-line interfaces and GUI settings dialogs from configuration parameters.
4
6
 
@@ -24,7 +26,7 @@ You can install `config-cli-gui` using pip:
24
26
 
25
27
  ```bash
26
28
  pip install config-cli-gui
27
- ````
29
+ ```
28
30
 
29
31
  ---
30
32
 
@@ -8,12 +8,8 @@ authors = [
8
8
  readme = "docs/index.md"
9
9
  requires-python = ">=3.10,<3.12"
10
10
  dependencies = [
11
- "fastkml>=1.1.0",
12
- "gpxpy>=1.6.2",
13
11
  "pydantic>=2.11.7",
14
12
  "pyyaml>=6.0.2",
15
- "shapely>=2.1.1",
16
- "srtm-py>=0.3.7",
17
13
  ]
18
14
 
19
15
  # Dev dependencies as optional dependencies
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.0.2'
21
- __version_tuple__ = version_tuple = (0, 0, 2)
20
+ __version__ = version = '0.1.1'
21
+ __version_tuple__ = version_tuple = (0, 1, 1)
@@ -46,6 +46,8 @@ class CliGenerator:
46
46
 
47
47
  # Generate arguments from CLI config parameters
48
48
  for param in cli_params:
49
+ param_type = type(param.default)
50
+
49
51
  if param.required and param.cli_arg is None:
50
52
  # Positional argument
51
53
  parser.add_argument(param.name, help=param.help)
@@ -57,17 +59,17 @@ class CliGenerator:
57
59
  }
58
60
 
59
61
  # Handle different parameter types
60
- if param.choices and param.type_ != bool:
62
+ if param.choices and param_type != bool:
61
63
  kwargs["choices"] = param.choices
62
64
 
63
- if param.type_ == int:
65
+ if param_type == int:
64
66
  kwargs["type"] = int
65
- elif param.type_ == float:
67
+ elif param_type == float:
66
68
  kwargs["type"] = float
67
- elif param.type_ == bool:
69
+ elif param_type == bool:
68
70
  kwargs["action"] = "store_true" if not param.default else "store_false"
69
71
  kwargs["help"] = f"{param.help} (default: {param.default})"
70
- elif param.type_ == str:
72
+ elif param_type == str:
71
73
  kwargs["type"] = str
72
74
 
73
75
  parser.add_argument(param.cli_arg, **kwargs)
@@ -0,0 +1,217 @@
1
+ # config_framework/core.py
2
+ """Generic configuration framework for Python applications."""
3
+
4
+ import json
5
+ from abc import ABC, abstractmethod
6
+ from dataclasses import dataclass
7
+ from datetime import datetime
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ import yaml
12
+ from pydantic import BaseModel
13
+
14
+
15
+ class Color:
16
+ """Simple color class for RGB values."""
17
+
18
+ def __init__(self, r: int = 0, g: int = 0, b: int = 0):
19
+ self.r = max(0, min(255, r))
20
+ self.g = max(0, min(255, g))
21
+ self.b = max(0, min(255, b))
22
+
23
+ def to_list(self) -> list[int]:
24
+ return [self.r, self.g, self.b]
25
+
26
+ def to_hex(self) -> str:
27
+ return f"#{self.r:02x}{self.g:02x}{self.b:02x}"
28
+
29
+ @classmethod
30
+ def from_list(cls, rgb_list: list[int]) -> "Color":
31
+ if len(rgb_list) >= 3:
32
+ return cls(rgb_list[0], rgb_list[1], rgb_list[2])
33
+ return cls()
34
+
35
+ @classmethod
36
+ def from_hex(cls, hex_color: str) -> "Color":
37
+ hex_color = hex_color.lstrip("#")
38
+ if len(hex_color) == 6:
39
+ return cls(int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16))
40
+ return cls()
41
+
42
+ def __str__(self):
43
+ return self.to_hex()
44
+
45
+ def __repr__(self):
46
+ return f"Color({self.r}, {self.g}, {self.b})"
47
+
48
+
49
+ @dataclass
50
+ class ConfigParameter:
51
+ """Represents a single configuration parameter with all its metadata."""
52
+
53
+ name: str
54
+ default: Any
55
+ choices: list | tuple | None = None
56
+ help: str = ""
57
+ cli_arg: str = None
58
+ required: bool = False
59
+ is_cli: bool = False
60
+ category: str = "general"
61
+
62
+ def __post_init__(self):
63
+ if self.is_cli and self.cli_arg is None and not self.required:
64
+ self.cli_arg = f"--{self.name}"
65
+ if isinstance(self.default, bool) and self.choices is None:
66
+ self.choices = [True, False]
67
+
68
+ @property
69
+ def type_(self) -> type:
70
+ """Get the type from the default value."""
71
+ return type(self.default)
72
+
73
+
74
+ class BaseConfigCategory(BaseModel, ABC):
75
+ """Base class for configuration categories."""
76
+
77
+ @abstractmethod
78
+ def get_category_name(self) -> str:
79
+ """Return the category name for this configuration group."""
80
+ pass
81
+
82
+ def get_parameters(self) -> list[ConfigParameter]:
83
+ """Get all ConfigParameter objects from this category."""
84
+ parameters = []
85
+ for field_name in self.__class__.model_fields:
86
+ param = getattr(self, field_name)
87
+ if isinstance(param, ConfigParameter):
88
+ param.category = self.get_category_name()
89
+ parameters.append(param)
90
+ return parameters
91
+
92
+
93
+ class ConfigManager:
94
+ """Generic configuration manager that can handle multiple configuration categories."""
95
+
96
+ def __init__(self, config_file: str = None, **kwargs):
97
+ """Initialize configuration manager.
98
+
99
+ Args:
100
+ config_file: Path to configuration file (JSON or YAML)
101
+ **kwargs: Override parameters in format category__parameter
102
+ """
103
+ self._categories: dict[str, BaseConfigCategory] = {}
104
+
105
+ # Load from file if provided
106
+ if config_file:
107
+ self.load_from_file(config_file)
108
+
109
+ # Override with provided kwargs
110
+ self._apply_kwargs(kwargs)
111
+
112
+ def add_category(self, name: str, category: BaseConfigCategory):
113
+ """Add a configuration category.
114
+
115
+ Args:
116
+ name: Name of the category (e.g., 'app', 'database', 'gui')
117
+ category: Configuration category instance
118
+ """
119
+ self._categories[name] = category
120
+
121
+ def get_category(self, name: str) -> BaseConfigCategory:
122
+ """Get a configuration category by name."""
123
+ return self._categories.get(name)
124
+
125
+ def _apply_kwargs(self, kwargs: dict[str, Any]):
126
+ """Apply keyword arguments to override configuration values."""
127
+ for key, value in kwargs.items():
128
+ if "__" in key:
129
+ category_name, param_name = key.split("__", 1)
130
+ if category_name in self._categories:
131
+ category = self._categories[category_name]
132
+ if hasattr(category, param_name):
133
+ param = getattr(category, param_name)
134
+ if isinstance(param, ConfigParameter):
135
+ param.default = value
136
+
137
+ def load_from_file(self, config_file: str):
138
+ """Load configuration from JSON or YAML file."""
139
+ config_path = Path(config_file)
140
+ if not config_path.exists():
141
+ raise FileNotFoundError(f"Configuration file not found: {config_file}")
142
+
143
+ with open(config_path, "r", encoding="utf-8") as f:
144
+ if config_path.suffix.lower() in [".yml", ".yaml"]:
145
+ config_data = yaml.safe_load(f)
146
+ else:
147
+ config_data = json.load(f)
148
+
149
+ # Apply loaded configuration
150
+ for category_name, category_data in config_data.items():
151
+ if category_name in self._categories:
152
+ category = self._categories[category_name]
153
+ for param_name, param_value in category_data.items():
154
+ if hasattr(category, param_name):
155
+ param = getattr(category, param_name)
156
+ if isinstance(param, ConfigParameter):
157
+ # Handle special types
158
+ if isinstance(param.default, Color) and isinstance(param_value, list):
159
+ param.default = Color.from_list(param_value)
160
+ elif isinstance(param.default, Path):
161
+ param.default = Path(param_value)
162
+ elif isinstance(param.default, datetime):
163
+ param.default = datetime.fromisoformat(param_value)
164
+ else:
165
+ param.default = param_value
166
+
167
+ def save_to_file(self, config_file: str, format_: str = "auto"):
168
+ """Save current configuration to file."""
169
+ config_path = Path(config_file)
170
+ config_data = self.to_dict()
171
+
172
+ # Determine format
173
+ if format_ == "auto":
174
+ format_ = "yaml" if config_path.suffix.lower() in [".yml", ".yaml"] else "json"
175
+
176
+ # Ensure directory exists
177
+ config_path.parent.mkdir(parents=True, exist_ok=True)
178
+
179
+ with open(config_path, "w", encoding="utf-8") as f:
180
+ if format_ == "yaml":
181
+ yaml.dump(config_data, f, default_flow_style=False, indent=2)
182
+ else:
183
+ json.dump(config_data, f, indent=2)
184
+
185
+ def to_dict(self) -> dict[str, Any]:
186
+ """Convert configuration to dictionary."""
187
+ result = {}
188
+ for category_name, category in self._categories.items():
189
+ category_dict = {}
190
+ for param in category.get_parameters():
191
+ value = param.default
192
+ # Handle special types for serialization
193
+ if isinstance(value, Color):
194
+ value = value.to_list()
195
+ elif isinstance(value, Path):
196
+ value = str(value)
197
+ elif isinstance(value, datetime):
198
+ value = value.isoformat()
199
+ category_dict[param.name] = value
200
+ result[category_name] = category_dict
201
+ return result
202
+
203
+ def get_all_parameters(self) -> list[ConfigParameter]:
204
+ """Get all parameters from all categories."""
205
+ parameters = []
206
+ for category in self._categories.values():
207
+ parameters.extend(category.get_parameters())
208
+ return parameters
209
+
210
+ def get_cli_parameters(self) -> list[ConfigParameter]:
211
+ """Get parameters that are CLI-enabled."""
212
+ cli_parameters = []
213
+ for category in self._categories.values():
214
+ for param in category.get_parameters():
215
+ if param.is_cli:
216
+ cli_parameters.append(param)
217
+ return cli_parameters
@@ -0,0 +1,190 @@
1
+ from pathlib import Path
2
+ from textwrap import dedent
3
+
4
+ from config_cli_gui.config_framework import ConfigManager
5
+
6
+
7
+ class DocumentationGenerator:
8
+ """Generates documentation and configuration files from ConfigManager."""
9
+
10
+ def __init__(self, config_manager: ConfigManager):
11
+ self.config_manager = config_manager
12
+
13
+ def generate_config_markdown_doc(self, output_file: str):
14
+ """Generate Markdown documentation for all configuration parameters."""
15
+
16
+ def pad(s, width):
17
+ return s + " " * (width - len(s))
18
+
19
+ markdown_content = dedent("""
20
+ # Configuration Parameters
21
+
22
+ These parameters are available to configure the behavior of your application.
23
+ The parameters in the cli category can be accessed via the command line interface.
24
+
25
+ """).lstrip()
26
+
27
+ for category_name, category in self.config_manager._categories.items():
28
+ markdown_content += f'## Category "{category_name}"\n\n'
29
+
30
+ # Collect all parameters for this category
31
+ rows = []
32
+ header = ["Name", "Type", "Description", "Default", "Choices"]
33
+
34
+ for param in category.get_parameters():
35
+ name = param.name
36
+ typ = type(param.default).__name__
37
+ desc = param.help
38
+ default = repr(param.default)
39
+ choices = str(param.choices) if param.choices else "-"
40
+
41
+ rows.append((name, typ, desc, default, choices))
42
+
43
+ if not rows:
44
+ continue
45
+
46
+ # Calculate column widths
47
+ all_rows = [header] + rows
48
+ widths = [max(len(str(col)) for col in column) for column in zip(*all_rows)]
49
+
50
+ # Create Markdown table
51
+ table = (
52
+ "| "
53
+ + " | ".join(pad(h, w) for h, w in zip(header, widths))
54
+ + " |\n"
55
+ + "|-"
56
+ + "-|-".join("-" * w for w in widths)
57
+ + "-|\n"
58
+ )
59
+ for row in rows:
60
+ table += "| " + " | ".join(pad(str(col), w) for col, w in zip(row, widths)) + " |\n"
61
+
62
+ markdown_content += table + "\n"
63
+
64
+ # Write to file
65
+ Path(output_file).parent.mkdir(parents=True, exist_ok=True)
66
+ with open(output_file, "w", encoding="utf-8") as f:
67
+ f.write(markdown_content)
68
+
69
+ def generate_default_config_file(self, output_file: str):
70
+ """Generate a default configuration file with all parameters and descriptions."""
71
+ output_path = Path(output_file)
72
+ output_path.parent.mkdir(parents=True, exist_ok=True)
73
+
74
+ with open(output_path, "w", encoding="utf-8") as f:
75
+ f.write("# Configuration File\n")
76
+ f.write("# This file was auto-generated. Modify as needed.\n\n")
77
+
78
+ for category_name, category in self.config_manager._categories.items():
79
+ f.write(f"# {category_name.upper()} Configuration\n")
80
+ f.write(f"{category_name}:\n")
81
+
82
+ for param in category.get_parameters():
83
+ f.write(f" # {param.help}\n")
84
+ if param.choices:
85
+ f.write(f" # Choices: {param.choices}\n")
86
+ f.write(f" # Type: {type(param.default).__name__}\n")
87
+ f.write(f" {param.name}: {repr(param.default)}\n\n")
88
+
89
+ f.write("\n")
90
+
91
+ def generate_cli_markdown_doc(self, output_file: str, app_name: str = "app"):
92
+ """Generate Markdown CLI documentation."""
93
+ cli_params = self.config_manager.get_cli_parameters()
94
+
95
+ if not cli_params:
96
+ return
97
+
98
+ rows = []
99
+ required_params = []
100
+ optional_params = []
101
+
102
+ for param in cli_params:
103
+ cli_arg = f"`--{param.name}`" if not param.required else f"`{param.name}`"
104
+ typ = type(param.default).__name__
105
+ desc = param.help
106
+ default = (
107
+ "*required*"
108
+ if param.required or param.default in (None, "")
109
+ else repr(param.default)
110
+ )
111
+ choices = str(param.choices) if param.choices else "-"
112
+
113
+ rows.append((cli_arg, typ, desc, default, choices))
114
+ if default == "*required*":
115
+ required_params.append(param)
116
+ else:
117
+ optional_params.append(param)
118
+
119
+ # Generate table
120
+ def pad(s, width):
121
+ return s + " " * (width - len(s))
122
+
123
+ widths = [max(len(str(col)) for col in column) for column in zip(*rows)]
124
+ header = ["Option", "Type", "Description", "Default", "Choices"]
125
+
126
+ table = (
127
+ "| "
128
+ + " | ".join(pad(h, w) for h, w in zip(header, widths))
129
+ + " |\n"
130
+ + "|-"
131
+ + "-|-".join("-" * w for w in widths)
132
+ + "-|\n"
133
+ )
134
+ for row in rows:
135
+ table += "| " + " | ".join(pad(str(col), w) for col, w in zip(row, widths)) + " |\n"
136
+
137
+ # Generate examples
138
+ examples = []
139
+ required_arg = required_params[0].name if required_params else "example.input"
140
+
141
+ examples.append(
142
+ dedent(
143
+ f"""
144
+ ### 1. Basic usage
145
+
146
+ ```bash
147
+ python -m {app_name} {required_arg}
148
+ ```
149
+ """
150
+ )
151
+ )
152
+
153
+ # Add more examples with optional parameters
154
+ for i, param in enumerate(optional_params[:3], 2):
155
+ if param.name in ["verbose", "quiet"]:
156
+ continue
157
+ example_value = param.choices[0] if param.choices else param.default
158
+ examples.append(
159
+ dedent(f"""
160
+ ### {i}. With {param.name} parameter
161
+
162
+ ```bash
163
+ python -m {app_name} --{param.name} {example_value} {required_arg}
164
+ ```
165
+ """)
166
+ )
167
+
168
+ markdown = dedent(
169
+ f"""
170
+ # Command Line Interface
171
+
172
+ Command line options for {app_name}
173
+
174
+ ```bash
175
+ python -m {app_name} [OPTIONS] {required_arg if required_params else ""}
176
+ ```
177
+
178
+ ## Options
179
+
180
+ {table}
181
+
182
+ ## Examples
183
+
184
+ {"".join(examples)}
185
+ """
186
+ ).strip()
187
+
188
+ Path(output_file).parent.mkdir(parents=True, exist_ok=True)
189
+ with open(output_file, "w", encoding="utf-8") as f:
190
+ f.write(markdown)