Initial upload with CI/CD workflow
This commit is contained in:
124
codesnap/output/markdown_exporter.py
Normal file
124
codesnap/output/markdown_exporter.py
Normal file
@@ -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")
|
||||
Reference in New Issue
Block a user