Skip to content

topmark.cli.commands.config_check

topmark / cli / commands / config_check

TopMark config check command.

Validates the effective TopMark configuration after applying defaults, project/user config files, and any CLI overrides.

Input modes
  • This command is file-agnostic: positional PATHS and '-' STDIN content mode are ignored (with a warning if present).
  • --files-from/--include-from/--exclude-from are accepted and used to populate pattern/path sources (as inputs with empty PATH list).
Resolution mechanism
  • This command uses the same layered config discovery rules as other CLI commands, via topmark.cli.config_resolver, to build the effective config snapshot.

config_check_command

config_check_command(
    *,
    verbosity,
    quiet,
    color_mode,
    no_color,
    no_config,
    config_files,
    strict,
    output_format,
)

Validate the final merged TopMark configuration.

Builds the effective configuration from defaults, discovered and explicit config files, and CLI overrides, then validates it and reports diagnostics.

Parameters:

Name Type Description Default
verbosity int

Increase TEXT output detail.

required
quiet bool

Suppress TEXT output.

required
color_mode ColorMode | None

Color mode for text format (default: auto).

required
no_color bool

If set, disable color mode.

required
no_config bool

If True, skip loading project/user configuration files.

required
config_files list[str]

Additional configuration file paths to load and merge.

required
strict bool | None

if True, report warnings as errors.

required
output_format OutputFormat | None

Output format to use (text, markdown, json, or ndjson).

required

Raises:

Type Description
NotImplementedError

When providing an unsupported OutputType.

Source code in src/topmark/cli/commands/config_check.py
@click.command(
    name=CliCmd.CONFIG_CHECK,
    context_settings=GROUP_CONTEXT_SETTINGS,
    help=(
        "Validate the effective merged TopMark configuration and report diagnostics. "
        "This command is file-agnostic: positional PATHS "
        "and file-processing STDIN modes are rejected. "
        f"Use {CliOpt.STRICT} to treat warnings as errors and "
        f"{CliOpt.OUTPUT_FORMAT}={OutputFormat.JSON.value}/{OutputFormat.NDJSON.value} "
        "for machine-readable output."
    ),
    epilog=(
        "\b\n"
        "Examples:\n"
        "  # Validate the effective runtime configuration\n"
        f"  topmark {CliCmd.CONFIG} {CliCmd.CONFIG_CHECK}\n"
        "  # Fail on warnings (strict mode)\n"
        f"  topmark {CliCmd.CONFIG} {CliCmd.CONFIG_CHECK} {CliOpt.STRICT}\n"
        "  # Emit machine-readable diagnostics\n"
        f"  topmark {CliCmd.CONFIG} {CliCmd.CONFIG_CHECK} "
        f"{CliOpt.OUTPUT_FORMAT}={OutputFormat.JSON.value}\n"
        "\n"
        "\b\n"
        "Notes:\n"
        "  • Configuration is built from defaults, discovered files, "
        f"explicit {CliOpt.CONFIG_FILES} files, and CLI overrides.\n"
        "  • Exit status is non-zero on validation failure "
        f"(errors, or warnings with {CliOpt.STRICT}).\n"
        "  • NDJSON emits a sequence of structured records.\n"
    ),
)
@common_color_options
@common_text_output_verbosity_options
@common_text_output_quiet_options
@common_config_resolution_options
@config_strict_options
@common_output_format_options
def config_check_command(
    *,
    # common_ui_options (verbosity, color):
    verbosity: int,
    quiet: bool,
    color_mode: ColorMode | None,
    no_color: bool,
    # common_config_resolution_options:
    no_config: bool,
    config_files: list[str],
    # config_strict_options:
    strict: bool | None,
    # common_output_format_options:
    output_format: OutputFormat | None,
) -> None:
    """Validate the final merged TopMark configuration.

    Builds the effective configuration from defaults, discovered and explicit
    config files, and CLI overrides, then validates it and reports diagnostics.

    Args:
        verbosity: Increase TEXT output detail.
        quiet: Suppress TEXT output.
        color_mode: Color mode for text format (default: auto).
        no_color: If set, disable color mode.
        no_config: If True, skip loading project/user configuration files.
        config_files: Additional configuration file paths to load and merge.
        strict: if True, report warnings as errors.
        output_format: Output format to use (``text``, ``markdown``, ``json``, or ``ndjson``).

    Raises:
        NotImplementedError: When providing an unsupported OutputType.
    """
    ctx: click.Context = click.get_current_context()
    state: TopmarkCliState = bootstrap_cli_state(ctx)

    # Effective output format (stored early so shared initialization sees it).
    state.output_format = output_format or OutputFormat.TEXT

    # Initialize the common state (verbosity, color mode) and initialize console
    init_common_state(
        ctx,
        verbosity=verbosity,
        quiet=quiet,
        color_mode=color_mode,
        no_color=no_color,
    )

    # Retrieve effective human facing program-output verbosity for gating extra details
    verbosity_level: int = state.verbosity

    # Select the console
    console: ConsoleProtocol = state.console

    # Machine metadata
    meta: MetaPayload = build_meta_payload()

    # Output format
    fmt: OutputFormat = state.output_format

    apply_color_policy_for_output_format(ctx, fmt=fmt)
    enable_color: bool = state.color_enabled

    # `config check` is file-agnostic w.r.t. positional PATHS and STDIN content mode ('-').
    # However, we still accept `*_from` options so callers can validate pattern/path sources.
    apply_ignore_positional_paths_policy(
        ctx,
        warn_stdin_dash=True,
    )

    # Build a merged draft config (we do not need an InputPlan since we're not processing files)
    resolved_config: ResolvedConfigDraft = resolve_toml_sources_and_build_mutable_config(
        strict=strict,
        no_config=no_config,
        extra_config_files=[Path(p) for p in config_files],
    )
    resolved_toml: ResolvedTopmarkTomlSources = resolved_config.resolved

    # Freeze ensures sanitize + schema validation runs (and produces diagnostics)
    config: FrozenConfig = resolved_config.draft.freeze()
    logger.trace("Run config after layered CLI overrides: %s", config)

    # Check config validity:
    config_valid: bool = is_config_valid(
        config,
        resolved=resolved_toml,
    )

    logger.trace("MutableConfig after merging CLI and discovered config: %s", resolved_config.draft)

    def _exit(ctx: click.Context, *, success: bool) -> None:
        """Select exit code depending on outcome."""
        ctx.exit(0 if success else ExitCode.FAILURE)

    if fmt in (OutputFormat.JSON, OutputFormat.NDJSON):
        emit_config_check_machine(
            console=console,
            meta=meta,
            config=config,
            resolved_toml=resolved_toml,
            strict=bool(resolved_toml.strict),
            ok=config_valid,
            fmt=fmt,
        )
        _exit(ctx, success=config_valid)

    # Human formats: prepare shared data once for TEXT/MARKDOWN emitters.
    report: ConfigCheckHumanReport = build_config_check_human_report(
        config=config,
        resolved_sources=resolved_toml,
        ok=config_valid,
        strict=bool(resolved_toml.strict),
        verbosity_level=verbosity_level,
        styled=enable_color,
    )

    if fmt == OutputFormat.MARKDOWN:
        console.print(
            render_config_check_markdown(report),
            nl=False,
        )
        _exit(ctx, success=config_valid)

    if fmt == OutputFormat.TEXT:
        if not state.quiet:
            console.print(render_config_check_text(report))
        _exit(ctx, success=config_valid)

    # Defensive guard in case OutputFormat gains new members
    raise NotImplementedError(f"Unsupported output format: {fmt!r}")