diff --git a/codesnap/output/markdown_exporter.py b/codesnap/output/markdown_exporter.py new file mode 100644 index 0000000..582df30 --- /dev/null +++ b/codesnap/output/markdown_exporter.py @@ -0,0 +1,124 @@ +"""Markdown export module for CodeSnap.""" + +from pathlib import Path +from typing import Any, Optional + +from ..core.extractor import ExtractedCode + + +class MarkdownExporter: + """Exports code summaries in Markdown format.""" + + def export( + self, + extracted_files: list[ExtractedCode], + file_paths: list[Path], + dependency_data: dict[str, Any], + complexity_data: dict[str, str], + output_path: Optional[Path] = None, + ) -> str: + """Export code summary to Markdown.""" + lines: list[str] = [] + + lines.append("# CodeSnap Analysis Report\n") + lines.append(f"**Analyzed Directory:** {file_paths[0].parent if file_paths else 'N/A'}\n") + lines.append(f"**Files Analyzed:** {len(extracted_files)}\n") + lines.append(f"**Date:** {self._get_timestamp()}\n") + + lines.append("\n## Language Breakdown\n") + language_counts: dict[str, int] = {} + for extracted in extracted_files: + language_counts[extracted.language] = language_counts.get(extracted.language, 0) + 1 + + for lang, count in sorted(language_counts.items(), key=lambda x: x[1], reverse=True): + lines.append(f"- **{lang}:** {count} files\n") + + lines.append("\n## File Structure\n") + lines.append("```\n") + lines.append(self._generate_tree(file_paths)) + lines.append("```\n") + + lines.append("\n## Key Functions and Classes\n") + for extracted in extracted_files: + if extracted.functions or extracted.classes: + lines.append(f"\n### {extracted.file_path.name}\n") + + if extracted.classes: + lines.append("**Classes:**\n") + for cls in extracted.classes: + lines.append(f"- `{cls.name}`") + if cls.base_classes: + lines.append(f" (extends: {', '.join(cls.base_classes)})") + lines.append("\n") + if cls.methods: + lines.append(" **Methods:**\n") + for method in cls.methods: + params = ", ".join(method.parameters) if method.parameters else "" + lines.append(f" - `{method.name}({params})`\n") + + if extracted.functions: + lines.append("**Functions:**\n") + for func in extracted.functions: + params = ", ".join(func.parameters) if func.parameters else "" + lines.append(f"- `{func.name}({params})`\n") + + lines.append("\n## Dependencies\n") + dependencies = dependency_data.get("dependencies", []) + if dependencies: + lines.append("### Import Relationships\n") + for dep in dependencies[:20]: + lines.append(f"- `{dep.source.name}` → `{dep.target.name}`\n") + + if len(dependencies) > 20: + lines.append(f"\n... and {len(dependencies) - 20} more dependencies\n") + else: + lines.append("No dependencies detected.\n") + + cycles = dependency_data.get("cycles", []) + if cycles: + lines.append("\n### Circular Dependencies\n") + for i, cycle in enumerate(cycles, 1): + cycle_names = " → ".join(p.name for p in cycle) + lines.append(f"**Cycle {i}:** {cycle_names}\n") + + orphaned = dependency_data.get("orphaned", []) + if orphaned: + lines.append("\n### Orphaned Files (no dependencies)\n") + for f in orphaned: + lines.append(f"- {f.name}\n") + + lines.append("\n## Complexity Metrics\n") + lines.append("| File | Complexity |\n") + lines.append("|------|------------|\n") + for extracted in extracted_files: + complexity = complexity_data.get(str(extracted.file_path), "unknown") + lines.append(f"| {extracted.file_path.name} | {complexity} |\n") + + report = "\n".join(lines) + + if output_path: + output_path.write_text(report, encoding="utf-8") + + return report + + def _generate_tree(self, file_paths: list[Path]) -> str: + """Generate a tree representation of the file structure.""" + if not file_paths: + return "" + + root = file_paths[0].parent + lines: list[str] = [] + + for path in sorted(file_paths): + relative = path.relative_to(root) + parts = list(relative.parts) + indent = " " * (len(parts) - 1) + prefix = "└── " if parts[-1] == relative.parts[-1] else "├── " + lines.append(f"{indent}{prefix}{parts[-1]}\n") + + return "".join(lines) + + def _get_timestamp(self) -> str: + """Get current timestamp.""" + from datetime import datetime + return datetime.utcnow().strftime("%Y-%m-%d %H:%M UTC")