Add CLI modules: main, detect, manifest
This commit is contained in:
219
confsync/cli/manifest.py
Normal file
219
confsync/cli/manifest.py
Normal file
@@ -0,0 +1,219 @@
|
||||
"""Manifest command for managing configuration manifests."""
|
||||
|
||||
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
|
||||
import yaml
|
||||
|
||||
from confsync.detectors.base import DetectorRegistry
|
||||
from confsync.core.manifest import ManifestBuilder
|
||||
from confsync.models.config_models import ConfigCategory
|
||||
|
||||
manifest_cmd = typer.Typer(
|
||||
name="manifest",
|
||||
help="Manage configuration manifests",
|
||||
no_args_is_help=True,
|
||||
)
|
||||
|
||||
console = Console()
|
||||
|
||||
|
||||
@manifest_cmd.command("init")
|
||||
def manifest_init(
|
||||
output: str = typer.Option(
|
||||
"confsync_manifest.yaml",
|
||||
"--output", "-o",
|
||||
help="Output file path for the manifest"
|
||||
),
|
||||
category: Optional[str] = typer.Option(
|
||||
None, "--category", "-c",
|
||||
help="Filter by category"
|
||||
),
|
||||
tool: Optional[str] = typer.Option(
|
||||
None, "--tool", "-t",
|
||||
help="Filter by tool name"
|
||||
),
|
||||
exclude: str = typer.Option(
|
||||
"", "--exclude", "-e",
|
||||
help="Comma-separated patterns to exclude"
|
||||
),
|
||||
):
|
||||
"""Initialize a new configuration manifest."""
|
||||
categories = None
|
||||
if category:
|
||||
try:
|
||||
categories = [ConfigCategory(category.lower())]
|
||||
except ValueError:
|
||||
console.print(f"[red]Error:[/red] Unknown category '{category}'")
|
||||
return
|
||||
|
||||
exclude_patterns = [p.strip() for p in exclude.split(",") if p.strip()]
|
||||
|
||||
console.print("[bold]Detecting configuration files...[/bold]")
|
||||
configs = DetectorRegistry.detect_all(categories)
|
||||
|
||||
if tool:
|
||||
configs = [c for c in configs if c.tool_name.lower() == tool.lower()]
|
||||
|
||||
if exclude_patterns:
|
||||
exclude_set = set(exclude_patterns)
|
||||
configs = [c for c in configs if not any(ep in c.path for ep in exclude_set)]
|
||||
|
||||
builder = ManifestBuilder(ignore_patterns=exclude_patterns)
|
||||
manifest = builder.build_from_detected(
|
||||
configs,
|
||||
metadata={"description": f"ConfSync manifest - {len(configs)} files", "categories": [c.value for c in categories] if categories else "all"}
|
||||
)
|
||||
|
||||
builder.save_manifest(manifest, output)
|
||||
|
||||
summary = builder.get_summary(manifest)
|
||||
|
||||
console.print(Panel.fit(
|
||||
"[bold]Manifest created successfully![/bold]",
|
||||
style="green",
|
||||
subtitle=f"Output: {output}",
|
||||
))
|
||||
console.print(f"Total files: {summary['total_files']}")
|
||||
console.print("\n[bold]By Category:[/bold]")
|
||||
for cat, count in summary['by_category'].items():
|
||||
console.print(f" {cat}: {count}")
|
||||
console.print("\n[bold]By Tool:[/bold]")
|
||||
for tool_name, count in summary['by_tool'].items():
|
||||
console.print(f" {tool_name}: {count}")
|
||||
|
||||
|
||||
@manifest_cmd.command("show")
|
||||
def manifest_show(
|
||||
path: str = typer.Option(
|
||||
"confsync_manifest.yaml",
|
||||
"--path", "-p",
|
||||
help="Path to manifest file"
|
||||
),
|
||||
format: str = typer.Option(
|
||||
"table", "--format", "-f",
|
||||
help="Output format (table, json, yaml)"
|
||||
),
|
||||
):
|
||||
"""Display the contents of a manifest."""
|
||||
if not Path(path).exists():
|
||||
console.print(f"[red]Error:[/red] Manifest file not found: {path}")
|
||||
return
|
||||
|
||||
builder = ManifestBuilder()
|
||||
manifest = builder.load_manifest(path)
|
||||
|
||||
if format == "yaml":
|
||||
console.print(yaml.dump(manifest.to_dict(), default_flow_style=False))
|
||||
elif format == "json":
|
||||
import json
|
||||
console.print(json.dumps(manifest.to_dict(), indent=2, default=str))
|
||||
else:
|
||||
table = Table(title="Configuration Manifest")
|
||||
table.add_column("ID", style="cyan", no_wrap=True)
|
||||
table.add_column("Tool", style="magenta")
|
||||
table.add_column("Category", style="green")
|
||||
table.add_column("File Path", style="yellow")
|
||||
table.add_column("Merge Strategy", style="blue")
|
||||
table.add_column("Priority", justify="right", style="red")
|
||||
|
||||
for entry_id, entry in manifest.entries.items():
|
||||
table.add_row(
|
||||
entry_id[:8],
|
||||
entry.config_file.tool_name,
|
||||
entry.config_file.category.value,
|
||||
entry.config_file.path,
|
||||
entry.merge_strategy,
|
||||
str(entry.priority),
|
||||
)
|
||||
|
||||
console.print(Panel.fit(
|
||||
f"[bold]Manifest Contents[/bold] - {len(manifest.entries)} files",
|
||||
style="blue",
|
||||
))
|
||||
console.print(table)
|
||||
|
||||
|
||||
@manifest_cmd.command("export")
|
||||
def manifest_export(
|
||||
path: str = typer.Option(
|
||||
"confsync_manifest.yaml",
|
||||
"--path", "-p",
|
||||
help="Path to manifest file"
|
||||
),
|
||||
output: str = typer.Option(
|
||||
"manifest_backup.zip",
|
||||
"--output", "-o",
|
||||
help="Output archive path"
|
||||
),
|
||||
):
|
||||
"""Export manifest with configurations to an archive."""
|
||||
from zipfile import ZipFile
|
||||
import json
|
||||
|
||||
if not Path(path).exists():
|
||||
console.print(f"[red]Error:[/red] Manifest file not found: {path}")
|
||||
return
|
||||
|
||||
builder = ManifestBuilder()
|
||||
manifest = builder.load_manifest(path)
|
||||
|
||||
try:
|
||||
with ZipFile(output, 'w') as zipf:
|
||||
zipf.write(path, "manifest.yaml")
|
||||
|
||||
for entry_id, entry in manifest.entries.items():
|
||||
config_path = entry.config_file.path
|
||||
if Path(config_path).exists():
|
||||
arcname = f"configs/{Path(config_path).name}"
|
||||
zipf.write(config_path, arcname)
|
||||
|
||||
manifest_data = json.dumps({
|
||||
"manifest_version": manifest.version,
|
||||
"files_count": len(manifest.entries),
|
||||
"exported_at": str(manifest.created_at),
|
||||
}, indent=2)
|
||||
zipf.writestr("metadata.json", manifest_data)
|
||||
|
||||
console.print(f"[green]Successfully exported manifest to {output}[/green]")
|
||||
except Exception as e:
|
||||
console.print(f"[red]Error exporting manifest:[/red] {e}")
|
||||
|
||||
|
||||
@manifest_cmd.command("summary")
|
||||
def manifest_summary(
|
||||
path: str = typer.Option(
|
||||
"confsync_manifest.yaml",
|
||||
"--path", "-p",
|
||||
help="Path to manifest file"
|
||||
),
|
||||
):
|
||||
"""Show summary statistics of a manifest."""
|
||||
if not Path(path).exists():
|
||||
console.print(f"[red]Error:[/red] Manifest file not found: {path}")
|
||||
return
|
||||
|
||||
builder = ManifestBuilder()
|
||||
manifest = builder.load_manifest(path)
|
||||
|
||||
summary = builder.get_summary(manifest)
|
||||
|
||||
console.print(Panel.fit(
|
||||
"[bold]Manifest Summary[/bold]",
|
||||
style="blue",
|
||||
))
|
||||
console.print(f"Version: {manifest.version}")
|
||||
console.print(f"Total Files: {summary['total_files']}")
|
||||
console.print(f"Created: {manifest.created_at.isoformat()}")
|
||||
console.print(f"Updated: {manifest.updated_at.isoformat()}")
|
||||
|
||||
console.print("\n[bold]By Category:[/bold]")
|
||||
for cat, count in summary['by_category'].items():
|
||||
console.print(f" {cat}: {count}")
|
||||
|
||||
console.print("\n[bold]By Tool:[/bold]")
|
||||
for tool_name, count in summary['by_tool'].items():
|
||||
console.print(f" {tool_name}: {count}")
|
||||
Reference in New Issue
Block a user