"""CLI interface for Shell History Alias Generator.""" import sys from pathlib import Path from typing import List, Optional import click from rich.console import Console from rich.panel import Panel from rich.text import Text from .parsers import HistoryParserFactory, BashHistoryParser, ZshHistoryParser, FishHistoryParser from .command_analyzer import CommandAnalyzer from .interactive_ui import InteractiveUI from .export_manager import ExportManager console = Console() @click.group() @click.version_option(version="0.1.0") def main(): """Shell History Alias Generator - Analyze shell history and create aliases.""" pass @main.command() @click.argument( 'history_file', type=click.Path(exists=True, file_okay=True, dir_okay=False, path_type=Path), required=False ) @click.option( '--shell', type=click.Choice(['bash', 'zsh', 'fish', 'auto']), default='auto', help='Shell type for history parsing' ) @click.option( '--min-length', type=int, default=15, help='Minimum command length to consider' ) @click.option( '--min-frequency', type=int, default=2, help='Minimum command frequency to consider' ) @click.option( '--export', type=click.Choice(['bash', 'zsh', 'fish']), default=None, help='Export format for generated aliases' ) @click.option( '--output', type=click.Path(path_type=Path), default=None, help='Output file path for export' ) @click.option( '--non-interactive', is_flag=True, default=False, help='Skip interactive review mode' ) def analyze( history_file: Optional[Path], shell: str, min_length: int, min_frequency: int, export: Optional[str], output: Optional[Path], non_interactive: bool ): """Analyze shell history and generate alias suggestions.""" console.print(Panel.fit( Text("Shell History Alias Generator", style="bold magenta"), subtitle="Analyzing your command history..." )) parser = _get_parser(shell, history_file) if parser is None: console.print("[red]Error: Could not determine shell type.[/red]") sys.exit(1) if history_file: commands = parser.parse_file(str(history_file)) else: commands = parser.parse_all_history_files() if hasattr(parser, 'parse_all_history_files') else [] if not commands: default_paths = parser.get_default_history_locations() if default_paths: if isinstance(default_paths, list) and default_paths: commands = parser.parse_file(default_paths[0]) if isinstance(default_paths[0], str) else [] else: commands = [] if not commands: console.print("[yellow]No commands found in history.[/yellow]") sys.exit(0) console.print(f"[dim]Parsed {len(commands)} commands[/dim]") analyzer = CommandAnalyzer() suggestions = analyzer.analyze_commands(commands) if not suggestions: console.print("[yellow]No alias suggestions generated.[/yellow]") sys.exit(0) if non_interactive: selected = suggestions else: ui = InteractiveUI() ui.display_suggestions(suggestions, "Generated Alias Suggestions") console.print("\n[bold]Reviewing suggestions...[/bold]") selected = ui.run_review_session(suggestions) if not selected: console.print("[yellow]No aliases selected.[/yellow]") sys.exit(0) ui.display_summary(selected) if not ui.confirm_selection(): console.print("[yellow]Cancelled.[/yellow]") sys.exit(0) if export: export_manager = ExportManager() if output: content = export_manager.export(selected, export, str(output)) console.print(f"[green]Exported to {output}[/green]") else: content = export_manager.export(selected, export) console.print("\n[bold]Generated aliases:[/bold]") console.print(content) else: _print_selected_aliases(selected) @main.command() @click.argument( 'history_file', type=click.Path(exists=True, file_okay=True, dir_okay=False, path_type=Path), required=False ) @click.option( '--shell', type=click.Choice(['bash', 'zsh', 'fish', 'auto']), default='auto', help='Shell type for history parsing' ) @click.option( '--format', 'output_format', type=click.Choice(['bash', 'zsh', 'fish']), default='bash', help='Export format' ) @click.option( '--append/--replace', default=False, help='Append to rcfile or replace aliases section' ) def install( history_file: Optional[Path], shell: str, output_format: str, append: bool ): """Install aliases directly to shell rc file.""" parser = _get_parser(shell, history_file) if parser is None: console.print("[red]Error: Could not determine shell type.[/red]") sys.exit(1) if history_file: commands = parser.parse_file(str(history_file)) else: commands = parser.parse_all_history_files() if hasattr(parser, 'parse_all_history_files') else [] if not commands: console.print("[yellow]No commands found in history.[/yellow]") sys.exit(0) analyzer = CommandAnalyzer() suggestions = analyzer.analyze_commands(commands) if not suggestions: console.print("[yellow]No alias suggestions generated.[/yellow]") sys.exit(0) rcfile = _get_rcfile_path(output_format) if rcfile is None or not rcfile.exists(): console.print(f"[red]Could not find {output_format} rc file.[/red]") sys.exit(1) export_manager = ExportManager() if export_manager.export_to_rcfile(suggestions, output_format, str(rcfile)): console.print(f"[green]Aliases added to {rcfile}[/green]") console.print(f"[dim]Restart your shell or run: source {rcfile}[/dim]") else: console.print("[red]Failed to write to rc file.[/red]") sys.exit(1) @main.command() def shells(): """List supported shell types.""" shells_list = HistoryParserFactory.get_supported_shells() console.print("Supported shell types:") for shell in shells_list: console.print(f" - {shell}") @main.command() def locations(): """Show default history file locations.""" console.print("Default history file locations:\n") for shell_name in ['bash', 'zsh', 'fish']: parser = HistoryParserFactory.get_parser(shell_name) if parser: locations = parser.get_default_history_locations() console.print(f"[bold]{shell_name.upper()}[/bold]:") for loc in locations: exists = "[green]✓[/green]" if Path(loc).exists() else "[red]✗[/red]" console.print(f" {exists} {loc}") console.print() def _get_parser(shell: str, history_file: Optional[Path]) -> Optional["BaseParser"]: """Get appropriate history parser.""" if shell == 'auto' and history_file: detected = HistoryParserFactory.detect_shell_from_file(str(history_file)) if detected: shell = detected if shell == 'auto': shell = 'bash' return HistoryParserFactory.get_parser(shell) def _get_rcfile_path(shell: str) -> Optional[Path]: """Get the rc file path for a shell.""" from os.path import expanduser rcfiles = { 'bash': expanduser('~/.bashrc'), 'zsh': expanduser('~/.zshrc'), 'fish': expanduser('~/.config/fish/config.fish'), } path = rcfiles.get(shell) return Path(path) if path else None def _print_selected_aliases(suggestions: List): """Print selected aliases in a nice format.""" console.print("\n[bold green]Generated Aliases:[/bold green]\n") for suggestion in suggestions: console.print(f"[bold green]={suggestion.alias_name}[/bold green]='{suggestion.original_command}'") console.print("\n[dim]Add these to your shell rc file or run with --export[/dim]") if __name__ == "__main__": main()