"""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]