config-cli-gui 0.3.2
Feature-rich Python project template for config-cli-gui.
Requires Python: <3.12,>=3.10

# Welcome to config-cli-gui

**Unified Configuration and Interface Management**

Provides a generic configuration framework that automatically generates both command-line interfaces and GUI settings dialogs from configuration parameters. 

[![Github CI Status](https://github.com/pamagister/config-cli-gui/actions/workflows/main.yml/badge.svg)](https://github.com/pamagister/config-cli-gui/actions)
[![GitHub release](https://img.shields.io/github/v/release/pamagister/config-cli-gui)](https://github.com/pamagister/config-cli-gui/releases)
[![Read the Docs](https://readthedocs.org/projects/config-cli-gui/badge/?version=stable)](https://config-cli-gui.readthedocs.io/en/stable/)
[![License](https://img.shields.io/github/license/pamagister/config-cli-gui)](https://github.com/pamagister/config-cli-gui/blob/main/LICENSE)
[![GitHub issues](https://img.shields.io/github/issues/pamagister/config-cli-gui)](https://github.com/pamagister/config-cli-gui/issues)
[![PyPI](https://img.shields.io/pypi/v/config-cli-gui)](https://pypi.org/project/config-cli-gui/)
[![Downloads](https://pepy.tech/badge/config-cli-gui)](https://pepy.tech/project/config-cli-gui/)


`config-cli-gui` is a Python library designed to streamline the management of application configurations, 
generating command-line interfaces (CLIs), and dynamically creating graphical user interface (GUI) settings dialogs 
from a single source of truth. It leverages Pydantic for robust parameter definition and offers 
powerful features for consistent configuration across different application entry points.

---

## 🚀 Installation

You can install `config-cli-gui` using pip:

```bash
pip install config-cli-gui
```

---

## ✨ Features

  * **Single Source of Truth**: Define all your application parameters in one place using simple, dataclass-like structures based on Pydantic's `BaseModel`. This ensures consistency and reduces errors across your application.

    ```yaml
    gui:
      # GUI theme setting | type=str | choices=['light', 'dark', 'auto']
      theme: light
    misc:
      # Example integer | type=int
      some_numeric: 42
      # Path to the file to use | type=PosixPath
      some_file: some_file.txt
      # Color setting for the application | type=Color
      some_color: '#ff0000'
      # Font setting for the application | type=Font
      some_font: 'DejaVuSans.ttf, 12, #0000ff'
    ```

  * **Categorized Configuration**: Organize your parameters into logical categories (e.g., `cli`, `app`, `gui`) for better structure and maintainability.
  * **Dynamic CLI Generation**: Automatically generate `argparse`-compatible command-line arguments directly from your defined configuration parameters, including help texts, types, and choices.
  * **Config File Management**: Easily load and save configurations from/to YAML or JSON files, allowing users to customize default settings.
  * **GUI Settings Dialogs**: Dynamically create Tkinter-based settings dialogs for your application, allowing users to intuitively modify configuration parameters via a graphical interface.

    ![settings_dlg.png](_static/img/settings_dlg.png)
 
  * **Documentation Generation**: Generate detailed Markdown documentation for both your CLI options and all configuration parameters, keeping your user guides always up-to-date with your codebase.

    ![settings_doc.png](_static/img/settings_doc.png)

  * **Override System**: Supports robust overriding of configuration values via configuration files and command-line arguments, with clear precedence.

---

## 📚 Usage

### 1\. Define Your Configuration

Start by defining your application's configuration parameters in a central `config.py` file within your project. You will inherit from `config-cli-gui`'s `GenericConfigManager` and `BaseConfigCategory`.

```python
# my_project/config_example.py

from datetime import datetime
from pathlib import Path

from config_cli_gui.config import (
    ConfigCategory,
    ConfigManager,
    ConfigParameter,
)
from config_cli_gui.configtypes.color import Color
from config_cli_gui.configtypes.font import Font
from config_cli_gui.configtypes.vector import Vector


class MiscConfig(ConfigCategory):
    def get_category_name(self) -> str:
        return "misc"

    some_numeric: ConfigParameter = ConfigParameter(
        name="some_numeric",
        value=int(42),
        help="Example integer",
        is_cli=True,
    )

    some_vector: ConfigParameter = ConfigParameter(
        name="some_vector",
        value=Vector(1, 2, 3),
        help="Example vector",
    )

    some_file: ConfigParameter = ConfigParameter(
        name="some_file",
        value=Path("some_file.txt"),
        help="Path to the file to use",
    )

    some_color: ConfigParameter = ConfigParameter(
        name="some_color",
        value=Color(255, 0, 0),
        help="Color setting for the application",
    )

    some_date: ConfigParameter = ConfigParameter(
        name="some_date",
        value=datetime.fromisoformat("2025-12-31 10:30:45"),
        help="Date setting for the application",
    )

    some_font: ConfigParameter = ConfigParameter(
        name="some_font",
        value=Font("DejaVuSans.ttf", size=12, color=Color(0, 0, 255)),
        help="Font setting for the application",
    )


class AppConfig(ConfigCategory):
    """Application-specific configuration parameters."""

    def get_category_name(self) -> str:
        return "app"

    log_level: ConfigParameter = ConfigParameter(
        name="log_level",
        value="INFO",
        choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
        help="Logging level for the application",
    )

    log_file_max_size: ConfigParameter = ConfigParameter(
        name="log_file_max_size",
        value=10,
        help="Maximum log file size in MB before rotation",
    )


class ProjectConfigManager(ConfigManager):  # Inherit from ConfigManager
    """Main configuration manager that handles all parameter categories."""

    app: AppConfig
    misc: MiscConfig
    
    def __init__(self, config_file: str | None = None, **kwargs):
        """Initialize the configuration manager with all parameter categories."""
        categories = (MiscConfig(), AppConfig())
        super().__init__(categories, config_file, **kwargs)


```

### 2\. Generate CLI

Use the generic CLI functions to parse command-line arguments based on your defined `CliConfig`.

```python
# my_project/cli_example.py
from config_cli_gui.cli import CliGenerator
from config_cli_gui.config import ConfigManager
from tests.example_project.config.config_example import ProjectConfigManager
from tests.example_project.core.base import BaseGPXProcessor
from tests.example_project.core.logging import initialize_logging


def run_main_processing(_config: ConfigManager) -> int:
    """Main processing function that gets called by the CLI generator.

    Args:
        _config: Configuration manager with all settings

    Returns:
        Exit code (0 for success, non-zero for error)
    """
    # Initialize logging system
    logger_manager = initialize_logging(_config)
    logger = logger_manager.get_logger("config_cli_gui.cli")

    try:
        # Log startup information
        logger.info("Starting config_cli_gui CLI")
        logger_manager.log_config_summary()

        logger.info(f"Processing input")

        # Create and run BaseGPXProcessor
        processor = BaseGPXProcessor(
            _config.get_category("cli").input.default,
            _config.get_category("cli").output.default,
            _config.get_category("cli").min_dist.default,
            _config.get_category("app").date_format.default,
            _config.get_category("cli").elevation.default,
            logger=logger,
        )

        logger.info("Starting conversion process")

        # Run the processing (adjust method name based on your actual implementation)
        result_files = processor.compress_files()
        logger.info(f"Successfully processed {result_files}")
        return 0

    except Exception as e:
        logger.error(f"Processing failed: {e}")
        logger.debug("Full traceback:", exc_info=True)
        return 1


def main():
    """Main entry point for the CLI application."""
    # Create the base configuration manager
    config_manager = ProjectConfigManager()

    # Create CLI generator
    cli_generator = CliGenerator(config_manager=config_manager, app_name="config_cli_gui")

    # Run the CLI with our main processing function
    return cli_generator.run_cli(
        main_function=run_main_processing,
        description="Example CLI for config-cli-gui using the generic config framework.",
    )


if __name__ == "__main__":
    import sys

    sys.exit(main())
```

### 3\. Integrate GUI Settings Dialog

The `SettingsDialog` from `config-cli-gui` (or your project's adapted version) can be used to easily create a settings window.

```python
# my_project/gui_example.py (Simplified example)
import tkinter as tk
from tests.example_project.config.config_example import ProjectConfigManager
from config_cli_gui.gui import GenericSettingsDialog  # Assuming gui_settings is part of the generic lib or adapted


def open_settings_window(parent_root, config_manager: ProjectConfigManager):
    dialog = GenericSettingsDialog(parent_root, config_manager)
    parent_root.wait_window(dialog.dialog)
    # After dialog closes, config_manager will have updated values if 'OK' was clicked
    print("Settings updated or cancelled.")
    print(f"New GUI Theme: {config_manager.get_category('gui').theme.default}")


if __name__ == "__main__":
    root = tk.Tk()
    root.withdraw()  # Hide main window for this example

    # Initialize your project's config manager
    project_config = ProjectConfigManager()

    open_settings_window(root, project_config)

    root.destroy()
```

### 4\. Generate Documentation and Default Config

Use the static methods on your `ProjectConfigManager` to generate `config.yaml`, `cli.md`, and `config.md` files.

```python
# scripts/generate_docs.py (or similar script in your project)
from tests.example_project.config.config_example import ProjectConfigManager
from config_cli_gui.docs import DocumentationGenerator
import os

# Define output paths
output_dir = "docs/generated"
os.makedirs(output_dir, exist_ok=True)

default_config = "config.yaml"  # At the project root or similar
default_cli_doc = os.path.join(output_dir, "cli.md")
default_config_doc = os.path.join(output_dir, "config.md")
_config = ProjectConfigManager()
doc_gen = DocumentationGenerator(_config)
doc_gen.generate_default_config_file(output_file=default_config)
print(f"Generated: {default_config}")

doc_gen.generate_config_markdown_doc(output_file=default_config_doc)
print(f"Generated: {default_config_doc}")

doc_gen.generate_cli_markdown_doc(output_file=default_cli_doc)
print(f"Generated: {default_cli_doc}")

print("Documentation and default config generation complete.")
```

By following this structure, `config-cli-gui` provides a robust and maintainable foundation for your application's configuration needs.