""" 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] + "...")