- Add return type annotations to __hash__ (-> int) and __eq__ (-> bool) in HistoryEntry - Add TextIO import and type annotations for file parameters - Add type ignore comment for fuzzywuzzy import - Add HistoryEntry import and list type annotations in time_analysis - Add assert statements for Optional[datetime] timestamps - Add TypedDict classes for type-safe pattern dictionaries - Add CommandPattern import and list[CommandPattern] type annotation - Add -> None return types to all test methods - Remove unused HistoryEntry import (F401)
116 lines
2.8 KiB
Python
116 lines
2.8 KiB
Python
"""Search command for fuzzy searching shell history."""
|
|
|
|
from typing import Optional
|
|
|
|
import click
|
|
from rich.console import Console
|
|
from rich.table import Table
|
|
|
|
from shellhist.core import HistoryLoader
|
|
from shellhist.core.search import fuzzy_search
|
|
|
|
|
|
@click.command("search")
|
|
@click.argument("query", type=str)
|
|
@click.option(
|
|
"--history",
|
|
"-H",
|
|
type=str,
|
|
help="Path to history file",
|
|
)
|
|
@click.option(
|
|
"--threshold",
|
|
"-t",
|
|
type=int,
|
|
default=70,
|
|
help="Minimum similarity threshold (0-100, default: 70)",
|
|
)
|
|
@click.option(
|
|
"--limit",
|
|
"-l",
|
|
type=int,
|
|
default=20,
|
|
help="Maximum number of results (default: 20)",
|
|
)
|
|
@click.option(
|
|
"--reverse/--no-reverse",
|
|
default=False,
|
|
help="Reverse sort order (newest first)",
|
|
)
|
|
@click.option(
|
|
"--recent",
|
|
"-r",
|
|
is_flag=True,
|
|
default=False,
|
|
help="Boost scores for recent commands (within 24h)",
|
|
)
|
|
@click.option(
|
|
"--shell",
|
|
"-s",
|
|
type=click.Choice(["bash", "zsh"]),
|
|
help="Shell type for parsing",
|
|
)
|
|
@click.pass_context
|
|
def search_command(
|
|
ctx: click.Context,
|
|
query: str,
|
|
history: Optional[str],
|
|
threshold: int,
|
|
limit: int,
|
|
reverse: bool,
|
|
recent: bool,
|
|
shell: Optional[str],
|
|
) -> None:
|
|
"""Search shell history with fuzzy matching.
|
|
|
|
Examples:
|
|
|
|
\b
|
|
shellhist search "git commit"
|
|
shellhist search "npm run" --threshold 80 --limit 10
|
|
shellhist search "docker ps" --recent
|
|
"""
|
|
console = Console()
|
|
|
|
try:
|
|
loader = HistoryLoader(history_path=history)
|
|
store = loader.load()
|
|
|
|
if not store.entries:
|
|
console.print("[yellow]No entries found in history.[/yellow]")
|
|
return
|
|
|
|
results = fuzzy_search(
|
|
store,
|
|
query,
|
|
threshold=threshold,
|
|
limit=limit,
|
|
reverse=reverse,
|
|
recent=recent,
|
|
)
|
|
|
|
if not results:
|
|
console.print(f"[yellow]No matches found for '{query}'.[/yellow]")
|
|
return
|
|
|
|
console.print(f"\n[bold cyan]Search Results for '{query}'[/bold cyan]")
|
|
|
|
table = Table(show_header=True, header_style="bold magenta")
|
|
table.add_column("#", width=4)
|
|
table.add_column("Match", width=6)
|
|
table.add_column("Command", width=70)
|
|
|
|
for i, (entry, score) in enumerate(results, 1):
|
|
score_str = f"{score}%".rjust(4)
|
|
cmd = entry.command[:68] if len(entry.command) > 68 else entry.command
|
|
table.add_row(str(i), score_str, cmd)
|
|
|
|
console.print(table)
|
|
|
|
except FileNotFoundError as e:
|
|
console.print(f"[red]Error: {e}[/red]")
|
|
ctx.exit(1)
|
|
except Exception as e:
|
|
console.print(f"[red]Error searching history: {e}[/red]")
|
|
ctx.exit(1)
|