Skip to content

Commit

Permalink
Code Explanation complete.
Browse files Browse the repository at this point in the history
  • Loading branch information
hrshdhgd committed Feb 9, 2024
1 parent 26f5f07 commit afdc5b9
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 13 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ line-length = 120
target-version = ["py38", "py39", "py310"]

[tool.ruff]
extend-ignore = [
lint.extend-ignore = [
"D211", # `no-blank-line-before-class`
"D212", # `multi-line-summary-first-line`
"D203", # `blank-line-before-docstring`
Expand Down
24 changes: 22 additions & 2 deletions src/codergpt/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@

logger = logging.getLogger(__name__)

path_argument = click.argument("path", type=click.Path(exists=True))

coder = CoderGPT()


@click.group()
@click.option("-v", "--verbose", count=True)
Expand All @@ -38,12 +42,28 @@ def main(verbose: int, quiet: bool):


@main.command()
@click.argument("path", type=click.Path(exists=True))
@path_argument
def inspect(path: Union[str, Path, TextIO]):
"""Inspect package to show file-language-map."""
coder = CoderGPT()
coder.inspect_package(path=path)


@main.command()
@path_argument
@click.option("-f", "--function", help="Function name to explain.")
@click.option("-c", "--classname", help="Class name to explain.")
def explain(path: Union[str, Path], function: str, classname: str):
"""Inspect package to show file-language-map."""
# Ensure path is a string or Path object for consistency
if isinstance(path, str):
path = Path(path)

# Check if path is a file
if path.is_file():
coder.explainer(path=path, function=function, classname=classname)
else:
raise ValueError("The path provided is not a file.")


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions src/codergpt/commenter/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Commenter module for the package."""
140 changes: 140 additions & 0 deletions src/codergpt/explainer/explainer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
"""Explainer Module."""

import ast
from pathlib import Path
from typing import Any, Dict, Optional, Union

from langchain_core.runnables.base import RunnableSerializable


class ExpressionEvaluator(ast.NodeVisitor):
"""Evaluate the code expression and extract the source code of the specified function or class."""

def __init__(self, source_code, function_name=None, class_name=None):
"""
Initialize the ExpressionEvaluator class.
:param function_name: The name of the function to find in the source code.
:type function_name: str or None
:param class_name: The name of the class to find in the source code.
:type class_name: str or None
"""
self.function_code = None
self.class_code = None
self.function_name = function_name
self.class_name = class_name
self.source_code = source_code

def visit_FunctionDef(self, node):
"""
Visit a FunctionDef (function definition) node in the AST.
If the function name matches the target function name, it extracts the source segment.
:param node: The node representing a function definition in the AST.
:type node: ast.FunctionDef
"""
if self.function_name == node.name:
self.function_code = ast.get_source_segment(self.source_code, node)
# Continue the traversal in case there are nested functions or classes
self.generic_visit(node)

def visit_ClassDef(self, node):
"""
Visit a ClassDef (class definition) node in the AST.
If the class name matches the target class name, it extracts the source segment.
:param node: The node representing a class definition in the AST.
:type node: ast.ClassDef
"""
if self.class_name == node.name:
self.class_code = ast.get_source_segment(self.source_code, node)
# Continue the traversal in case there are nested functions or classes
self.generic_visit(node)


class CodeExplainer:
"""Code Explainer class that extracts and explains code from a given file."""

def __init__(self, chain: RunnableSerializable[Dict, Any]):
"""
Initialize the CodeExplainer class with a runnable chain.
:param chain: A RunnableSerializable object capable of executing tasks.
"""
self.chain = chain

def get_function_code(
self, filename: str, function_name: Optional[str] = None, class_name: Optional[str] = None
) -> Optional[str]:
"""
Extract and return the source code of the specified function or class from a file.
:param filename: The path to the file containing the code.
:param function_name: The name of the function to extract code for. Default is None.
:param class_name: The name of the class to extract code for. Default is None.
:return: The extracted source code of the specified function or class, if found.
"""
with open(filename, "r") as source_file:
source_code = source_file.read()

# Parse the source code into an AST
parsed_code = ast.parse(source_code)

# Create a visitor instance and walk through the AST
visitor = ExpressionEvaluator(source_code=source_code, function_name=function_name, class_name=class_name)
visitor.visit(parsed_code)
if function_name:
return visitor.function_code
elif class_name:
return visitor.class_code

def explain(self, path: Union[str, Path], function: Optional[str] = None, classname: Optional[str] = None):
"""
Explain the contents of the code file by invoking the runnable chain.
:param path: The path to the code file to be explained.
:param function: The name of the function to explain. Default is None.
:param classname: The name of the class to explain. Default is None.
"""
if function:
code = self.get_function_code(filename=path, function_name=function)
response = self.chain.invoke({"input": f"Explain the following code: \n\n```\n{code}\n```"})

# Pretty print the response
print(f"Explanation for '{function}':\n{response.content}")
elif classname:
code = self.get_function_code(filename=path, class_name=classname)
response = self.chain.invoke({"input": f"Explain the following code: \n\n```\n{code}\n```"})
# Pretty print the response
print(f"Explanation for '{classname}':\n{response.content}")

# # Ensure path is a string or Path object for consistency
# if isinstance(path, str):
# path = Path(path)

# with open(path, "r") as file:
# code = file.read()

# # Guess the lexer based on the file name and content
# lexer = guess_lexer_for_filename(path.name, code)

# # Tokenize the code using the guessed lexer
# tokens = list(pygments.lex(code, lexer))

# all_functions = []
# all_classes = []
# # Process the tokens (for demonstration, just print them)
# for token_type, token_value in tokens:
# if token_type in Token.Name.Function:
# print(f"{token_type}: {token_value}")
# all_functions.append(token_value)
# elif token_type in Token.Name.Class:
# all_classes.append(token_value)
# else:
# continue

# elif token_type in Token.Comment:
# print(f"Comment: {token_value}")
# Add more conditions as needed to handle different parts of the code
17 changes: 7 additions & 10 deletions src/codergpt/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from tabulate import tabulate

from codergpt.constants import EXTENSION_MAP_FILE, INSPECTION_HEADERS
from codergpt.explainer.explainer import CodeExplainer


class CoderGPT:
Expand All @@ -28,16 +29,6 @@ def __init__(self):
# This chain will be used to send the input to the AI in the correct context.
self.chain = self.prompt | self.llm

# # Invoke the chain with the specific input asking about the programming language
# # associated with the given file extension. The input is formatted to include the
# # file extension in the question and requests a one-word response.
# response = chain.invoke(
# {
# "input": f"Given the file extensions {ext},\
# return just programming language name e.g. 'Python' or 'JavaScript'."
# }
# )

def inspect_package(self, path: Union[str, Path]):
"""Inspecting the code and displaying a mapping of files to their languages in a table."""
print("Inspecting the code.")
Expand Down Expand Up @@ -72,6 +63,12 @@ def inspect_package(self, path: Union[str, Path]):
# Display the results as a table
print(tabulate(file_language_list, headers=INSPECTION_HEADERS))

def explainer(self, path: Union[str, Path], function: str = None, classname=None):
"""Explains contents of the code file."""
# Ensure path is a string or Path object for consistency
code_explainer = CodeExplainer(self.chain)
code_explainer.explain(path=path, function=function, classname=classname)


if __name__ == "__main__":
coder = CoderGPT()
Expand Down

0 comments on commit afdc5b9

Please sign in to comment.