diff --git a/backend/main.py b/backend/main.py index cd4df8d..3c8080c 100644 --- a/backend/main.py +++ b/backend/main.py @@ -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("/") @@ -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: @@ -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 @@ -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"]) @@ -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}") @@ -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""" @@ -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 = {} diff --git a/backend/services/document_converter.py b/backend/services/document_converter.py index 80f2b1e..9df6774 100644 --- a/backend/services/document_converter.py +++ b/backend/services/document_converter.py @@ -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. diff --git a/backend/services/repo_scanner.py b/backend/services/repo_scanner.py index 6d4cfff..3eda81d 100644 --- a/backend/services/repo_scanner.py +++ b/backend/services/repo_scanner.py @@ -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', @@ -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"""