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.
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.idea/runConfigurations/config_generate.xml +2 -2
- config_cli_gui-0.1.1/HISTORY.md +16 -0
- {config_cli_gui-0.0.2/src/config_cli_gui.egg-info → config_cli_gui-0.1.1}/PKG-INFO +5 -7
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/README.md +4 -2
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/docs/index.md +4 -2
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/pyproject.toml +0 -4
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/src/config_cli_gui/_version.py +2 -2
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/src/config_cli_gui/cli_generator.py +7 -5
- config_cli_gui-0.1.1/src/config_cli_gui/config_framework.py +217 -0
- config_cli_gui-0.1.1/src/config_cli_gui/docs_generator.py +190 -0
- config_cli_gui-0.1.1/src/config_cli_gui/gui_generator.py +541 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1/src/config_cli_gui.egg-info}/PKG-INFO +5 -7
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/src/config_cli_gui.egg-info/SOURCES.txt +15 -15
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/src/config_cli_gui.egg-info/requires.txt +0 -4
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/src/config_cli_gui.egg-info/top_level.txt +0 -1
- {config_cli_gui-0.0.2/src → config_cli_gui-0.1.1/tests}/example_project/config/config.py +10 -26
- config_cli_gui-0.1.1/tests/example_project/core/base.py +53 -0
- {config_cli_gui-0.0.2/src → config_cli_gui-0.1.1/tests}/example_project/gui/gui.py +4 -4
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/tests/test_generic_cli.py +9 -13
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/uv.lock +51 -293
- config_cli_gui-0.0.2/.idea/runConfigurations/module_cli.xml +0 -25
- config_cli_gui-0.0.2/HISTORY.md +0 -4
- config_cli_gui-0.0.2/src/config_cli_gui/config_framework.py +0 -362
- config_cli_gui-0.0.2/src/config_cli_gui/gui_generator.py +0 -225
- config_cli_gui-0.0.2/src/example_project/core/base.py +0 -634
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.github/FUNDING.yml +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.github/actions/setup-environment/action.yml +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.github/dependabot.yml +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.github/init.sh +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.github/release_message.sh +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.github/update_funding.py +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.github/workflows/build-macos.yml +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.github/workflows/build.yml +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.github/workflows/main.yml +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.github/workflows/release.yml +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.github/workflows/update_readme.yml +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.gitignore +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.idea/runConfigurations/module_gui.xml +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.pre-commit-config.yaml +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/.readthedocs.yaml +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/LICENSE +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/Makefile +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/config.yaml +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/docs/.nav.yml +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/docs/_static/img/favicon.png +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/docs/_static/img/logo.png +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/docs/css/custom.css +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/docs/develop/contributing.md +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/docs/develop/make_windows.md +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/docs/develop/naming_convention.md +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/docs/develop/pypi_release.md +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/docs/funding/funding.md +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/docs/getting-started/install.md +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/docs/getting-started/virtual-environment.md +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/docs/usage/cli.md +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/docs/usage/config.md +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/examples/kuhkopfsteig.gpx +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/examples/rother_lilienstein.gpx +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/examples/teneriffa.gpx +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/mkdocs.yml +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/scripts/show_filelist.ps1 +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/scripts/show_tree.ps1 +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/scripts/show_tree.py +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/scripts/update_readme.py +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/setup.cfg +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/src/__init__.py +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/src/config_cli_gui/__init__.py +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/src/config_cli_gui.egg-info/dependency_links.txt +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/src/config_cli_gui.egg-info/entry_points.txt +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/src/main.py +0 -0
- {config_cli_gui-0.0.2 → config_cli_gui-0.1.1}/template.yml.url +0 -0
- {config_cli_gui-0.0.2/src/example_project → config_cli_gui-0.1.1/tests}/__init__.py +0 -0
- {config_cli_gui-0.0.2/src/example_project/cli → config_cli_gui-0.1.1/tests/example_project}/__init__.py +0 -0
- {config_cli_gui-0.0.2/src → config_cli_gui-0.1.1/tests}/example_project/__main__.py +0 -0
- {config_cli_gui-0.0.2/src/example_project/config → config_cli_gui-0.1.1/tests/example_project/cli}/__init__.py +0 -0
- {config_cli_gui-0.0.2/src → config_cli_gui-0.1.1/tests}/example_project/cli/__main__.py +0 -0
- {config_cli_gui-0.0.2/src → config_cli_gui-0.1.1/tests}/example_project/cli/cli.py +0 -0
- {config_cli_gui-0.0.2/src/example_project/core → config_cli_gui-0.1.1/tests/example_project/config}/__init__.py +0 -0
- {config_cli_gui-0.0.2/src/example_project/gui → config_cli_gui-0.1.1/tests/example_project/core}/__init__.py +0 -0
- {config_cli_gui-0.0.2/src → config_cli_gui-0.1.1/tests}/example_project/core/logging.py +0 -0
- {config_cli_gui-0.0.2/tests → config_cli_gui-0.1.1/tests/example_project/gui}/__init__.py +0 -0
- {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$/
|
|
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$/
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
@@ -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
|
|
62
|
+
if param.choices and param_type != bool:
|
|
61
63
|
kwargs["choices"] = param.choices
|
|
62
64
|
|
|
63
|
-
if
|
|
65
|
+
if param_type == int:
|
|
64
66
|
kwargs["type"] = int
|
|
65
|
-
elif
|
|
67
|
+
elif param_type == float:
|
|
66
68
|
kwargs["type"] = float
|
|
67
|
-
elif
|
|
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
|
|
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)
|