Add CLI modules: validate, sync, merge, history
Some checks failed
CI / test (push) Has been cancelled
CI / build (push) Has been cancelled

This commit is contained in:
2026-02-04 20:05:32 +00:00
parent abe7f17add
commit d2947815fe

195
confsync/cli/validate.py Normal file
View File

@@ -0,0 +1,195 @@
"""Validate command for configuration validation."""
from pathlib import Path
from typing import Optional
import typer
from rich.console import Console
from rich.table import Table
from rich.panel import Panel
from confsync.detectors.base import DetectorRegistry
from confsync.core.validator import Validator
from confsync.models.config_models import ConfigCategory, ValidationResult
validate_cmd = typer.Typer(
name="validate",
help="Validate configuration files",
no_args_is_help=True,
)
console = Console()
@validate_cmd.command("all")
def validate_all(
category: Optional[str] = typer.Option(
None, "--category", "-c",
help="Filter by category"
),
strict: bool = typer.Option(False, "--strict", "-s", help="Treat warnings as errors"),
quiet: bool = typer.Option(False, "--quiet", "-q", help="Only show issues"),
):
"""Validate all detected configuration files."""
categories = None
if category:
try:
categories = [ConfigCategory(category.lower())]
except ValueError:
console.print(f"[red]Error:[/red] Unknown category '{category}'")
return
configs = DetectorRegistry.detect_all(categories)
if not configs:
console.print("[yellow]No configuration files detected to validate.[/yellow]")
return
validator = Validator()
result = validator.validate_manifest(configs)
_display_validation_result(result, strict, quiet)
@validate_cmd.command("file")
def validate_file(
path: str = typer.Argument(..., help="Path to configuration file"),
strict: bool = typer.Option(False, "--strict", "-s", help="Treat warnings as errors"),
):
"""Validate a specific configuration file."""
from confsync.models.config_models import ConfigFile
if not Path(path).exists():
console.print(f"[red]Error:[/red] File not found: {path}")
return
config = ConfigFile(
path=path,
name=Path(path).name,
category=ConfigCategory.MISC,
tool_name="unknown",
)
validator = Validator()
result = validator.validate_file(config)
_display_validation_result(result, strict)
@validate_cmd.command("manifest")
def validate_manifest(
path: str = typer.Option(
"confsync_manifest.yaml",
"--path", "-p",
help="Path to manifest file"
),
strict: bool = typer.Option(False, "--strict", "-s", help="Treat warnings as errors"),
):
"""Validate configurations in a manifest file."""
from confsync.core.manifest import ManifestBuilder
if not Path(path).exists():
console.print(f"[red]Error:[/red] Manifest file not found: {path}")
return
builder = ManifestBuilder()
manifest = builder.load_manifest(path)
configs = [entry.config_file for entry in manifest.entries.values()]
validator = Validator()
result = validator.validate_manifest(configs)
_display_validation_result(result, strict)
@validate_cmd.command("report")
def validate_report(
path: str = typer.Option(
"confsync_manifest.yaml",
"--path", "-p",
help="Path to manifest file"
),
output: Optional[str] = typer.Option(
None, "--output", "-o",
help="Output file for report (default: print to stdout)"
),
):
"""Generate a detailed validation report."""
from confsync.core.manifest import ManifestBuilder
if not Path(path).exists():
console.print(f"[red]Error:[/red] Manifest file not found: {path}")
return
builder = ManifestBuilder()
manifest = builder.load_manifest(path)
configs = [entry.config_file for entry in manifest.entries.values()]
validator = Validator()
result = validator.validate_manifest(configs)
report = validator.generate_report(result)
if output:
Path(output).write_text(report)
console.print(f"[green]Report written to {output}[/green]")
else:
console.print(report)
def _display_validation_result(result: ValidationResult, strict: bool = False, quiet: bool = False):
"""Display validation result."""
if not quiet:
if result.is_valid:
console.print(Panel.fit(
"[bold green]Validation Passed[/bold green]",
style="green",
subtitle=f"Validated {result.validated_files} files",
))
else:
console.print(Panel.fit(
"[bold yellow]Validation Issues Found[/bold yellow]",
style="yellow",
subtitle=f"Validated {result.validated_files} files, {len(result.issues)} issues",
))
if result.issues:
error_issues = [i for i in result.issues if i.severity.value == "error"]
warning_issues = [i for i in result.issues if i.severity.value == "warning"]
info_issues = [i for i in result.issues if i.severity.value == "info"]
if error_issues or (strict and warning_issues):
if not quiet:
table = Table(title="Issues Found")
table.add_column("Severity", style="red")
table.add_column("Rule", style="cyan")
table.add_column("Message", style="white")
table.add_column("File", style="green")
for issue in result.issues:
if strict and issue.severity.value == "warning":
severity_style = "red"
else:
severity_style = issue.severity.value
table.add_row(
f"[{severity_style}]{issue.severity.value}[/{severity_style}]",
issue.rule,
issue.message[:80] + "..." if len(issue.message) > 80 else issue.message,
issue.file_path or "N/A",
)
console.print(table)
if not quiet:
console.print("\n[bold]Summary:[/bold]")
console.print(f" Errors: {len(error_issues)}")
console.print(f" Warnings: {len(warning_issues)}")
console.print(f" Info: {len(info_issues)}")
else:
if not quiet:
console.print("[green]No issues found![/green]")
if not result.is_valid and strict:
console.print("\n[yellow]Validation failed due to strict mode.[/yellow]")