githubexplorer / app /services /parser_service.py
Kareman's picture
initial commit: full implemntation of git explorer project
acc643d
Raw
History Blame Contribute Delete
5 kB
import ast
from pathlib import Path
from app.models.code import (
ImportInfo,
FunctionInfo,
ClassInfo,
PythonFileInfo,
CallInfo,
)
class FunctionCallVisitor(ast.NodeVisitor):
def __init__(self):
self.calls = []
def visit_Call(self, node: ast.Call):
name = self._get_call_name(node.func)
if name is not None:
self.calls.append(
CallInfo(
name=name,
line=node.lineno,
)
)
self.generic_visit(node)
def _get_call_name(self, node):
if isinstance(node, ast.Name):
return node.id
if isinstance(node, ast.Attribute):
parts = []
while isinstance(node, ast.Attribute):
parts.append(node.attr)
node = node.value
if isinstance(node, ast.Name):
parts.append(node.id)
return ".".join(reversed(parts))
return None
class ParserService:
def _module_name(self, file_path: Path):
parts = list(file_path.parts)
if "app" in parts:
index = parts.index("app")
parts = parts[index:]
parts[-1] = parts[-1].replace(".py", "")
return ".".join(parts)
def parse_file(self, file_path: Path) -> PythonFileInfo:
module_name = self._module_name(file_path)
source = file_path.read_text(
encoding="utf-8",
errors="ignore",
)
lines = source.splitlines()
tree = ast.parse(source)
imports = []
classes = []
functions = []
for node in tree.body:
if isinstance(node, ast.Import):
for alias in node.names:
imports.append(
ImportInfo(
module=alias.name,
name=alias.name,
alias=alias.asname,
)
)
elif isinstance(node, ast.ImportFrom):
for alias in node.names:
imports.append(
ImportInfo(
module=node.module,
name=alias.name,
alias=alias.asname,
)
)
elif isinstance(node, ast.FunctionDef):
functions.append(
self._parse_function(
node,
module_name,
lines=lines,
)
)
elif isinstance(node, ast.ClassDef):
methods = []
for child in node.body:
if isinstance(child, ast.FunctionDef):
methods.append(
self._parse_function(
node=child,
module_name=module_name,
lines=lines,
class_name=node.name,
)
)
class_source = self._get_source_code(
node,
lines,
)
classes.append(
ClassInfo(
name=node.name,
qualified_name=f"{module_name}.{node.name}",
line=node.lineno,
end_line=node.end_lineno,
source_code=class_source,
methods=methods,
)
)
return PythonFileInfo(
path=str(file_path),
imports=imports,
classes=classes,
functions=functions,
)
def _get_source_code(
self,
node,
lines,
) -> str:
return "\n".join(
lines[node.lineno - 1 : node.end_lineno]
)
def _parse_function(
self,
node,
module_name,
lines,
class_name=None,
):
visitor = FunctionCallVisitor()
visitor.visit(node)
if class_name:
qualified = f"{module_name}.{class_name}.{node.name}"
else:
qualified = f"{module_name}.{node.name}"
print("module_name:", repr(module_name))
print("class_name:", repr(class_name))
print("node.name:", repr(node.name))
print("qualified:", repr(qualified))
source_code = self._get_source_code(
node,
lines,
)
return FunctionInfo(
name=node.name,
qualified_name=qualified,
line=node.lineno,
end_line=node.end_lineno,
docstring=ast.get_docstring(node),
source_code=source_code,
calls=visitor.calls,
)