Initial commit
Dieser Commit ist enthalten in:
295
readme_generator.py
Normale Datei
295
readme_generator.py
Normale Datei
@ -0,0 +1,295 @@
|
||||
"""
|
||||
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] + "...")
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren