Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 29 additions & 19 deletions backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,26 @@
"java": JavaParser
}

# Place this after app initialization

@app.post("/insert-diagram-to-docx")
def insert_diagram_to_docx(repo_path: str, docx_path: str):
"""
Generate Mermaid class diagram and insert it at the end of the specified docx file.
"""
try:
relationships = repo_scanner.extract_class_relationships(repo_path, language="java")
mermaid_code = repo_scanner.generate_mermaid_class_diagram(relationships)
converter = DocumentConverter()
converter.insert_mermaid_diagram_to_docx(mermaid_code, docx_path)
return {"success": True, "message": "Diagram inserted into docx", "docx_path": docx_path}
except Exception as e:
import traceback
print("\n--- ERROR in /insert-diagram-to-docx ---")
traceback.print_exc()
print("--- END ERROR ---\n")
raise HTTPException(status_code=500, detail=f"Failed to insert diagram: {str(e)}")

# ===== CORE API ENDPOINTS =====

@app.get("/")
Expand Down Expand Up @@ -305,12 +325,9 @@ def generate_complete_repo_docs(repo_path: str, output_file: str = "Complete_Rep
# Generate AI docs for key functions
for func in functions[:2]:
try:
func.commits = []
summary = doc_generator.generate_function_doc(func, target_format)
doc_content += f"#### {func.name}\n{summary}\n\n"
except Exception:
doc_content += f"#### {func.name}\n**Parameters:** {', '.join(func.params) if func.params else 'None'}\n**Lines:** {func.lineno}-{func.end_lineno}\n\n"

except Exception as e:
doc_content += f"#### {func.name}\nError generating documentation: {e}\n\n"
doc_content += "---\n\n"
documented_files += 1
except Exception as e:
Expand All @@ -325,6 +342,10 @@ def generate_complete_repo_docs(repo_path: str, output_file: str = "Complete_Rep
- **Languages detected:** {', '.join(structure.get('languages', {}).keys())}

*Generated by Starter Doc Generator*
# Generate and append class relationship diagram
relationships = repo_scanner.extract_class_relationships(repo_path, language="java")
diagram_str = repo_scanner.generate_mermaid_class_diagram(relationships)
doc_content += "\n## Class Relationship Diagram\n" + diagram_str + "\n"
"""

# Save documentation in organized folder structure
Expand Down Expand Up @@ -375,10 +396,6 @@ def generate_individual_docs(repo_path: str, language: str = "java", target_form
elif lang_key == "python":
lang_key = "py"

parser_class = PARSERS.get(lang_key)
if not parser_class or not os.path.exists(file_info["full_path"]):
continue

# Parse functions
parser = parser_class()
functions = parser.parse_file(file_info["full_path"])
Expand Down Expand Up @@ -430,11 +447,7 @@ def generate_individual_docs(repo_path: str, language: str = "java", target_form
with open(doc_path, 'w', encoding='utf-8') as f:
f.write(file_doc_content)

generated_docs.append({
"file_path": file_info['file_path'],
"documentation_file": doc_filename,
"functions_documented": len(functions)
})
generated_docs.append(doc_filename)

except Exception as e:
print(f"Error processing {file_info['file_path']}: {e}")
Expand Down Expand Up @@ -497,10 +510,7 @@ def convert_documentation_to_word(folder_path: str = "documentation-generated"):
}

except Exception as e:
import traceback
traceback.print_exc()
raise HTTPException(status_code=500, detail=f"Word conversion failed: {str(e)}")

pass
@app.post("/convert-single-file")
def convert_single_markdown_file(file_path: str, formats: List[str] = ["docx"]):
"""Convert a single markdown file to Word format only"""
Expand Down Expand Up @@ -561,7 +571,7 @@ def test_all_apis():
try:
# Test parameters for Employee Management System
test_file = "src/main/java/com/example/EmployeeManagementSystem/model/Employee.java"
test_repo = r"C:\Users\User\VisualStudio\Employee-Management-Sys\EmployeeManagementSystem"
test_repo = r"C:\Users\User\Documents\Clone_GithubProject\Employee-Management-Sys\EmployeeManagementSystem"
test_language = "java"

results = {}
Expand Down
41 changes: 32 additions & 9 deletions backend/services/document_converter.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,49 @@
"""
Document conversion service for converting markdown files to Word format only.
PDF conversion removed due to dependency issues.
"""

import os
import subprocess
from docx import Document
from docx.shared import Inches
import os
import re
from typing import Optional
import logging

# Configure logging
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class DocumentConverter:
"""Service for converting markdown documents to Word format only."""

def __init__(self):
"""Initialize the document converter for Word documents only."""
logger.info("DocumentConverter initialized for Word conversion only")


def convert_to_word(self, markdown_file_path: str, output_path: Optional[str] = None) -> str:
# ...existing code...
pass

def insert_mermaid_diagram_to_docx(self, mermaid_code: str, docx_path: str, image_path: str = "diagram.png"):
"""
Render Mermaid diagram to image and insert at the end of a docx file.
"""
# Save mermaid code to temp file
mermaid_file = "temp_mermaid.mmd"
with open(mermaid_file, "w", encoding="utf-8") as f:
f.write(mermaid_code)

# Render to PNG using mermaid-cli
subprocess.run(["mmdc", "-i", mermaid_file, "-o", image_path], check=True)

# Insert image into docx
doc = Document(docx_path)
doc.add_picture(image_path, width=Inches(6))
doc.save(docx_path)

# Clean up temp files
os.remove(mermaid_file)
os.remove(image_path)
def __init__(self):
"""Initialize the document converter for Word documents only."""
logger.info("DocumentConverter initialized for Word conversion only")

def convert_to_word(self, markdown_file_path: str, output_path: Optional[str] = None) -> str:
"""
Convert markdown file to Word document.
Expand Down
132 changes: 132 additions & 0 deletions backend/services/repo_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,88 @@
import json

class RepoScanner:
def extract_class_relationships(self, repo_path: str, language: str = "java") -> dict:
"""
Extract class relationships, attributes, and method signatures for visualization.
Returns: { 'classes': {name: {'parents': [...], 'associations': [...], 'attributes': [...], 'methods': [...]}} }
"""
import re
relationships = {"classes": {}}
class_names = set()
# First pass: collect all class names
for root, dirs, files in os.walk(repo_path):
for file in files:
if language == "java" and file.endswith(".java"):
file_path = os.path.join(root, file)
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
class_matches = re.findall(r'class\s+(\w+)', content)
for cname in class_matches:
class_names.add(cname)
except Exception:
continue
# Second pass: extract relationships and details
for root, dirs, files in os.walk(repo_path):
for file in files:
if language == "java" and file.endswith(".java"):
file_path = os.path.join(root, file)
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Find class declarations
class_decl = re.findall(r'class\s+(\w+)(?:\s+extends\s+(\w+))?(?:\s+implements\s+([\w, ]+))?', content)
for match in class_decl:
class_name = match[0]
parent = match[1] if match[1] else None
implements = [i.strip() for i in match[2].split(',')] if match[2] else []
if class_name not in relationships["classes"]:
relationships["classes"][class_name] = {"parents": [], "associations": [], "attributes": [], "methods": []}
if parent:
relationships["classes"][class_name]["parents"].append(parent)
for impl in implements:
relationships["classes"][class_name]["parents"].append(impl)
# Find attributes (fields)
attr_matches = re.findall(r'(public|private|protected)?\s*([\w<>\[\]]+)\s+(\w+)\s*;', content)
for _, attr_type, attr_name in attr_matches:
relationships["classes"][class_name]["attributes"].append(f"{attr_type} {attr_name}")
if attr_type in class_names and attr_type != class_name:
relationships["classes"][class_name]["associations"].append(attr_type)
# Find method signatures and parameter associations
method_matches = re.findall(r'(public|private|protected)?\s*([\w<>\[\]]+)\s+(\w+)\s*\(([^)]*)\)', content)
for _, ret_type, method_name, params in method_matches:
param_types = [p.strip().split()[0] for p in params.split(',') if p.strip()]
param_str = ', '.join([p.strip() for p in params.split(',') if p.strip()])
relationships["classes"][class_name]["methods"].append(f"{ret_type} {method_name}({param_str})")
for ptype in param_types:
if ptype in class_names and ptype != class_name:
relationships["classes"][class_name]["associations"].append(ptype)
except Exception:
continue
# Remove duplicate associations and attributes
for cls in relationships["classes"]:
relationships["classes"][cls]["associations"] = list(set(relationships["classes"][cls]["associations"]))
relationships["classes"][cls]["attributes"] = list(set(relationships["classes"][cls]["attributes"]))
return relationships

def generate_mermaid_class_diagram(self, relationships: dict) -> str:
"""
Generate a detailed Mermaid class diagram from relationships dict.
"""
lines = ["classDiagram"]
for cls, info in relationships.get('classes', {}).items():
for parent in info.get('parents', []):
lines.append(f"{parent} <|-- {cls}")
for assoc in info.get('associations', []):
lines.append(f"{cls} --> {assoc}")
# Add class block with attributes and methods
lines.append(f"class {cls} {{")
for attr in info.get('attributes', []):
lines.append(f" {attr}")
for method in info.get('methods', []):
lines.append(f" {method}")
lines.append("}")
return "\n".join(lines)
def __init__(self):
self.supported_extensions = {
'.py': 'python',
Expand All @@ -22,6 +104,56 @@ def __init__(self):
'.yml': 'yaml',
'.yaml': 'yaml'
}
def extract_class_relationships(self, repo_path: str, language: str = "java") -> dict:
"""
Extract class relationships (inheritance, associations) for visualization.
Returns: { 'classes': {name: {'parents': [...], 'associations': [...]}} }
"""
import re
relationships = {"classes": {}}
for root, dirs, files in os.walk(repo_path):
for file in files:
if language == "java" and file.endswith(".java"):
file_path = os.path.join(root, file)
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Find class declarations
class_matches = re.findall(r'class\s+(\w+)(?:\s+extends\s+(\w+))?(?:\s+implements\s+([\w, ]+))?', content)
for match in class_matches:
class_name = match[0]
parent = match[1] if match[1] else None
implements = [i.strip() for i in match[2].split(',')] if match[2] else []
if class_name not in relationships["classes"]:
relationships["classes"][class_name] = {"parents": [], "associations": []}
if parent:
relationships["classes"][class_name]["parents"].append(parent)
for impl in implements:
relationships["classes"][class_name]["parents"].append(impl)
# Find associations (fields of other class types)
field_matches = re.findall(r'(\w+)\s+(\w+);', content)
for type_name, var_name in field_matches:
# Only associate with known classes
if type_name in relationships["classes"] and type_name != class_name:
relationships["classes"][class_name]["associations"].append(type_name)
except Exception:
continue
# Add support for other languages here if needed
return relationships

def generate_mermaid_class_diagram(self, relationships: dict) -> str:
"""
Generate a Mermaid class diagram from relationships dict.
"""
lines = ["```mermaid", "classDiagram"]
for cls, info in relationships.get('classes', {}).items():
for parent in info.get('parents', []):
lines.append(f"{parent} <|-- {cls}")
for assoc in info.get('associations', []):
lines.append(f"{cls} --> {assoc}")
lines.append(f"class {cls}")
lines.append("```")
return "\n".join(lines)

def scan_repository(self, repo_path: str) -> Dict[str, Any]:
"""Scan entire repository and return structure analysis"""
Expand Down