Files
shell-history-alias-generator/shell_alias_gen/cli.py
2026-02-01 08:51:29 +00:00

280 lines
8.0 KiB
Python

"""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]):
"""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):
"""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()