From fae8074a403bdef0512d66700c6a6e72eabca2d1 Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Sun, 1 Feb 2026 08:51:30 +0000 Subject: [PATCH] Initial upload: Shell History Alias Generator with full test suite --- shell_alias_gen/command_analyzer.py | 161 ++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 shell_alias_gen/command_analyzer.py diff --git a/shell_alias_gen/command_analyzer.py b/shell_alias_gen/command_analyzer.py new file mode 100644 index 0000000..defb450 --- /dev/null +++ b/shell_alias_gen/command_analyzer.py @@ -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]