Skip to content

Commit bde1dda

Browse files
authored
refactor openSUSE#112: reorganize config CLI and fix help-mode crash (openSUSE#230)
* Lazy-Loading & Help Logic Implemented a "Help Shield": The root cli function now detects if a help flag (--help or -h) is present in sys.argv before attempting to load any configuration files. * Task-Oriented Config Subcommands Merged Resources: Replaced the separate config application and config environment commands with unified config list and config validate commands. * Test Suite Stabilization Metadata Logging: Updated tests/cli/cmd_metadata/test_cmd_metadata.py to use unittest.mock.patch on the module-level logger. This ensures tests passing by bypassing caplog capture issues in async environments. --------- Signed-off-by: sushant-suse <[email protected]>
1 parent 4fb392e commit bde1dda

13 files changed

Lines changed: 218 additions & 152 deletions

File tree

docs/source/user/config/index.rst

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,26 @@
1-
Showing Configuration
2-
=====================
1+
Viewing and Validating Configuration
2+
------------------------------------
33

4-
The docbuild tool distinguishes between two types of configuration files:
4+
You can Use the :command:`config` subcommand to list or validate your current settings.
55

6-
* Application configuration files ("app config")
6+
Listing Configuration
7+
~~~~~~~~~~~~~~~~~~~~~
78

8-
* Environment configuration files ("env config")
9+
To see the current merged configuration:
910

10-
Additionally, the docbuild tool has also hardcoded default values for both types of configuration.
11+
.. code-block:: shell
1112
12-
If no env or app configuration files are found, the docbuild tool will
13-
fallback to these hardcoded default values.
13+
docbuild config list
1414
15-
Keep in mind that these defaults may not be suitable for all use cases, and it is recommended to create and use a configuration file to customize the behavior of the docbuild tool.
15+
Use the ``--flat`` flag to see the dotted-path format, or filter by ``--app`` or ``--env``.
1616

17+
Validating Configuration
18+
~~~~~~~~~~~~~~~~~~~~~~~~
1719

18-
.. admonition:: TOML as default format
20+
To ensure your TOML files match the required schema:
1921

20-
Both configuration types are written in TOML format, which is a human-readable data serialization standard. See `TOML docs <https://toml.io/en/>`_ for more information on its syntax and structure.
22+
.. code-block:: shell
2123
22-
The following subsections provide more details on how to view and manage the configuration files for both application and environment settings.
24+
docbuild config validate
2325
24-
25-
.. toctree::
26-
:maxdepth: 2
27-
28-
application
29-
environment
26+
This checks both application and environment files. You can also validate them individually using the ``--app`` or ``--env`` flags.

docs/source/user/validate.rst

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
1-
Validating XML configuration
2-
============================
1+
Validating Configuration
2+
========================
33

4-
The :command:`docbuild build` subcommand is used to validate XML configuration files. It checks the syntax and structure of the XML files against a RelaxNG schema to ensure they conform to the expected format.
4+
The :command:`docbuild config validate` subcommand is used to verify that your TOML configuration files are syntactically correct and conform to the required schema.
55

66
.. code-block:: shell
7-
:caption: Synopsis of :command:`docbuild validate`
7+
:caption: Synopsis of :command:`docbuild config validate`
88
9-
docbuild validate [OPTIONS]
9+
docbuild config validate [OPTIONS]
1010
11-
The process is a two step approach:
11+
The tool validates:
1212

13-
1. It first validates the XML config file individually against the RNG schema to ensure it is structurally correct. After this, it applies several check rules to ensure the XML file is semantically correct.
14-
2. If the first step is successful, it then creates a combined XML file that includes all the individual XML files ("stitchfile"). This combined file is checked if references inside are correct.
13+
1. **Application Configuration**: Ensures core settings like logging and worker limits are valid.
14+
2. **Environment Configuration**: Checks paths, server roles, and build parameters.
1515

16+
Options
17+
-------
18+
19+
* ``--app``: Validate only the application configuration.
20+
* ``--env``: Validate only the environment configuration.
21+
* ``--all``: Validate everything (default).
22+
23+
.. note::
24+
Deep XML/Portal validation is currently handled as part of the build process or via external tools. Modular XML validation will be reintroduced in a future update.

