From 4693969f8ee5803bdc61ee33af7ed99eea4d55c0 Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Fri, 30 Jan 2026 23:35:15 +0000 Subject: [PATCH] Initial upload: CLI Explain Fix project with CI/CD workflow --- src/cli_explain_fix/parser.py | 248 ++++++++++++++++++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 src/cli_explain_fix/parser.py diff --git a/src/cli_explain_fix/parser.py b/src/cli_explain_fix/parser.py new file mode 100644 index 0000000..e86ec91 --- /dev/null +++ b/src/cli_explain_fix/parser.py @@ -0,0 +1,248 @@ +"""Error pattern parsing module.""" + +import re +from dataclasses import dataclass, field +from typing import Optional, List, Dict, Any + + +@dataclass +class ParsedError: + """Container for parsed error information.""" + error_type: str + message: str + language: str + file_name: Optional[str] = None + line_number: Optional[int] = None + column_number: Optional[int] = None + stack_frames: List[Dict[str, Any]] = field(default_factory=list) + raw_input: str = "" + + def to_dict(self) -> Dict[str, Any]: + """Convert to dictionary for JSON output.""" + return { + 'error_type': self.error_type, + 'message': self.message, + 'language': self.language, + 'file_name': self.file_name, + 'line_number': self.line_number, + 'column_number': self.column_number, + 'stack_frames': self.stack_frames, + } + + +class ErrorParser: + """Parser for various error formats and languages.""" + + PYTHON_TRACEBACK_PATTERN = re.compile( + r'^(?:Traceback \(most recent call last\):\n)?' + r'(?P File "([^"]+)", line (?P\d+), in .+\n' + r' .+\n)?' + r'(?P[A-Za-z]+Error): (?P.+)$', + re.MULTILINE + ) + + PYTHON_SIMPLE_PATTERN = re.compile( + r'^(?P[A-Za-z]+Error): (?P.+)$', + re.MULTILINE + ) + + JS_ERROR_PATTERN = re.compile( + r'^(?:TypeError|ReferenceError|SyntaxError|RangeError|Error): (?P.+)$', + re.MULTILINE + ) + + GO_PANIC_PATTERN = re.compile( + r'^panic: (?P.+)$\n' + r'(?:\n.*goroutine \d+ \[.*\]:\n)?' + r'(?P.*\n)*', + re.MULTILINE + ) + + RUST_PANIC_PATTERN = re.compile( + r'^thread .* panicked at .*"(?P.+)"', + re.MULTILINE + ) + + GENERIC_CLI_PATTERN = re.compile( + r'^(?:error|Error|ERROR)(?::?)\s*(?P.+)$', + re.MULTILINE + ) + + JSON_PARSE_PATTERN = re.compile( + r'^(?:JSONDecodeError|Expecting value|syntax error): (?P.+)$', + re.MULTILINE + ) + + YAML_PARSE_PATTERN = re.compile( + r'^(?:YAMLError|ParserError|ScannerError): (?P.+)$', + re.MULTILINE + ) + + def __init__(self): + self.language_patterns = { + 'python': [ + (self.PYTHON_TRACEBACK_PATTERN, self._parse_python_traceback), + (self.PYTHON_SIMPLE_PATTERN, self._parse_python_simple), + ], + 'javascript': [ + (self.JS_ERROR_PATTERN, self._parse_js_error), + ], + 'go': [ + (self.GO_PANIC_PATTERN, self._parse_go_panic), + ], + 'rust': [ + (self.RUST_PANIC_PATTERN, self._parse_rust_panic), + ], + 'json': [ + (self.JSON_PARSE_PATTERN, self._parse_json_error), + ], + 'yaml': [ + (self.YAML_PARSE_PATTERN, self._parse_yaml_error), + ], + } + + def detect_language(self, input_text: str) -> str: + """Detect the programming language from error format.""" + text = input_text.strip() + + if 'Traceback (most recent call last):' in text or 'File "' in text: + return 'python' + if re.search(r'File ".+\.py", line \d+', text): + return 'python' + if re.search(r'panic:', text): + return 'go' + if re.search(r'thread .* panicked at', text): + return 'rust' + if re.search(r'goroutine \d+', text): + return 'go' + if re.search(r'(JSONDecodeError|Expecting value)', text): + return 'json' + if re.search(r'(YAMLError|ParserError|ScannerError)', text): + return 'yaml' + if re.search(r'^(TypeError|ReferenceError|SyntaxError|RangeError):', text, re.MULTILINE): + return 'javascript' + if re.search(r'[A-Za-z]+Error:', text): + return 'python' + if re.search(r'^error:|^Error:|^ERROR:', text, re.MULTILINE): + return 'cli' + + return 'unknown' + + def parse(self, input_text: str, language: Optional[str] = None) -> ParsedError: + """Parse error text and extract structured information.""" + if language is None: + language = self.detect_language(input_text) + + detected_language = language + + if language in self.language_patterns: + for pattern, parser in self.language_patterns[language]: + match = pattern.search(input_text) + if match: + return parser(match, input_text, detected_language) + + if language == 'cli': + match = self.GENERIC_CLI_PATTERN.search(input_text) + if match: + return ParsedError( + error_type='GenericError', + message=match.group('message').strip(), + language='cli', + raw_input=input_text, + ) + + return ParsedError( + error_type='UnknownError', + message=input_text.strip() if input_text.strip() else 'Unknown error occurred', + language=detected_language, + raw_input=input_text, + ) + + def _parse_python_traceback(self, match: re.Match, input_text: str, language: str) -> ParsedError: + """Parse Python traceback format.""" + exception = match.group('exception') + message = match.group('message') + + stack_frames = [] + frame_match = re.findall( + r' File "([^"]+)", line (\d+), in ([^\n]+)\n ([^\n]+)', + input_text + ) + for frame in frame_match: + stack_frames.append({ + 'file': frame[0], + 'line': int(frame[1]) if frame[1].isdigit() else None, + 'function': frame[2], + 'code': frame[3], + }) + + file_name = None + line_number = None + if stack_frames: + last_frame = stack_frames[-1] + file_name = last_frame.get('file') + line_number = last_frame.get('line') + + return ParsedError( + error_type=exception, + message=message, + language=language, + file_name=file_name, + line_number=line_number, + stack_frames=stack_frames, + raw_input=input_text, + ) + + def _parse_python_simple(self, match: re.Match, input_text: str, language: str) -> ParsedError: + """Parse simple Python error format.""" + return ParsedError( + error_type=match.group('exception'), + message=match.group('message'), + language=language, + raw_input=input_text, + ) + + def _parse_js_error(self, match: re.Match, input_text: str, language: str) -> ParsedError: + """Parse JavaScript error format.""" + return ParsedError( + error_type=match.group(0).split(':')[0] if ':' in match.group(0) else 'Error', + message=match.group('message'), + language=language, + raw_input=input_text, + ) + + def _parse_go_panic(self, match: re.Match, input_text: str, language: str) -> ParsedError: + """Parse Go panic format.""" + return ParsedError( + error_type='panic', + message=match.group('message'), + language=language, + raw_input=input_text, + ) + + def _parse_rust_panic(self, match: re.Match, input_text: str, language: str) -> ParsedError: + """Parse Rust panic format.""" + return ParsedError( + error_type='panic', + message=match.group('message'), + language=language, + raw_input=input_text, + ) + + def _parse_json_error(self, match: re.Match, input_text: str, language: str) -> ParsedError: + """Parse JSON parse error format.""" + return ParsedError( + error_type='JSONParseError', + message=match.group('message'), + language=language, + raw_input=input_text, + ) + + def _parse_yaml_error(self, match: re.Match, input_text: str, language: str) -> ParsedError: + """Parse YAML parse error format.""" + return ParsedError( + error_type='YAMLParseError', + message=match.group('message'), + language=language, + raw_input=input_text, + )