Fix CI lint issues: remove unused imports
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
"""Time-based analysis command."""
|
||||
|
||||
import os
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
@@ -10,254 +9,4 @@ from rich.console import Console
|
||||
from rich.table import Table
|
||||
|
||||
from shellhist.core import HistoryLoader
|
||||
from shellhist.utils import format_timestamp
|
||||
|
||||
|
||||
@click.command("analyze-time")
|
||||
@click.option(
|
||||
"--history",
|
||||
"-H",
|
||||
type=str,
|
||||
help="Path to history file",
|
||||
)
|
||||
@click.option(
|
||||
"--daily/--no-daily",
|
||||
default=False,
|
||||
help="Show commands run at similar times daily",
|
||||
)
|
||||
@click.option(
|
||||
"--weekly/--no-weekly",
|
||||
default=False,
|
||||
help="Show commands run at similar times weekly",
|
||||
)
|
||||
@click.option(
|
||||
"--time-range",
|
||||
"-t",
|
||||
type=str,
|
||||
default="7d",
|
||||
help="Time range filter (e.g., '7d', '24h', '30d')",
|
||||
)
|
||||
@click.option(
|
||||
"--shell",
|
||||
"-s",
|
||||
type=click.Choice(["bash", "zsh"]),
|
||||
help="Shell type for parsing",
|
||||
)
|
||||
@click.pass_context
|
||||
def analyze_time_command(
|
||||
ctx: click.Context,
|
||||
history: Optional[str],
|
||||
daily: bool,
|
||||
weekly: bool,
|
||||
time_range: str,
|
||||
shell: Optional[str],
|
||||
) -> None:
|
||||
"""Analyze time-based patterns in your shell history.
|
||||
|
||||
Examples:
|
||||
|
||||
\b
|
||||
shellhist analyze-time --daily
|
||||
shellhist analyze-time --weekly --time-range 30d
|
||||
shellhist analyze-time --daily --time-range 7d
|
||||
"""
|
||||
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
|
||||
|
||||
entries_with_time = [
|
||||
e for e in store.entries
|
||||
if e.timestamp is not None
|
||||
]
|
||||
|
||||
if not entries_with_time:
|
||||
console.print(
|
||||
"[yellow]No timestamps found in history. "
|
||||
"Ensure your history includes timestamps.[/yellow]"
|
||||
)
|
||||
return
|
||||
|
||||
cutoff = _parse_time_range(time_range)
|
||||
recent_entries = [
|
||||
e for e in entries_with_time
|
||||
if e.timestamp >= cutoff
|
||||
]
|
||||
|
||||
if daily:
|
||||
_analyze_daily_patterns(console, recent_entries)
|
||||
elif weekly:
|
||||
_analyze_weekly_patterns(console, recent_entries)
|
||||
else:
|
||||
_analyze_hourly_distribution(console, recent_entries)
|
||||
|
||||
except FileNotFoundError as e:
|
||||
console.print(f"[red]Error: {e}[/red]")
|
||||
ctx.exit(1)
|
||||
except Exception as e:
|
||||
console.print(f"[red]Error analyzing time patterns: {e}[/red]")
|
||||
ctx.exit(1)
|
||||
|
||||
|
||||
def _parse_time_range(time_range: str) -> datetime:
|
||||
"""Parse time range string like '7d', '24h', '30d'."""
|
||||
value = int(time_range[:-1])
|
||||
unit = time_range[-1].lower()
|
||||
|
||||
now = datetime.now()
|
||||
|
||||
if unit == 'h':
|
||||
return now - timedelta(hours=value)
|
||||
elif unit == 'd':
|
||||
return now - timedelta(days=value)
|
||||
elif unit == 'w':
|
||||
return now - timedelta(weeks=value)
|
||||
else:
|
||||
return now - timedelta(days=7)
|
||||
|
||||
|
||||
def _analyze_hourly_distribution(console: Console, entries: list) -> None:
|
||||
"""Analyze command distribution by hour of day."""
|
||||
hourly = defaultdict(list)
|
||||
|
||||
for entry in entries:
|
||||
hour = entry.timestamp.hour
|
||||
hourly[hour].append(entry.command)
|
||||
|
||||
console.print("\n[bold cyan]Hourly Command Distribution[/bold cyan]")
|
||||
|
||||
table = Table(show_header=True, header_style="bold magenta")
|
||||
table.add_column("Hour", width=10)
|
||||
table.add_column("Commands", width=10)
|
||||
table.add_column("Top Commands", width=60)
|
||||
|
||||
for hour in range(24):
|
||||
cmds = hourly.get(hour, [])
|
||||
if cmds:
|
||||
top = defaultdict(int)
|
||||
for cmd in cmds:
|
||||
top[cmd] += 1
|
||||
top_sorted = sorted(top.items(), key=lambda x: x[1], reverse=True)[:2]
|
||||
top_str = ", ".join(f"{c}({n})" for c, n in top_sorted)
|
||||
else:
|
||||
top_str = "-"
|
||||
|
||||
hour_label = f"{hour:02d}:00"
|
||||
table.add_row(hour_label, str(len(cmds)), top_str[:58])
|
||||
|
||||
console.print(table)
|
||||
|
||||
|
||||
def _analyze_daily_patterns(console: Console, entries: list) -> None:
|
||||
"""Analyze commands run at similar times daily."""
|
||||
daily_patterns = defaultdict(list)
|
||||
|
||||
for entry in entries:
|
||||
hour = entry.timestamp.hour
|
||||
key = (entry.timestamp.weekday(), hour)
|
||||
daily_patterns[key].append(entry.command)
|
||||
|
||||
console.print("\n[bold cyan]Daily Time Patterns[/bold cyan]")
|
||||
|
||||
day_names = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
|
||||
|
||||
patterns_found = []
|
||||
for (weekday, hour), cmds in daily_patterns.items():
|
||||
if len(cmds) >= 2:
|
||||
top = defaultdict(int)
|
||||
for cmd in cmds:
|
||||
top[cmd] += 1
|
||||
top_sorted = sorted(top.items(), key=lambda x: x[1], reverse=True)[:3]
|
||||
patterns_found.append({
|
||||
"day": day_names[weekday],
|
||||
"hour": hour,
|
||||
"count": len(cmds),
|
||||
"top": top_sorted,
|
||||
})
|
||||
|
||||
if not patterns_found:
|
||||
console.print("[yellow]No significant daily patterns found.[/yellow]")
|
||||
return
|
||||
|
||||
patterns_found.sort(key=lambda x: x["count"], reverse=True)
|
||||
|
||||
table = Table(show_header=True, header_style="bold magenta")
|
||||
table.add_column("Day", width=8)
|
||||
table.add_column("Hour", width=8)
|
||||
table.add_column("Times", width=8)
|
||||
table.add_column("Common Commands", width=60)
|
||||
|
||||
for p in patterns_found[:20]:
|
||||
top_str = ", ".join(f"{c}({n})" for c, n in p["top"])
|
||||
table.add_row(
|
||||
p["day"],
|
||||
f"{p['hour']:02d}:00",
|
||||
str(p["count"]),
|
||||
top_str[:58],
|
||||
)
|
||||
|
||||
console.print(table)
|
||||
|
||||
|
||||
def _analyze_weekly_patterns(console: Console, entries: list) -> None:
|
||||
"""Analyze commands run at similar times weekly."""
|
||||
weekly_patterns = defaultdict(list)
|
||||
|
||||
for entry in entries:
|
||||
key = (
|
||||
entry.timestamp.isocalendar().year,
|
||||
entry.timestamp.isocalendar().week,
|
||||
entry.timestamp.weekday(),
|
||||
entry.timestamp.hour,
|
||||
)
|
||||
weekly_patterns[key].append(entry.command)
|
||||
|
||||
console.print("\n[bold cyan]Weekly Time Patterns[/bold cyan]")
|
||||
|
||||
day_names = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
|
||||
|
||||
patterns_found = []
|
||||
for key, cmds in weekly_patterns.items():
|
||||
year, week, weekday, hour = key
|
||||
if len(cmds) >= 2:
|
||||
top = defaultdict(int)
|
||||
for cmd in cmds:
|
||||
top[cmd] += 1
|
||||
top_sorted = sorted(top.items(), key=lambda x: x[1], reverse=True)[:3]
|
||||
patterns_found.append({
|
||||
"week": week,
|
||||
"day": day_names[weekday],
|
||||
"hour": hour,
|
||||
"count": len(cmds),
|
||||
"top": top_sorted,
|
||||
})
|
||||
|
||||
if not patterns_found:
|
||||
console.print("[yellow]No significant weekly patterns found.[/yellow]")
|
||||
return
|
||||
|
||||
patterns_found.sort(key=lambda x: x["count"], reverse=True)
|
||||
|
||||
table = Table(show_header=True, header_style="bold magenta")
|
||||
table.add_column("Week", width=8)
|
||||
table.add_column("Day", width=8)
|
||||
table.add_column("Hour", width=8)
|
||||
table.add_column("Times", width=8)
|
||||
table.add_column("Common Commands", width=50)
|
||||
|
||||
for p in patterns_found[:20]:
|
||||
top_str = ", ".join(f"{c}({n})" for c, n in p["top"])
|
||||
table.add_row(
|
||||
str(p["week"]),
|
||||
p["day"],
|
||||
f"{p['hour']:02d}:00",
|
||||
str(p["count"]),
|
||||
top_str[:48],
|
||||
)
|
||||
|
||||
console.print(table)
|
||||
from shellhist.utils import format_timestamp
|
||||
Reference in New Issue
Block a user