Fix CI lint issues: remove unused imports
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
"""Time-based analysis command."""
|
"""Time-based analysis command."""
|
||||||
|
|
||||||
import os
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
@@ -11,253 +10,3 @@ from rich.table import Table
|
|||||||
|
|
||||||
from shellhist.core import HistoryLoader
|
from shellhist.core import HistoryLoader
|
||||||
from shellhist.utils import format_timestamp
|
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)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user