Initial upload: Shell History Alias Generator with full test suite
Some checks failed
CI / test (push) Has been cancelled

This commit is contained in:
2026-02-01 08:51:30 +00:00
parent cbf0a4e978
commit fae8074a40

View File

@@ -0,0 +1,161 @@
"""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]