Add CLI modules: validate, sync, merge, history
This commit is contained in:
195
confsync/cli/validate.py
Normal file
195
confsync/cli/validate.py
Normal 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]")
|
||||||
Reference in New Issue
Block a user