Files
2026-02-01 08:51:30 +00:00

162 lines
5.7 KiB
Python

"""Command analysis and alias generation module."""
from collections import Counter
from dataclasses import dataclass, field
from typing import List, Dict, Optional, Tuple
from .parsers import ParsedCommand
@dataclass
class AliasSuggestion:
"""Represents a suggested alias."""
alias_name: str
original_command: str
frequency: int
score: float
def to_shell(self, shell: str = 'bash') -> str:
"""Generate alias string for specified shell."""
if shell == 'fish':
return f"alias {self.alias_name}='{self.original_command}'"
return f"alias {self.alias_name}='{self.original_command}'"
class CommandAnalyzer:
"""Analyzes shell commands and generates alias suggestions."""
MIN_COMMAND_LENGTH = 15
MIN_FREQUENCY = 2
SCORE_WEIGHTS = {
'frequency': 1.0,
'length': 0.5,
'complexity': 0.3,
}
def __init__(self):
self.used_aliases: Dict[str, int] = {}
def analyze_commands(self, commands: List[ParsedCommand]) -> List[AliasSuggestion]:
"""Analyze commands and return alias suggestions."""
command_counter = Counter()
for cmd in commands:
normalized = cmd.normalized
if len(normalized) >= self.MIN_COMMAND_LENGTH:
command_counter[normalized] += 1
suggestions = []
for command, frequency in command_counter.most_common():
if frequency >= self.MIN_FREQUENCY:
score = self._calculate_score(command, frequency)
alias_name = self._generate_alias_name(command, frequency)
if self._is_valid_alias(alias_name, command):
suggestion = AliasSuggestion(
alias_name=alias_name,
original_command=command,
frequency=frequency,
score=score,
)
suggestions.append(suggestion)
suggestions.sort(key=lambda x: x.score, reverse=True)
return suggestions[:50]
def _calculate_score(self, command: str, frequency: int) -> float:
"""Calculate a score for an alias suggestion."""
length_score = min(len(command) / 100, 1.0)
freq_score = min(frequency / 10, 1.0)
complexity = self._calculate_complexity(command)
complexity_score = min(complexity / 20, 1.0)
return (
freq_score * self.SCORE_WEIGHTS['frequency'] +
length_score * self.SCORE_WEIGHTS['length'] +
complexity_score * self.SCORE_WEIGHTS['complexity']
)
def _calculate_complexity(self, command: str) -> int:
"""Calculate command complexity based on various factors."""
complexity = 0
words = command.split()
complexity += len(words) * 2
complexity += sum(1 for w in words if len(w) > 5)
complexity += sum(1 for c in command if c in '-/')
complexity += command.count('--') * 2
flags = [w for w in words if w.startswith('-')]
complexity += len(flags) * 2
return complexity
def _generate_alias_name(self, command: str, frequency: int) -> str:
"""Generate a short alias name from a command."""
words = command.split()
if not words:
return 'alias_cmd'
candidates = []
first_letters = ''.join(w[0] for w in words if w and len(w) > 0)
if len(first_letters) >= 2:
candidates.append(first_letters[:8].lower())
main_words = [w for w in words if not w.startswith('-') and not w.startswith('$')]
if main_words:
meaningful = [w.rstrip(',.;:') for w in main_words if len(w) > 2]
if meaningful:
base = meaningful[0]
candidates.append(base[:10].lower())
if len(meaningful) > 1:
candidates.append((meaningful[0][:4] + meaningful[1][:4]).lower())
keywords = ['git', 'docker', 'npm', 'pip', 'python', 'ssh', 'curl', 'grep']
for kw in keywords:
if kw in command.lower():
candidates.insert(0, kw)
break
base_name = candidates[0] if candidates else 'cmd'
suffix = 0
final_name = base_name
while final_name in self.used_aliases or not self._is_valid_alias_name(final_name):
suffix += 1
final_name = f"{base_name}{suffix}"
if suffix > 100:
final_name = f"alias_{hash(command) % 10000}"
break
self.used_aliases[final_name] = frequency
return final_name
def _is_valid_alias_name(self, name: str) -> bool:
"""Check if name is a valid shell alias identifier."""
if not name:
return False
if not name[0].isalpha() and name[0] != '_':
return False
return all(c.isalnum() or c == '_' for c in name)
def _is_valid_alias(self, alias_name: str, command: str) -> bool:
"""Validate alias is suitable for creation."""
if not self._is_valid_alias_name(alias_name):
return False
if len(alias_name) > 64:
return False
if len(command) > 1000:
return False
return True
def filter_suggestions(
self,
suggestions: List[AliasSuggestion],
min_score: float = 0.1,
max_count: int = 20
) -> List[AliasSuggestion]:
"""Filter suggestions by score and count."""
filtered = [s for s in suggestions if s.score >= min_score]
return filtered[:max_count]