295 Zeilen
10 KiB
Python
295 Zeilen
10 KiB
Python
"""
|
|
README Generator Module
|
|
Automatically generates and updates README files for projects
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
from datetime import datetime
|
|
from typing import Dict, List, Set
|
|
import mimetypes
|
|
|
|
class ReadmeGenerator:
|
|
def __init__(self):
|
|
self.language_extensions = {
|
|
'.py': 'Python',
|
|
'.js': 'JavaScript',
|
|
'.ts': 'TypeScript',
|
|
'.jsx': 'React',
|
|
'.tsx': 'React TypeScript',
|
|
'.java': 'Java',
|
|
'.cpp': 'C++',
|
|
'.c': 'C',
|
|
'.cs': 'C#',
|
|
'.php': 'PHP',
|
|
'.rb': 'Ruby',
|
|
'.go': 'Go',
|
|
'.rs': 'Rust',
|
|
'.swift': 'Swift',
|
|
'.kt': 'Kotlin',
|
|
'.dart': 'Dart',
|
|
'.r': 'R',
|
|
'.scala': 'Scala',
|
|
'.lua': 'Lua',
|
|
'.pl': 'Perl',
|
|
'.sh': 'Shell',
|
|
'.bat': 'Batch',
|
|
'.ps1': 'PowerShell',
|
|
}
|
|
|
|
self.framework_indicators = {
|
|
'React': ['package.json', 'react'],
|
|
'Angular': ['angular.json', '@angular'],
|
|
'Vue': ['vue.config.js', 'vue'],
|
|
'Django': ['manage.py', 'django'],
|
|
'Flask': ['app.py', 'flask'],
|
|
'Express': ['express', 'node_modules'],
|
|
'Spring': ['pom.xml', 'spring'],
|
|
'.NET': ['.csproj', 'dotnet'],
|
|
'Laravel': ['artisan', 'laravel'],
|
|
'Rails': ['Gemfile', 'rails'],
|
|
}
|
|
|
|
def analyze_project(self, project_path: str) -> Dict:
|
|
"""Analyze project structure and content"""
|
|
analysis = {
|
|
'path': project_path,
|
|
'name': os.path.basename(project_path),
|
|
'languages': set(),
|
|
'frameworks': set(),
|
|
'file_count': 0,
|
|
'total_size': 0,
|
|
'structure': {},
|
|
'key_files': [],
|
|
'last_modified': None,
|
|
}
|
|
|
|
# Walk through directory
|
|
for root, dirs, files in os.walk(project_path):
|
|
# Skip hidden and system directories
|
|
dirs[:] = [d for d in dirs if not d.startswith('.') and d not in ['node_modules', '__pycache__', 'venv', 'env']]
|
|
|
|
rel_path = os.path.relpath(root, project_path)
|
|
level = rel_path.count(os.sep)
|
|
|
|
# Limit depth for structure
|
|
if level < 3:
|
|
current_dict = analysis['structure']
|
|
if rel_path != '.':
|
|
for part in rel_path.split(os.sep):
|
|
if part not in current_dict:
|
|
current_dict[part] = {}
|
|
current_dict = current_dict[part]
|
|
|
|
# Add files to structure
|
|
for file in files[:10]: # Limit files shown
|
|
if not file.startswith('.'):
|
|
current_dict[file] = 'file'
|
|
|
|
# Analyze files
|
|
for file in files:
|
|
if file.startswith('.'):
|
|
continue
|
|
|
|
file_path = os.path.join(root, file)
|
|
analysis['file_count'] += 1
|
|
|
|
try:
|
|
# Get file size
|
|
size = os.path.getsize(file_path)
|
|
analysis['total_size'] += size
|
|
|
|
# Get modification time
|
|
mtime = os.path.getmtime(file_path)
|
|
if not analysis['last_modified'] or mtime > analysis['last_modified']:
|
|
analysis['last_modified'] = mtime
|
|
|
|
# Detect language
|
|
ext = os.path.splitext(file)[1].lower()
|
|
if ext in self.language_extensions:
|
|
analysis['languages'].add(self.language_extensions[ext])
|
|
|
|
# Key files
|
|
if file in ['README.md', 'package.json', 'requirements.txt', 'Dockerfile',
|
|
'.gitignore', 'Makefile', 'setup.py', 'pom.xml']:
|
|
analysis['key_files'].append(file)
|
|
|
|
except:
|
|
pass
|
|
|
|
# Detect frameworks
|
|
for framework, indicators in self.framework_indicators.items():
|
|
for indicator in indicators:
|
|
if any(indicator in str(f) for f in analysis['key_files']):
|
|
analysis['frameworks'].add(framework)
|
|
|
|
# Convert sets to lists for JSON serialization
|
|
analysis['languages'] = list(analysis['languages'])
|
|
analysis['frameworks'] = list(analysis['frameworks'])
|
|
|
|
return analysis
|
|
|
|
def generate_readme(self, project_path: str, project_name: str = None) -> str:
|
|
"""Generate README content for a project"""
|
|
analysis = self.analyze_project(project_path)
|
|
|
|
if project_name:
|
|
analysis['name'] = project_name
|
|
|
|
# Format file size
|
|
size_mb = analysis['total_size'] / (1024 * 1024)
|
|
size_str = f"{size_mb:.1f} MB" if size_mb > 1 else f"{analysis['total_size'] / 1024:.1f} KB"
|
|
|
|
# Format last modified
|
|
if analysis['last_modified']:
|
|
last_mod = datetime.fromtimestamp(analysis['last_modified']).strftime('%Y-%m-%d %H:%M')
|
|
else:
|
|
last_mod = "Unknown"
|
|
|
|
# Build README content
|
|
content = f"""# {analysis['name']}
|
|
|
|
*This README was automatically generated by Claude Project Manager*
|
|
|
|
## Project Overview
|
|
|
|
- **Path**: `{analysis['path']}`
|
|
- **Files**: {analysis['file_count']} files
|
|
- **Size**: {size_str}
|
|
- **Last Modified**: {last_mod}
|
|
|
|
## Technology Stack
|
|
|
|
"""
|
|
|
|
if analysis['languages']:
|
|
content += "### Languages\n"
|
|
for lang in sorted(analysis['languages']):
|
|
content += f"- {lang}\n"
|
|
content += "\n"
|
|
|
|
if analysis['frameworks']:
|
|
content += "### Frameworks & Libraries\n"
|
|
for framework in sorted(analysis['frameworks']):
|
|
content += f"- {framework}\n"
|
|
content += "\n"
|
|
|
|
# Add structure
|
|
content += "## Project Structure\n\n```\n"
|
|
content += self._format_structure(analysis['structure'])
|
|
content += "```\n\n"
|
|
|
|
# Add key files section
|
|
if analysis['key_files']:
|
|
content += "## Key Files\n\n"
|
|
for file in analysis['key_files']:
|
|
content += f"- `{file}`\n"
|
|
content += "\n"
|
|
|
|
# Add Claude usage section
|
|
content += """## Claude Integration
|
|
|
|
This project is managed with Claude Project Manager. To work with this project:
|
|
|
|
1. Open Claude Project Manager
|
|
2. Click on this project's tile
|
|
3. Claude will open in the project directory
|
|
|
|
## Notes
|
|
|
|
*Add your project-specific notes here*
|
|
|
|
---
|
|
|
|
## Development Log
|
|
|
|
"""
|
|
|
|
# Add timestamp
|
|
content += f"- README generated on {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
|
|
|
|
return content
|
|
|
|
def _format_structure(self, structure: Dict, prefix: str = "", is_last: bool = True) -> str:
|
|
"""Format directory structure for display"""
|
|
output = ""
|
|
items = list(structure.items())
|
|
|
|
for i, (name, value) in enumerate(items):
|
|
is_last_item = i == len(items) - 1
|
|
|
|
# Add tree characters
|
|
if prefix:
|
|
output += prefix
|
|
output += "└── " if is_last_item else "├── "
|
|
|
|
output += name
|
|
|
|
if isinstance(value, dict) and value: # It's a directory with contents
|
|
output += "/\n"
|
|
extension = " " if is_last_item else "│ "
|
|
output += self._format_structure(value, prefix + extension, is_last_item)
|
|
else:
|
|
output += "\n"
|
|
|
|
return output
|
|
|
|
def update_readme(self, readme_path: str, new_content: str):
|
|
"""Update README file, preserving user notes"""
|
|
if os.path.exists(readme_path):
|
|
try:
|
|
with open(readme_path, 'r', encoding='utf-8') as f:
|
|
old_content = f.read()
|
|
|
|
# Try to preserve user notes section
|
|
notes_marker = "## Notes"
|
|
if notes_marker in old_content:
|
|
notes_start = old_content.find(notes_marker)
|
|
notes_end = old_content.find("\n---", notes_start)
|
|
if notes_end > notes_start:
|
|
user_notes = old_content[notes_start:notes_end]
|
|
# Replace empty notes section with user's notes
|
|
new_content = new_content.replace(
|
|
"## Notes\n\n*Add your project-specific notes here*\n",
|
|
user_notes
|
|
)
|
|
|
|
# Append to development log
|
|
if "## Development Log" in old_content:
|
|
log_start = old_content.find("## Development Log")
|
|
old_log = old_content[log_start:]
|
|
# Remove the new log header and just append entries
|
|
new_log_entry = f"- README updated on {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
|
|
new_content = new_content[:new_content.find("## Development Log")] + old_log + new_log_entry
|
|
|
|
except:
|
|
pass
|
|
|
|
# Write the README
|
|
with open(readme_path, 'w', encoding='utf-8') as f:
|
|
f.write(new_content)
|
|
|
|
def generate_and_save_readme(self, project_path: str, project_name: str = None) -> str:
|
|
"""Generate and save README for a project"""
|
|
readme_path = os.path.join(project_path, "CLAUDE_PROJECT_README.md")
|
|
content = self.generate_readme(project_path, project_name)
|
|
self.update_readme(readme_path, content)
|
|
return readme_path
|
|
|
|
# Test the module
|
|
if __name__ == "__main__":
|
|
generator = ReadmeGenerator()
|
|
|
|
# Test with current directory
|
|
test_path = os.path.dirname(os.path.abspath(__file__))
|
|
analysis = generator.analyze_project(test_path)
|
|
|
|
print("Project Analysis:")
|
|
print(f"Languages: {analysis['languages']}")
|
|
print(f"Files: {analysis['file_count']}")
|
|
print(f"Key files: {analysis['key_files']}")
|
|
|
|
# Generate README
|
|
readme = generator.generate_readme(test_path, "Claude Project Manager")
|
|
print("\nGenerated README Preview:")
|
|
print(readme[:500] + "...") |