This commit is contained in:
127
shellhist/cli/search.py
Normal file
127
shellhist/cli/search.py
Normal file
@@ -0,0 +1,127 @@
|
||||
"""Search command for the shell history tool."""
|
||||
|
||||
import os
|
||||
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
|
||||
from shellhist.utils import format_timestamp
|
||||
|
||||
|
||||
@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",
|
||||
"-r",
|
||||
default=False,
|
||||
help="Sort by recency (newest first)",
|
||||
)
|
||||
@click.option(
|
||||
"--recent/--no-recent",
|
||||
default=False,
|
||||
help="Boost scores for recent commands (last 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.
|
||||
|
||||
QUERY is the search string to match against your command history.
|
||||
|
||||
Examples:
|
||||
|
||||
\b
|
||||
shellhist search "git commit"
|
||||
shellhist search "npm run" --threshold 80 --limit 10
|
||||
shellhist search "docker ps" --recent
|
||||
"""
|
||||
console = Console()
|
||||
|
||||
try:
|
||||
if shell:
|
||||
os.environ["SHELL"] = f"/bin/{shell}"
|
||||
|
||||
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=store,
|
||||
query=query,
|
||||
threshold=threshold,
|
||||
limit=limit,
|
||||
reverse=reverse,
|
||||
recent=recent,
|
||||
)
|
||||
|
||||
if not results:
|
||||
console.print(f"[yellow]No commands found matching '{query}' with threshold {threshold}[/yellow]")
|
||||
return
|
||||
|
||||
table = Table(show_header=True, header_style="bold magenta")
|
||||
table.add_column("#", width=4)
|
||||
table.add_column("Match %", width=8)
|
||||
table.add_column("Command", width=60)
|
||||
table.add_column("Last Used", width=20)
|
||||
|
||||
for i, (entry, score) in enumerate(results, 1):
|
||||
timestamp = format_timestamp(entry.timestamp)
|
||||
table.add_row(
|
||||
str(i),
|
||||
f"{score}%",
|
||||
entry.command[:58] + ".." if len(entry.command) > 60 else entry.command,
|
||||
timestamp,
|
||||
)
|
||||
|
||||
console.print(table)
|
||||
|
||||
total_unique = len(store.get_unique_commands())
|
||||
console.print(f"\n[dim]Found {len(results)} matches from {total_unique} unique commands[/dim]")
|
||||
|
||||
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)
|
||||
Reference in New Issue
Block a user