src/docbuild/cli/cmd_cli.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,15 @@ def cli(
208208
:param env_config: Filename to a environment's TOML config file.
209209
:param kwargs: Additional keyword arguments.
210210
"""
211+
# 1. Click's internal guard for completion/resilient parsing
212+
if ctx.resilient_parsing:
213+
return
214+
215+
# 2. Check if the user is asking for help ANYWHERE in the command
216+
# This covers 'docbuild -h', 'docbuild config -h', and 'docbuild config list -h'
217+
if any(arg in ctx.help_option_names for arg in sys.argv):
218+
return
219+
211220
if ctx.invoked_subcommand is None:
212221
click.echo(10 * "-")
213222
click.echo(ctx.get_help())
Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,17 @@
1-
"""CLI interface to shows config files how docbuild sees it."""
1+
"""CLI interface to manage and view configuration."""
22

33
import click
44

5-
from .application import app
6-
from .environment import env
5+
from .list import list_config
6+
from .validate import validate
77

88

9-
@click.group(
10-
name="config",
11-
help=__doc__,
12-
)
9+
@click.group(name="config", help="Manage and view docbuild configuration.")
1310
@click.pass_context
1411
def config(ctx: click.Context) -> None:
15-
"""Subcommand to show the configuration files and their content."""
12+
"""Subcommand to manage the configuration files and their content."""
1613
pass
1714

1815

19-
# Register the subcommands for the config group
20-
config.add_command(env)
21-
config.add_command(app)
16+
config.add_command(list_config)
17+
config.add_command(validate)

src/docbuild/cli/cmd_config/application.py

Lines changed: 0 additions & 23 deletions
This file was deleted.

src/docbuild/cli/cmd_config/environment.py

Lines changed: 0 additions & 26 deletions
This file was deleted.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""CLI interface to list the configuration."""
2+
3+
from typing import Any
4+
5+
import click
6+
from rich import print_json
7+
from rich.console import Console
8+
9+
from ...utils.flatten import flatten_dict
10+
11+
console = Console()
12+
13+
def print_section(title: str, data: dict[str, Any], prefix: str, flat: bool, color: str) -> None:
14+
"""Print a configuration section in either flat or JSON format."""
15+
if flat:
16+
for k, v in flatten_dict(data, prefix):
17+
# Using repr(v) ensures strings are quoted and types like Paths are clear
18+
console.print(f"[bold {color}]{k}[/bold {color}] = [green]{v!r}[/green]")
19+
else:
20+
console.print(f"\n# {title}", style="blue")
21+
print_json(data=data)
22+
23+
24+
@click.command(name="list")
25+
@click.option("--app", is_flag=True, help="Show only application configuration")
26+
@click.option("--env", is_flag=True, help="Show only environment configuration")
27+
@click.option("--flat", is_flag=True, help="Output in flat dotted format (git-style)")
28+
@click.pass_context
29+
def list_config(ctx: click.Context, app: bool, env: bool, flat: bool) -> None:
30+
"""List the configuration as JSON or flat text."""
31+
context = ctx.obj
32+
# If no specific flags are provided, show everything
33+
show_all = not (app or env)
34+
35+
if (app or show_all) and context.appconfig:
36+
# mode="json" handles IPv4Address, Path, and Enums automatically
37+
app_data = context.appconfig.model_dump(mode="json")
38+
print_section("Application Configuration", app_data, "app", flat, "cyan")
39+
40+
if (env or show_all) and context.envconfig:
41+
# Handles serialization for environment settings
42+
env_data = context.envconfig.model_dump(mode="json")
43+
print_section("Environment Configuration", env_data, "env", flat, "yellow")
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""CLI interface to validate the configuration files."""
2+
3+
import click
4+
from rich.console import Console
5+
from rich.panel import Panel
6+
7+
8+
@click.command(name="validate")
9+
@click.pass_context
10+
def validate(ctx: click.Context) -> None:
11+
"""Validate TOML configuration files.
12+
13+
This checks the syntax and schema of application and environment files.
14+
15+
:param ctx: The Click context object, which should already have loaded configurations.
16+
"""
17+
context = ctx.obj
18+
console = Console()
19+
20+
console.print("[bold blue]Running Configuration Validation...[/bold blue]\n")
21+
22+
if context.appconfig:
23+
console.print("✅ [bold]Application Configuration:[/bold] Valid")
24+
for f in (context.appconfigfiles or []):
25+
console.print(f" [dim]- {f}[/dim]")
26+
27+
if context.envconfig:
28+
console.print("\n✅ [bold]Environment Configuration:[/bold] Valid")
29+
if context.envconfigfiles:
30+
for f in context.envconfigfiles:
31+
console.print(f" [dim]- {f}[/dim]")
32+
elif context.envconfig_from_defaults:
33+
console.print(" [dim]- Using internal defaults[/dim]")
34+
35+
console.print(
36+
Panel(
37+
"[bold green]Configuration is valid![/bold green]\n"
38+
"All TOML files match the required schema.",
39+
border_style="green",
40+
expand=False
41+
)
42+
)

src/docbuild/utils/flatten.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"""Utility to flatten nested dictionaries into dotted keys."""
2+
3+
from collections.abc import Generator
4+
from typing import Any
5+
6+
7+
def flatten_dict(d: dict[str, Any], prefix: str = "") -> Generator[tuple[str, Any], None, None]:
8+
"""Recursively flatten a nested dictionary into dotted keys.
9+
10+
:param d: The dictionary to flatten.
11+
:param prefix: The current key prefix (used for recursion).
12+
:yields: Tuples of (dotted_key, value).
13+
"""
14+
for k, v in d.items():
15+
new_key = f"{prefix}.{k}" if prefix else k
16+
if isinstance(v, dict):
17+
yield from flatten_dict(v, new_key)
18+
else:
19+
yield new_key, v

tests/cli/cmd_config/test_application.py

Lines changed: 0 additions & 58 deletions
This file was deleted.

0 commit comments

Comments
 (0)