config-cli-gui 0.3.4__tar.gz → 0.3.5__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.3.4 → config_cli_gui-0.3.5}/.github/workflows/main.yml +1 -1
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/HISTORY.md +19 -0
- {config_cli_gui-0.3.4/src/config_cli_gui.egg-info → config_cli_gui-0.3.5}/PKG-INFO +1 -1
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/config.yaml +18 -24
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/docs/usage/config.md +20 -23
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/src/config_cli_gui/_version.py +3 -3
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/src/config_cli_gui/config.py +106 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/src/config_cli_gui/gui.py +65 -16
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/src/config_cli_gui/logging.py +80 -6
- config_cli_gui-0.3.5/src/config_cli_gui/persistence.py +75 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5/src/config_cli_gui.egg-info}/PKG-INFO +1 -1
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/src/config_cli_gui.egg-info/SOURCES.txt +1 -1
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/tests/example_project/cli/cli_example.py +6 -1
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/tests/example_project/config/config_example.py +2 -81
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/tests/example_project/gui/gui_example.py +38 -6
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/tests/test_config_manager.py +1 -1
- config_cli_gui-0.3.4/tests/example_project/gui/config.yaml +0 -48
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/.github/FUNDING.yml +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/.github/actions/setup-environment/action.yml +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/.github/dependabot.yml +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/.github/init.sh +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/.github/release_message.sh +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/.github/update_funding.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/.github/workflows/release.yml +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/.github/workflows/update_readme.yml +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/.gitignore +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/.idea/runConfigurations/config_generate.xml +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/.idea/runConfigurations/example_project_cli.xml +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/.idea/runConfigurations/example_project_gui.xml +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/.pre-commit-config.yaml +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/.readthedocs.yaml +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/LICENSE +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/Makefile +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/README.md +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/docs/.nav.yml +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/docs/_static/img/favicon.png +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/docs/_static/img/logo.png +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/docs/_static/img/settings_dlg.png +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/docs/_static/img/settings_doc.png +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/docs/css/custom.css +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/docs/develop/contributing.md +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/docs/develop/make_windows.md +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/docs/develop/naming_convention.md +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/docs/funding/funding.md +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/docs/getting-started/install.md +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/docs/getting-started/virtual-environment.md +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/docs/index.md +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/docs/usage/cli.md +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/mkdocs.yml +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/pyproject.toml +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/scripts/show_filelist.ps1 +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/scripts/show_tree.ps1 +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/scripts/show_tree.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/scripts/update_readme.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/setup.cfg +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/src/__init__.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/src/config_cli_gui/__init__.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/src/config_cli_gui/cli.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/src/config_cli_gui/configtypes/__init__.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/src/config_cli_gui/configtypes/color.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/src/config_cli_gui/configtypes/font.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/src/config_cli_gui/configtypes/vector.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/src/config_cli_gui/docs.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/src/config_cli_gui.egg-info/dependency_links.txt +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/src/config_cli_gui.egg-info/entry_points.txt +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/src/config_cli_gui.egg-info/requires.txt +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/src/config_cli_gui.egg-info/top_level.txt +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/template.yml.url +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/tests/__init__.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/tests/example_project/__init__.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/tests/example_project/__main__.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/tests/example_project/cli/__init__.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/tests/example_project/cli/__main__.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/tests/example_project/config/__init__.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/tests/example_project/core/__init__.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/tests/example_project/core/base.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/tests/example_project/example.gpx +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/tests/example_project/gui/__init__.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/tests/example_project/gui/__main__.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/tests/test_cli.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/tests/test_docs.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/tests/test_generic_cli.py +0 -0
- {config_cli_gui-0.3.4 → config_cli_gui-0.3.5}/uv.lock +0 -0
|
@@ -4,6 +4,25 @@ Changelog
|
|
|
4
4
|
|
|
5
5
|
(unreleased)
|
|
6
6
|
------------
|
|
7
|
+
- Improve gui. [Paul Magister]
|
|
8
|
+
- Fix gui log issue: even better fix. [Paul Magister]
|
|
9
|
+
- Fix gui log issue: [Paul Magister]
|
|
10
|
+
|
|
11
|
+
Previously the logger handler attempted to call the passed object like a function; GuiLogWriter provides write(), not a call. Because emit() swallowed exceptions, you saw nothing in the GUI log.
|
|
12
|
+
Now connect_gui_logging detects objects with .write() and registers the writer.write callable with the UI handler. GuiLogHandler.emit calls that callable, which writes to the text widget and the text appears.
|
|
13
|
+
- Improve config consistency: correctly consider logging parameters.
|
|
14
|
+
[Paul Magister]
|
|
15
|
+
- Improve config usage consistency: allways load last config. Move
|
|
16
|
+
AppConfig into config.py since it is used in the core lib component.
|
|
17
|
+
[Paul Magister]
|
|
18
|
+
- Save last opened config to start there on the next session, move theme
|
|
19
|
+
to app category. [Paul Magister]
|
|
20
|
+
- Do not run tests on macos. [Paul Magister]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
0.3.4 (2026-06-18)
|
|
24
|
+
------------------
|
|
25
|
+
- Docs: Update HISTORY.md for release 0.3.4. [Paul Magister]
|
|
7
26
|
- Bugfix: init color display properly. [Paul Magister]
|
|
8
27
|
- Use ttkbootstrap gui styles. [Paul Magister]
|
|
9
28
|
- Improve GUI scaling and font sizes. [Paul Magister]
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
app:
|
|
2
|
+
# Date format to use | type=str
|
|
3
|
+
date_format: '%Y-%m-%d'
|
|
4
|
+
# Logging level for the application | type=str | choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
|
|
5
|
+
log_level: INFO
|
|
6
|
+
# Maximum log file size in MB before rotation | type=int
|
|
7
|
+
log_file_max_size: 2
|
|
8
|
+
# Enable logging to file | type=bool | choices=[True, False]
|
|
9
|
+
enable_file_logging: true
|
|
10
|
+
# Enable logging to console | type=bool | choices=[True, False]
|
|
11
|
+
enable_console_logging: true
|
|
12
|
+
# GUI theme setting supported by ttkbootstrap | type=str | choices=['cosmo', 'flatly', 'litera', 'minty', 'lumen', 'sandstone', 'yeti', 'pulse', 'united', 'darkly', 'superhero', 'solar', 'cyborg', 'vapor', 'simplex']
|
|
13
|
+
theme: superhero
|
|
1
14
|
cli:
|
|
2
15
|
# Path to input (file or folder) | type=str | [CLI]
|
|
3
16
|
input: ''
|
|
@@ -9,26 +22,7 @@ cli:
|
|
|
9
22
|
extract_waypoints: true
|
|
10
23
|
# Include elevation data in waypoints | type=bool | [CLI] | choices=[True, False]
|
|
11
24
|
elevation: false
|
|
12
|
-
app:
|
|
13
|
-
# Date format to use | type=str
|
|
14
|
-
date_format: '%Y-%m-%d'
|
|
15
|
-
# Logging level for the application | type=str | choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
|
|
16
|
-
log_level: INFO
|
|
17
|
-
# Maximum log file size in MB before rotation | type=int
|
|
18
|
-
log_file_max_size: 10
|
|
19
|
-
# Number of backup log files to keep | type=int
|
|
20
|
-
log_backup_count: 5
|
|
21
|
-
# Log message format style | type=str | choices=['simple', 'detailed', 'json']
|
|
22
|
-
log_format: detailed
|
|
23
|
-
# Maximum number of worker threads | type=int
|
|
24
|
-
max_workers: 4
|
|
25
|
-
# Enable logging to file | type=bool | choices=[True, False]
|
|
26
|
-
enable_file_logging: true
|
|
27
|
-
# Enable logging to console | type=bool | choices=[True, False]
|
|
28
|
-
enable_console_logging: true
|
|
29
25
|
gui:
|
|
30
|
-
# GUI theme setting suppoerted by ttkbootstrap | type=str | choices=['cosmo', 'flatly', 'litera', 'minty', 'lumen', 'sandstone', 'yeti', 'pulse', 'united', 'darkly', 'superhero', 'solar', 'cyborg', 'vapor', 'simplex']
|
|
31
|
-
theme: darkly
|
|
32
26
|
# Default window width | type=int
|
|
33
27
|
window_width: 800
|
|
34
28
|
# Default window height | type=int
|
|
@@ -40,21 +34,21 @@ gui:
|
|
|
40
34
|
# Maximum number of log lines to keep in GUI | type=int
|
|
41
35
|
max_log_lines: 1000
|
|
42
36
|
# Point in 2D space | type=Vector
|
|
43
|
-
point2D: (7, 11)
|
|
37
|
+
point2D: (7.0, 11.0)
|
|
44
38
|
# Point in 3D space | type=Vector
|
|
45
39
|
point3D: (1.2, 3.4, 5.6)
|
|
46
40
|
misc:
|
|
47
41
|
# Example integer | type=int
|
|
48
42
|
some_numeric: 42
|
|
49
43
|
# Example vector 2D | type=Vector
|
|
50
|
-
some_vector2d: (1, 2)
|
|
44
|
+
some_vector2d: (1.0, 2.0)
|
|
51
45
|
# Example vector 3D | type=Vector
|
|
52
46
|
some_vector3d: (1.1, 2.2, 3.3)
|
|
53
|
-
# Path to the file to use | type=
|
|
47
|
+
# Path to the file to use | type=str
|
|
54
48
|
some_file: some_file.txt
|
|
55
49
|
# Color setting for the application | type=Color
|
|
56
50
|
some_color: '#ff0000'
|
|
57
51
|
# Date setting for the application | type=datetime
|
|
58
|
-
some_date: '2025-12-31T10:30:
|
|
52
|
+
some_date: '2025-12-31T10:30:00'
|
|
59
53
|
# Font setting for the application | type=Font
|
|
60
|
-
some_font: 'DejaVuSans.ttf, 12, #0000ff'
|
|
54
|
+
some_font: 'DejaVuSans.ttf, 12.0, #0000ff'
|
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
These parameters are available to configure the behavior of your application.
|
|
4
4
|
The parameters in the cli category can be accessed via the command line interface.
|
|
5
5
|
|
|
6
|
+
## Category "app"
|
|
7
|
+
|
|
8
|
+
| Name | Type | Description | Default | Choices |
|
|
9
|
+
|------------------------|------|---------------------------------------------|------------|-------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
10
|
+
| date_format | str | Date format to use | '%Y-%m-%d' | - |
|
|
11
|
+
| log_level | str | Logging level for the application | 'INFO' | ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'] |
|
|
12
|
+
| log_file_max_size | int | Maximum log file size in MB before rotation | 2 | - |
|
|
13
|
+
| enable_file_logging | bool | Enable logging to file | True | [True, False] |
|
|
14
|
+
| enable_console_logging | bool | Enable logging to console | True | [True, False] |
|
|
15
|
+
| theme | str | GUI theme setting supported by ttkbootstrap | 'darkly' | ['cosmo', 'flatly', 'litera', 'minty', 'lumen', 'sandstone', 'yeti', 'pulse', 'united', 'darkly', 'superhero', 'solar', 'cyborg', 'vapor', 'simplex'] |
|
|
16
|
+
|
|
6
17
|
## Category "cli"
|
|
7
18
|
|
|
8
19
|
| Name | Type | Description | Default | Choices |
|
|
@@ -13,31 +24,17 @@ The parameters in the cli category can be accessed via the command line interfac
|
|
|
13
24
|
| extract_waypoints | bool | Extract starting points of each track as waypoint | True | [True, False] |
|
|
14
25
|
| elevation | bool | Include elevation data in waypoints | False | [True, False] |
|
|
15
26
|
|
|
16
|
-
## Category "app"
|
|
17
|
-
|
|
18
|
-
| Name | Type | Description | Default | Choices |
|
|
19
|
-
|------------------------|------|---------------------------------------------|------------|---------------------------------------------------|
|
|
20
|
-
| date_format | str | Date format to use | '%Y-%m-%d' | - |
|
|
21
|
-
| log_level | str | Logging level for the application | 'INFO' | ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'] |
|
|
22
|
-
| log_file_max_size | int | Maximum log file size in MB before rotation | 10 | - |
|
|
23
|
-
| log_backup_count | int | Number of backup log files to keep | 5 | - |
|
|
24
|
-
| log_format | str | Log message format style | 'detailed' | ['simple', 'detailed', 'json'] |
|
|
25
|
-
| max_workers | int | Maximum number of worker threads | 4 | - |
|
|
26
|
-
| enable_file_logging | bool | Enable logging to file | True | [True, False] |
|
|
27
|
-
| enable_console_logging | bool | Enable logging to console | True | [True, False] |
|
|
28
|
-
|
|
29
27
|
## Category "gui"
|
|
30
28
|
|
|
31
|
-
| Name | Type | Description | Default | Choices
|
|
32
|
-
|
|
33
|
-
|
|
|
34
|
-
|
|
|
35
|
-
|
|
|
36
|
-
|
|
|
37
|
-
|
|
|
38
|
-
|
|
|
39
|
-
|
|
|
40
|
-
| point3D | Vector | Point in 3D space | Vector(1.2, 3.4, 5.6) | - |
|
|
29
|
+
| Name | Type | Description | Default | Choices |
|
|
30
|
+
|-------------------|--------|------------------------------------------------|-----------------------|---------------|
|
|
31
|
+
| window_width | int | Default window width | 800 | - |
|
|
32
|
+
| window_height | int | Default window height | 600 | - |
|
|
33
|
+
| log_window_height | int | Height of the log window in pixels | 200 | - |
|
|
34
|
+
| auto_scroll_log | bool | Automatically scroll to the newest log entries | True | [True, False] |
|
|
35
|
+
| max_log_lines | int | Maximum number of log lines to keep in GUI | 1000 | - |
|
|
36
|
+
| point2D | Vector | Point in 2D space | Vector(7, 11) | - |
|
|
37
|
+
| point3D | Vector | Point in 3D space | Vector(1.2, 3.4, 5.6) | - |
|
|
41
38
|
|
|
42
39
|
## Category "misc"
|
|
43
40
|
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.3.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 3,
|
|
21
|
+
__version__ = version = '0.3.5'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 3, 5)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g089c3018c'
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import logging
|
|
2
3
|
from abc import ABC, abstractmethod
|
|
3
4
|
from collections.abc import Iterable
|
|
4
5
|
from dataclasses import dataclass
|
|
@@ -9,6 +10,7 @@ from typing import Any
|
|
|
9
10
|
import yaml
|
|
10
11
|
from pydantic import BaseModel
|
|
11
12
|
|
|
13
|
+
from config_cli_gui import persistence
|
|
12
14
|
from config_cli_gui.configtypes.color import Color
|
|
13
15
|
from config_cli_gui.configtypes.font import Font
|
|
14
16
|
from config_cli_gui.configtypes.vector import Vector
|
|
@@ -108,6 +110,74 @@ class ConfigSerializer:
|
|
|
108
110
|
return value
|
|
109
111
|
|
|
110
112
|
|
|
113
|
+
class AppConfig(ConfigCategory):
|
|
114
|
+
"""Application-specific configuration parameters (centralized).
|
|
115
|
+
|
|
116
|
+
This category was moved from example project to the core library so that
|
|
117
|
+
every project uses a consistent, non-editable application configuration
|
|
118
|
+
section. It contains general application settings such as logging and
|
|
119
|
+
theme preferences.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
def get_category_name(self) -> str:
|
|
123
|
+
return "app"
|
|
124
|
+
|
|
125
|
+
date_format: ConfigParameter = ConfigParameter(
|
|
126
|
+
name="date_format",
|
|
127
|
+
value="%Y-%m-%d",
|
|
128
|
+
help="Date format to use",
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
log_level: ConfigParameter = ConfigParameter(
|
|
132
|
+
name="log_level",
|
|
133
|
+
value="INFO",
|
|
134
|
+
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
|
|
135
|
+
help="Logging level for the application",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
log_file_max_size: ConfigParameter = ConfigParameter(
|
|
139
|
+
name="log_file_max_size",
|
|
140
|
+
value=2,
|
|
141
|
+
help="Maximum log file size in MB before rotation",
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
enable_file_logging: ConfigParameter = ConfigParameter(
|
|
145
|
+
name="enable_file_logging",
|
|
146
|
+
value=True,
|
|
147
|
+
help="Enable logging to file",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
enable_console_logging: ConfigParameter = ConfigParameter(
|
|
151
|
+
name="enable_console_logging",
|
|
152
|
+
value=True,
|
|
153
|
+
help="Enable logging to console",
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# ttkbootstrap.Style().theme_names()
|
|
157
|
+
theme: ConfigParameter = ConfigParameter(
|
|
158
|
+
name="theme",
|
|
159
|
+
value="darkly",
|
|
160
|
+
choices=[
|
|
161
|
+
"cosmo",
|
|
162
|
+
"flatly",
|
|
163
|
+
"litera",
|
|
164
|
+
"minty",
|
|
165
|
+
"lumen",
|
|
166
|
+
"sandstone",
|
|
167
|
+
"yeti",
|
|
168
|
+
"pulse",
|
|
169
|
+
"united",
|
|
170
|
+
"darkly",
|
|
171
|
+
"superhero",
|
|
172
|
+
"solar",
|
|
173
|
+
"cyborg",
|
|
174
|
+
"vapor",
|
|
175
|
+
"simplex",
|
|
176
|
+
],
|
|
177
|
+
help="GUI theme setting supported by ttkbootstrap",
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
111
181
|
class ConfigManager:
|
|
112
182
|
"""Manages loading, saving, and accessing configuration categories."""
|
|
113
183
|
|
|
@@ -119,7 +189,9 @@ class ConfigManager:
|
|
|
119
189
|
):
|
|
120
190
|
self._categories: dict[str, ConfigCategory] = {}
|
|
121
191
|
self._serializer = ConfigSerializer()
|
|
192
|
+
self.app: AppConfig = AppConfig()
|
|
122
193
|
|
|
194
|
+
categories = (self.app, *categories)
|
|
123
195
|
for category in categories:
|
|
124
196
|
if not isinstance(category, ConfigCategory):
|
|
125
197
|
raise TypeError(f"Expected ConfigCategory instance, got {type(category)}")
|
|
@@ -167,6 +239,18 @@ class ConfigManager:
|
|
|
167
239
|
data = yaml.safe_load(f) if path.suffix.lower() in [".yml", ".yaml"] else json.load(f)
|
|
168
240
|
|
|
169
241
|
self._apply_config_data(data)
|
|
242
|
+
# Persist the information about the last used configuration file so the
|
|
243
|
+
# application can restore the same config on next start.
|
|
244
|
+
try:
|
|
245
|
+
persistence.write_last_used_config("config-cli-gui", str(path))
|
|
246
|
+
except Exception:
|
|
247
|
+
# Persistence should not break normal config loading.
|
|
248
|
+
pass
|
|
249
|
+
try:
|
|
250
|
+
logging.getLogger("config_cli_gui").info(f"Loaded configuration from: {path}")
|
|
251
|
+
except Exception:
|
|
252
|
+
# Non-fatal: logging may not be configured yet.
|
|
253
|
+
pass
|
|
170
254
|
|
|
171
255
|
def _apply_config_data(self, data: dict[str, Any]) -> None:
|
|
172
256
|
"""Apply loaded data to the configuration parameters."""
|
|
@@ -205,6 +289,28 @@ class ConfigManager:
|
|
|
205
289
|
if file_format == "yaml":
|
|
206
290
|
self._append_comments_to_yaml(path)
|
|
207
291
|
|
|
292
|
+
# Record the saved config as the last-used config as well.
|
|
293
|
+
try:
|
|
294
|
+
persistence.write_last_used_config("config-cli-gui", str(path))
|
|
295
|
+
except Exception:
|
|
296
|
+
# Ignore persistence failures when saving
|
|
297
|
+
pass
|
|
298
|
+
try:
|
|
299
|
+
logging.getLogger("config_cli_gui").info(f"Saved configuration to: {path}")
|
|
300
|
+
except Exception:
|
|
301
|
+
# Non-fatal: logging may not be configured yet.
|
|
302
|
+
pass
|
|
303
|
+
|
|
304
|
+
def get_last_used_config(self) -> str | None:
|
|
305
|
+
"""Return the last used config file path recorded by the persistence layer.
|
|
306
|
+
|
|
307
|
+
This is a convenience wrapper around persistence.read_last_used_config.
|
|
308
|
+
"""
|
|
309
|
+
try:
|
|
310
|
+
return persistence.read_last_used_config("config-cli-gui")
|
|
311
|
+
except Exception:
|
|
312
|
+
return None
|
|
313
|
+
|
|
208
314
|
def to_dict(self) -> dict[str, Any]:
|
|
209
315
|
"""Convert all configuration categories to a dictionary."""
|
|
210
316
|
result: dict[str, dict[str, Any]] = {}
|
|
@@ -155,8 +155,12 @@ class CalendarDialog:
|
|
|
155
155
|
button_frame = ttk.Frame(main_frame)
|
|
156
156
|
button_frame.pack(fill=tk.X, pady=(10, 0))
|
|
157
157
|
|
|
158
|
-
ttk.Button(button_frame, text="OK", command=self._on_ok).pack(
|
|
159
|
-
|
|
158
|
+
ttk.Button(button_frame, text="OK", command=self._on_ok, width=10).pack(
|
|
159
|
+
side=tk.RIGHT, padx=(5, 0)
|
|
160
|
+
)
|
|
161
|
+
ttk.Button(button_frame, text="Cancel", command=self._on_cancel, width=10).pack(
|
|
162
|
+
side=tk.RIGHT
|
|
163
|
+
)
|
|
160
164
|
|
|
161
165
|
def _update_calendar(self):
|
|
162
166
|
"""Update day spinbox based on selected month/year."""
|
|
@@ -213,7 +217,7 @@ class GenericSettingsDialog:
|
|
|
213
217
|
config_file="config.yaml",
|
|
214
218
|
):
|
|
215
219
|
self.parent = parent
|
|
216
|
-
self.config_manager = config_manager
|
|
220
|
+
self.config_manager: ConfigManager = config_manager
|
|
217
221
|
self.config_file = config_file
|
|
218
222
|
self.result = None
|
|
219
223
|
self.widgets = {}
|
|
@@ -252,11 +256,15 @@ class GenericSettingsDialog:
|
|
|
252
256
|
button_frame.pack(fill=tk.X, pady=(10, 0))
|
|
253
257
|
|
|
254
258
|
# Apply: apply current settings (in-memory) but keep dialog open
|
|
255
|
-
ttk.Button(button_frame, text="Apply", command=self._on_apply).pack(
|
|
259
|
+
ttk.Button(button_frame, text="Apply", command=self._on_apply, width=10).pack(
|
|
256
260
|
side=tk.RIGHT, padx=(5, 0)
|
|
257
261
|
)
|
|
258
|
-
ttk.Button(button_frame, text="OK", command=self._on_ok).pack(
|
|
259
|
-
|
|
262
|
+
ttk.Button(button_frame, text="OK", command=self._on_ok, width=10).pack(
|
|
263
|
+
side=tk.RIGHT, padx=(5, 0)
|
|
264
|
+
)
|
|
265
|
+
ttk.Button(button_frame, text="Cancel", command=self._on_cancel, width=10).pack(
|
|
266
|
+
side=tk.RIGHT
|
|
267
|
+
)
|
|
260
268
|
|
|
261
269
|
def _create_category_tab(self, category_name: str, category):
|
|
262
270
|
"""Create a tab for a configuration category."""
|
|
@@ -297,13 +305,13 @@ class GenericSettingsDialog:
|
|
|
297
305
|
continue
|
|
298
306
|
"""
|
|
299
307
|
|
|
300
|
-
# Create label
|
|
301
|
-
label = ttk.Label(parent, text=f"{param.name}:")
|
|
302
|
-
label.grid(row=row, column=0, sticky="w", padx=5, pady=
|
|
308
|
+
# Create label (fixed width for alignment)
|
|
309
|
+
label = ttk.Label(parent, text=f"{param.name}:", width=28, anchor="w")
|
|
310
|
+
label.grid(row=row, column=0, sticky="w", padx=5, pady=4)
|
|
303
311
|
|
|
304
312
|
# Create appropriate widget based on parameter type
|
|
305
313
|
widget = self._create_parameter_widget(parent, param)
|
|
306
|
-
widget.grid(row=row, column=1, sticky="ew", padx=5, pady=
|
|
314
|
+
widget.grid(row=row, column=1, sticky="ew", padx=5, pady=4)
|
|
307
315
|
|
|
308
316
|
# Add tooltip
|
|
309
317
|
ToolTip(label, param.help)
|
|
@@ -407,10 +415,10 @@ class GenericSettingsDialog:
|
|
|
407
415
|
if path:
|
|
408
416
|
var.set(path)
|
|
409
417
|
|
|
410
|
-
browse_btn = ttk.Button(frame, text="File", command=browse_file)
|
|
418
|
+
browse_btn = ttk.Button(frame, text="File", command=browse_file, width=10)
|
|
411
419
|
browse_btn.pack(side=tk.RIGHT, padx=(5, 0))
|
|
412
420
|
|
|
413
|
-
browse_btn = ttk.Button(frame, text="Directory", command=browse_dir)
|
|
421
|
+
browse_btn = ttk.Button(frame, text="Directory", command=browse_dir, width=10)
|
|
414
422
|
browse_btn.pack(side=tk.RIGHT, padx=(5, 0))
|
|
415
423
|
|
|
416
424
|
frame.var = var
|
|
@@ -436,7 +444,7 @@ class GenericSettingsDialog:
|
|
|
436
444
|
var.set(color[1])
|
|
437
445
|
color_display.config(bg=color[1])
|
|
438
446
|
|
|
439
|
-
pick_btn = ttk.Button(frame, text="Pick", command=pick_color)
|
|
447
|
+
pick_btn = ttk.Button(frame, text="Pick", command=pick_color, width=10)
|
|
440
448
|
pick_btn.pack(side=tk.LEFT, padx=(5, 0))
|
|
441
449
|
color_display.config(bg=color_value.to_hex())
|
|
442
450
|
|
|
@@ -485,7 +493,7 @@ class GenericSettingsDialog:
|
|
|
485
493
|
color_var.set(color[1])
|
|
486
494
|
color_display.config(bg=color[1])
|
|
487
495
|
|
|
488
|
-
pick_btn = ttk.Button(frame, text="Pick Color", command=pick_color)
|
|
496
|
+
pick_btn = ttk.Button(frame, text="Pick Color", command=pick_color, width=10)
|
|
489
497
|
pick_btn.pack(side=tk.LEFT, padx=(5, 0))
|
|
490
498
|
|
|
491
499
|
def on_color_change(*args):
|
|
@@ -531,7 +539,7 @@ class GenericSettingsDialog:
|
|
|
531
539
|
img_label.image = photo
|
|
532
540
|
img_label.pack()
|
|
533
541
|
|
|
534
|
-
preview_btn = ttk.Button(frame, text="Preview", command=show_preview)
|
|
542
|
+
preview_btn = ttk.Button(frame, text="Preview", command=show_preview, width=10)
|
|
535
543
|
preview_btn.pack(side=tk.LEFT, padx=(5, 0))
|
|
536
544
|
|
|
537
545
|
# Store variables in the frame for later access
|
|
@@ -584,7 +592,7 @@ class GenericSettingsDialog:
|
|
|
584
592
|
if dialog.result:
|
|
585
593
|
var.set(dialog.result.strftime("%Y-%m-%d %H:%M"))
|
|
586
594
|
|
|
587
|
-
cal_btn = ttk.Button(frame, text="Calendar", command=pick_datetime)
|
|
595
|
+
cal_btn = ttk.Button(frame, text="Calendar", command=pick_datetime, width=10)
|
|
588
596
|
cal_btn.pack(side=tk.RIGHT, padx=(5, 0))
|
|
589
597
|
|
|
590
598
|
frame.var = var
|
|
@@ -636,7 +644,17 @@ class GenericSettingsDialog:
|
|
|
636
644
|
def _on_ok(self):
|
|
637
645
|
"""Handle OK button click."""
|
|
638
646
|
try:
|
|
647
|
+
# Remember old theme to detect changes
|
|
648
|
+
try:
|
|
649
|
+
old_theme = self.config_manager.app.theme.value
|
|
650
|
+
except Exception:
|
|
651
|
+
old_theme = None
|
|
652
|
+
|
|
639
653
|
self.__persist_settings()
|
|
654
|
+
|
|
655
|
+
# Apply GUI updates (theme change etc.) after persisting
|
|
656
|
+
self._apply_gui_updates(old_theme)
|
|
657
|
+
|
|
640
658
|
self.dialog.destroy()
|
|
641
659
|
|
|
642
660
|
except Exception as e:
|
|
@@ -698,6 +716,30 @@ class GenericSettingsDialog:
|
|
|
698
716
|
|
|
699
717
|
self.result = "ok"
|
|
700
718
|
|
|
719
|
+
def _apply_gui_updates(self, old_theme=None):
|
|
720
|
+
"""Apply GUI-level updates that should happen after settings are persisted.
|
|
721
|
+
|
|
722
|
+
Currently this changes the ttkbootstrap theme if it was modified. The
|
|
723
|
+
logic is centralized here so it can be called from both Apply and OK
|
|
724
|
+
handlers.
|
|
725
|
+
"""
|
|
726
|
+
try:
|
|
727
|
+
new_theme = None
|
|
728
|
+
try:
|
|
729
|
+
new_theme = self.config_manager.app.theme.value
|
|
730
|
+
except Exception:
|
|
731
|
+
new_theme = None
|
|
732
|
+
|
|
733
|
+
if new_theme and new_theme != old_theme:
|
|
734
|
+
try:
|
|
735
|
+
ttkbootstrap.Style().theme_use(new_theme)
|
|
736
|
+
except Exception:
|
|
737
|
+
# Best-effort; don't crash the settings dialog if theme switch fails
|
|
738
|
+
pass
|
|
739
|
+
except Exception:
|
|
740
|
+
# Swallow any unexpected errors to keep UI responsive
|
|
741
|
+
pass
|
|
742
|
+
|
|
701
743
|
def _on_cancel(self):
|
|
702
744
|
"""Handle Cancel button click."""
|
|
703
745
|
self.result = "cancel"
|
|
@@ -710,7 +752,14 @@ class GenericSettingsDialog:
|
|
|
710
752
|
applies the theme change live if the GUI theme parameter is present.
|
|
711
753
|
"""
|
|
712
754
|
try:
|
|
755
|
+
# Remember current theme, persist settings and apply GUI updates
|
|
756
|
+
try:
|
|
757
|
+
old_theme = self.config_manager.app.theme.value
|
|
758
|
+
except Exception:
|
|
759
|
+
old_theme = None
|
|
760
|
+
|
|
713
761
|
self.__persist_settings()
|
|
762
|
+
self._apply_gui_updates(old_theme)
|
|
714
763
|
|
|
715
764
|
except Exception as e:
|
|
716
765
|
messagebox.showerror("Error", f"Failed to save configuration: {e}")
|
|
@@ -45,13 +45,21 @@ class GuiLogHandler(logging.Handler):
|
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
class LoggerManager:
|
|
48
|
-
"""Manages all logging configuration and handlers.
|
|
48
|
+
"""Manages all logging configuration and handlers.
|
|
49
|
+
|
|
50
|
+
Accepts explicit parameters for integration with the project's
|
|
51
|
+
configuration system. All file-size related checks are performed once
|
|
52
|
+
during initialization and clamped to the allowed range.
|
|
53
|
+
"""
|
|
49
54
|
|
|
50
55
|
def __init__(
|
|
51
56
|
self,
|
|
52
57
|
log_level: str = "INFO",
|
|
53
58
|
log_dir: Path = Path("logs"),
|
|
54
59
|
log_file_name: str = "config_cli_gui.log",
|
|
60
|
+
log_file_max_size: int | None = None,
|
|
61
|
+
enable_file_logging: bool = True,
|
|
62
|
+
enable_console_logging: bool = True,
|
|
55
63
|
):
|
|
56
64
|
"""
|
|
57
65
|
Initialize the logger manager.
|
|
@@ -59,15 +67,38 @@ class LoggerManager:
|
|
|
59
67
|
log_level: The initial log level (e.g., "DEBUG", "INFO").
|
|
60
68
|
log_dir: The directory to store log files.
|
|
61
69
|
log_file_name: The name of the log file.
|
|
70
|
+
log_file_max_size: Maximum log file size in MB (clamped to 0..100).
|
|
71
|
+
enable_file_logging: Whether to enable file logging.
|
|
72
|
+
enable_console_logging: Whether to enable console logging.
|
|
62
73
|
"""
|
|
63
74
|
self.log_level = log_level.upper()
|
|
64
75
|
self.log_dir = log_dir
|
|
65
76
|
self.log_file_name = log_file_name
|
|
66
77
|
|
|
78
|
+
# Validate and clamp file size (MB). Must be >= 0 and <= 100.
|
|
79
|
+
if log_file_max_size is None:
|
|
80
|
+
# keep the previous hard-coded default of 10 MB when not provided
|
|
81
|
+
self.log_file_max_size_mb = 10
|
|
82
|
+
else:
|
|
83
|
+
try:
|
|
84
|
+
size_mb = int(log_file_max_size)
|
|
85
|
+
except Exception:
|
|
86
|
+
size_mb = 10
|
|
87
|
+
# clamp to allowed range
|
|
88
|
+
if size_mb < 0:
|
|
89
|
+
size_mb = 0
|
|
90
|
+
if size_mb > 100:
|
|
91
|
+
size_mb = 100
|
|
92
|
+
self.log_file_max_size_mb = size_mb
|
|
93
|
+
|
|
94
|
+
# Enable/disable handlers
|
|
95
|
+
self.enable_file_logging = bool(enable_file_logging)
|
|
96
|
+
self.enable_console_logging = bool(enable_console_logging)
|
|
97
|
+
|
|
67
98
|
self.logger = logging.getLogger("config_cli_gui")
|
|
68
|
-
self.gui_handler:
|
|
99
|
+
self.gui_handler: logging.Handler | None = None
|
|
69
100
|
self.file_handler: logging.handlers.RotatingFileHandler | None = None
|
|
70
|
-
self.console_handler: logging.
|
|
101
|
+
self.console_handler: logging.Handler | None = None
|
|
71
102
|
|
|
72
103
|
self._setup_logging()
|
|
73
104
|
|
|
@@ -87,12 +118,20 @@ class LoggerManager:
|
|
|
87
118
|
|
|
88
119
|
def _setup_file_handler(self, formatter: logging.Formatter) -> None:
|
|
89
120
|
"""Set up a rotating file handler."""
|
|
121
|
+
# Only create file handler when enabled
|
|
122
|
+
if not self.enable_file_logging:
|
|
123
|
+
self.file_handler = None
|
|
124
|
+
return
|
|
125
|
+
|
|
90
126
|
self.log_dir.mkdir(exist_ok=True)
|
|
91
127
|
log_file = self.log_dir / self.log_file_name
|
|
92
128
|
|
|
129
|
+
max_bytes = int(self.log_file_max_size_mb) * 1024 * 1024
|
|
130
|
+
|
|
131
|
+
# If max_bytes is 0, RotatingFileHandler will effectively never rotate.
|
|
93
132
|
self.file_handler = logging.handlers.RotatingFileHandler(
|
|
94
133
|
log_file,
|
|
95
|
-
maxBytes=
|
|
134
|
+
maxBytes=max_bytes,
|
|
96
135
|
backupCount=5,
|
|
97
136
|
encoding="utf-8",
|
|
98
137
|
)
|
|
@@ -101,6 +140,10 @@ class LoggerManager:
|
|
|
101
140
|
|
|
102
141
|
def _setup_console_handler(self, formatter: logging.Formatter) -> None:
|
|
103
142
|
"""Set up a console handler."""
|
|
143
|
+
if not self.enable_console_logging:
|
|
144
|
+
self.console_handler = None
|
|
145
|
+
return
|
|
146
|
+
|
|
104
147
|
self.console_handler = logging.StreamHandler(sys.stdout)
|
|
105
148
|
self.console_handler.setFormatter(formatter)
|
|
106
149
|
self.logger.addHandler(self.console_handler)
|
|
@@ -158,17 +201,48 @@ class LoggerManager:
|
|
|
158
201
|
_logger_manager: LoggerManager | None = None
|
|
159
202
|
|
|
160
203
|
|
|
161
|
-
def initialize_logging(
|
|
204
|
+
def initialize_logging(
|
|
205
|
+
log_level: str = "INFO",
|
|
206
|
+
log_file_max_size: int | None = None,
|
|
207
|
+
enable_file_logging: bool | None = None,
|
|
208
|
+
enable_console_logging: bool | None = None,
|
|
209
|
+
log_dir: Path | None = None,
|
|
210
|
+
log_file_name: str | None = None,
|
|
211
|
+
) -> LoggerManager | None:
|
|
162
212
|
"""
|
|
163
213
|
Initialize the global logging system.
|
|
214
|
+
|
|
215
|
+
Accepts individual configuration values (no dependency on AppConfig).
|
|
164
216
|
Args:
|
|
165
217
|
log_level: The initial log level.
|
|
218
|
+
log_file_max_size: Maximum log file size in MB (clamped to 0..100).
|
|
219
|
+
enable_file_logging: Enable or disable file logging. When None, defaults to True.
|
|
220
|
+
enable_console_logging: Enable or disable console logging. When None, defaults to True.
|
|
221
|
+
log_dir: Directory for log files. When None, defaults to Path('logs').
|
|
222
|
+
log_file_name: Log filename. When None, defaults to 'config_cli_gui.log'.
|
|
166
223
|
Returns:
|
|
167
224
|
The initialized LoggerManager instance.
|
|
168
225
|
"""
|
|
169
226
|
global _logger_manager
|
|
170
227
|
if _logger_manager is None:
|
|
171
|
-
|
|
228
|
+
# Defaults: file and console logging enabled unless explicitly disabled
|
|
229
|
+
if enable_file_logging is None:
|
|
230
|
+
enable_file_logging = True
|
|
231
|
+
if enable_console_logging is None:
|
|
232
|
+
enable_console_logging = True
|
|
233
|
+
if log_dir is None:
|
|
234
|
+
log_dir = Path("logs")
|
|
235
|
+
if log_file_name is None:
|
|
236
|
+
log_file_name = "config_cli_gui.log"
|
|
237
|
+
|
|
238
|
+
_logger_manager = LoggerManager(
|
|
239
|
+
log_level=log_level,
|
|
240
|
+
log_dir=log_dir,
|
|
241
|
+
log_file_name=log_file_name,
|
|
242
|
+
log_file_max_size=log_file_max_size,
|
|
243
|
+
enable_file_logging=enable_file_logging,
|
|
244
|
+
enable_console_logging=enable_console_logging,
|
|
245
|
+
)
|
|
172
246
|
return _logger_manager
|
|
173
247
|
|
|
174
248
|
|