diff --git a/src/constants.ts b/src/constants.ts index 14c7de1..46b6d9f 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,6 +1,7 @@ import { Node } from "@babel/traverse"; import { JavascriptParser } from "./context/language/javascript-parser"; import { ChatCompletionMessageParam } from "groq-sdk/resources/chat/completions"; +import { PythonParser } from "./context/language/python-parser"; export interface PRFile { sha: string; @@ -107,6 +108,7 @@ const EXTENSIONS_TO_PARSERS: Map = new Map([ ["tsx", new JavascriptParser()], ["js", new JavascriptParser()], ["jsx", new JavascriptParser()], + ["py", new PythonParser()], ]); export const getParserForExtension = (filename: string) => { diff --git a/src/context/language/py-parser.py b/src/context/language/py-parser.py new file mode 100644 index 0000000..1c8f0ca --- /dev/null +++ b/src/context/language/py-parser.py @@ -0,0 +1,70 @@ +import ast +import sys +import json + +def find_enclosing_context(file_content, line_start, line_end): + try: + tree = ast.parse(file_content) + except SyntaxError as e: + return {"error": f"Syntax error in the file: {e}"} + + class ContextFinder(ast.NodeVisitor): + def __init__(self, line_start, line_end): + self.line_start = line_start + self.line_end = line_end + self.largest_context = None + self.largest_size = 0 + + def visit_FunctionDef(self, node): + self.check_node(node) + self.generic_visit(node) + + def visit_ClassDef(self, node): + self.check_node(node) + self.generic_visit(node) + + def check_node(self, node): + if hasattr(node, 'end_lineno') and node.lineno <= self.line_start and self.line_end <= node.end_lineno: + size = node.end_lineno - node.lineno + if size > self.largest_size: + self.largest_size = size + self.largest_context = node + + finder = ContextFinder(line_start, line_end) + finder.visit(tree) + + if finder.largest_context: + return { + "type": finder.largest_context.__class__.__name__, + "name": getattr(finder.largest_context, 'name', None), + "start_line": finder.largest_context.lineno, + "end_line": getattr(finder.largest_context, 'end_lineno', None) + } + + return {"error": "No enclosing context found"} + +def main(): + if len(sys.argv) != 4: + print(json.dumps({"error": "Usage: python python_parser.py "})) + return + + file_path = sys.argv[1] + try: + line_start = int(sys.argv[2]) + line_end = int(sys.argv[3]) + except ValueError: + print(json.dumps({"error": "Line numbers must be integers"})) + return + + try: + with open(file_path, 'r') as file: + file_content = file.read() + except FileNotFoundError: + print(json.dumps({"error": f"File not found: {file_path}"})) + return + + context = find_enclosing_context(file_content, line_start, line_end) + print(json.dumps(context, indent=4)) + +if __name__ == "__main__": + main() diff --git a/src/context/language/python-parser.ts b/src/context/language/python-parser.ts index 845e90b..0a64119 100644 --- a/src/context/language/python-parser.ts +++ b/src/context/language/python-parser.ts @@ -1,15 +1,50 @@ import { AbstractParser, EnclosingContext } from "../../constants"; +import * as path from "path"; +import { execSync } from "child_process"; // Use to execute Python script +import * as fs from "fs"; + export class PythonParser implements AbstractParser { + private pythonScriptPath: string; + + constructor() { + this.pythonScriptPath = path.resolve(__dirname, "python_parser.py"); + } + findEnclosingContext( file: string, lineStart: number, lineEnd: number - ): EnclosingContext { - // TODO: Implement this method for Python - return null; + ): EnclosingContext | null { + try { + const result = execSync( + `python ${this.pythonScriptPath} "${file}" ${lineStart} ${lineEnd}`, + { encoding: "utf8" } + ); + + const context = JSON.parse(result); + + if (context.error) { + console.error("Error from Python script:", context.error); + return null; + } + + return { + enclosingContext: context, + } as EnclosingContext; + } catch (error) { + console.error("Failed to execute Python script:", error.message); + return null; + } } + dryRun(file: string): { valid: boolean; error: string } { - // TODO: Implement this method for Python - return { valid: false, error: "Not implemented yet" }; + try { + fs.readFileSync(file, "utf8"); + + execSync(`python ${this.pythonScriptPath} "${file}" 1 1`); + return { valid: true, error: "" }; + } catch (error) { + return { valid: false, error: error.message }; + } } }