Add utils, templates, config, interactive, and github modules
This commit is contained in:
313
src/auto_readme/templates/__init__.py
Normal file
313
src/auto_readme/templates/__init__.py
Normal file
@@ -0,0 +1,313 @@
|
||||
"""Template system for README generation."""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from jinja2 import Environment, FileSystemLoader, BaseLoader
|
||||
|
||||
from ..models import Project
|
||||
|
||||
|
||||
class TemplateRenderer:
|
||||
"""Renders README templates with project context."""
|
||||
|
||||
BUILTIN_TEMPLATES = {
|
||||
"base": """# {{ title }}
|
||||
|
||||
{% if description %}
|
||||
{{ description }}
|
||||
{% endif %}
|
||||
|
||||
{% if badges %}
|
||||
{{ badges }}
|
||||
{% endif %}
|
||||
|
||||
{% if table_of_contents %}
|
||||
## Table of Contents
|
||||
|
||||
- [Overview](#overview)
|
||||
{% if installation_steps %}- [Installation](#installation){% endif %}
|
||||
{% if usage_examples %}- [Usage](#usage){% endif %}
|
||||
{% if features %}- [Features](#features){% endif %}
|
||||
{% if api_functions %}- [API Reference](#api-reference){% endif %}
|
||||
{% if contributing_guidelines %}- [Contributing](#contributing){% endif %}
|
||||
{% if license_info %}- [License](#license){% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
## Overview
|
||||
|
||||
{{ project_overview }}
|
||||
|
||||
{% if tech_stack %}
|
||||
## Supported Languages
|
||||
|
||||
This project uses:
|
||||
{% for tech in tech_stack %}
|
||||
- **{{ tech }}**
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if installation_steps %}
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
{{ installation_steps|join('\\n') }}
|
||||
```
|
||||
|
||||
{% if dependencies %}
|
||||
### Dependencies
|
||||
|
||||
{% for dep in dependencies %}
|
||||
- `{{ dep.name }}`{% if dep.version %} v{{ dep.version }}{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if usage_examples %}
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```python
|
||||
{{ usage_examples[0] }}
|
||||
```
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% if features %}
|
||||
## Features
|
||||
|
||||
{% for feature in features %}
|
||||
- {{ feature }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if api_functions %}
|
||||
## API Reference
|
||||
|
||||
{% for func in api_functions %}
|
||||
### `{{ func.name }}`
|
||||
|
||||
{% if func.docstring %}
|
||||
{{ func.docstring }}
|
||||
{% endif %}
|
||||
|
||||
**Parameters:** `{{ func.parameters|join(', ') }}`
|
||||
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if contributing_guidelines %}
|
||||
## Contributing
|
||||
|
||||
{{ contributing_guidelines }}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% if license_info %}
|
||||
## License
|
||||
|
||||
{{ license_info }}
|
||||
{% endif %}
|
||||
|
||||
---
|
||||
|
||||
*Generated by Auto README Generator on {{ generated_at }}*
|
||||
""",
|
||||
}
|
||||
|
||||
def __init__(self, custom_template_dir: Optional[Path] = None):
|
||||
"""Initialize the template renderer."""
|
||||
self.env = Environment(
|
||||
loader=FileSystemLoader(str(custom_template_dir)) if custom_template_dir else BaseLoader(),
|
||||
trim_blocks=True,
|
||||
lstrip_blocks=True,
|
||||
)
|
||||
self.custom_template_dir = custom_template_dir
|
||||
|
||||
def render(
|
||||
self,
|
||||
project: Project,
|
||||
template_name: str = "base",
|
||||
**extra_context,
|
||||
) -> str:
|
||||
"""Render a README template with project context."""
|
||||
context = self._build_context(project, **extra_context)
|
||||
|
||||
if template_name in self.BUILTIN_TEMPLATES:
|
||||
template = self.env.from_string(self.BUILTIN_TEMPLATES[template_name])
|
||||
elif self.custom_template_dir:
|
||||
try:
|
||||
template = self.env.get_template(template_name)
|
||||
except Exception:
|
||||
template = self.env.from_string(self.BUILTIN_TEMPLATES["base"])
|
||||
else:
|
||||
template = self.env.from_string(self.BUILTIN_TEMPLATES["base"])
|
||||
|
||||
return template.render(**context)
|
||||
|
||||
def _build_context(self, project: Project, **extra_context) -> dict:
|
||||
"""Build the template context from project data."""
|
||||
context = {
|
||||
"title": project.config.name if project.config else project.root_path.name,
|
||||
"description": project.config.description if project.config else None,
|
||||
"project_overview": self._generate_overview(project),
|
||||
"badges": None,
|
||||
"tech_stack": self._get_tech_stack(project),
|
||||
"installation_steps": project.installation_steps or self._generate_installation_steps(project),
|
||||
"usage_examples": project.usage_examples or self._generate_usage_examples(project),
|
||||
"features": project.features or self._detect_features(project),
|
||||
"api_functions": project.all_functions()[:20],
|
||||
"contributing_guidelines": self._generate_contributing_guidelines(project),
|
||||
"license_info": project.config.license if project.config else None,
|
||||
"generated_at": project.generated_at.strftime("%Y-%m-%d"),
|
||||
"table_of_contents": True,
|
||||
"dependencies": project.dependencies,
|
||||
}
|
||||
|
||||
context.update(extra_context)
|
||||
return context
|
||||
|
||||
def _generate_overview(self, project: Project) -> str:
|
||||
"""Generate a project overview."""
|
||||
lines = [
|
||||
f"A project of type **{project.project_type.value}** located at `{project.root_path}`.",
|
||||
]
|
||||
lines.append(f"Contains {len(project.files)} files.")
|
||||
|
||||
if project.config and project.config.description:
|
||||
lines.insert(0, project.config.description)
|
||||
|
||||
return " ".join(lines)
|
||||
|
||||
def _get_tech_stack(self, project: Project) -> list[str]:
|
||||
"""Extract technology stack from dependencies."""
|
||||
tech = [project.project_type.value.title()]
|
||||
|
||||
dep_names = {dep.name.lower() for dep in project.dependencies}
|
||||
|
||||
framework_map = {
|
||||
"fastapi": "FastAPI",
|
||||
"flask": "Flask",
|
||||
"django": "Django",
|
||||
"click": "Click",
|
||||
"express": "Express.js",
|
||||
"react": "React",
|
||||
"vue": "Vue.js",
|
||||
"angular": "Angular",
|
||||
"gin": "Gin",
|
||||
"echo": "Echo",
|
||||
"actix-web": "Actix Web",
|
||||
"rocket": "Rocket",
|
||||
}
|
||||
|
||||
for dep, framework in framework_map.items():
|
||||
if dep in dep_names:
|
||||
tech.append(framework)
|
||||
|
||||
return list(dict.fromkeys(tech))
|
||||
|
||||
def _generate_installation_steps(self, project: Project) -> list[str]:
|
||||
"""Generate installation steps based on project type."""
|
||||
steps = []
|
||||
|
||||
if project.project_type.value == "python":
|
||||
steps = [
|
||||
"pip install -r requirements.txt",
|
||||
"pip install -e .",
|
||||
]
|
||||
elif project.project_type.value in ("javascript", "typescript"):
|
||||
steps = ["npm install", "npm run build"]
|
||||
elif project.project_type.value == "go":
|
||||
steps = ["go mod download", "go build"]
|
||||
elif project.project_type.value == "rust":
|
||||
steps = ["cargo build --release"]
|
||||
|
||||
if project.git_info and project.git_info.is_repo:
|
||||
steps.insert(0, f"git clone {project.git_info.remote_url or 'https://github.com/user/repo.git'}")
|
||||
|
||||
return steps
|
||||
|
||||
def _generate_usage_examples(self, project: Project) -> list[str]:
|
||||
"""Generate usage examples based on project type."""
|
||||
if project.project_type.value == "python":
|
||||
return ["from project_name import main", "main()"]
|
||||
elif project.project_type.value in ("javascript", "typescript"):
|
||||
return ["import { main } from 'project-name'", "main()"]
|
||||
elif project.project_type.value == "go":
|
||||
return ["go run main.go"]
|
||||
elif project.project_type.value == "rust":
|
||||
return ["cargo run --release"]
|
||||
return ["# Check the docs for usage information"]
|
||||
|
||||
def _detect_features(self, project: Project) -> list[str]:
|
||||
"""Detect project features from structure."""
|
||||
features = []
|
||||
|
||||
if any(f.file_type.name == "TEST" for f in project.files):
|
||||
features.append("Test suite included")
|
||||
|
||||
if project.git_info and project.git_info.is_repo:
|
||||
features.append("Git repository")
|
||||
|
||||
if project.dependencies:
|
||||
features.append(f"Uses {len(project.dependencies)} dependencies")
|
||||
|
||||
if project.all_classes():
|
||||
features.append(f"Contains {len(project.all_classes())} classes")
|
||||
|
||||
if project.all_functions():
|
||||
features.append(f"Contains {len(project.all_functions())} functions")
|
||||
|
||||
return features if features else ["Auto-generated documentation"]
|
||||
|
||||
def _generate_contributing_guidelines(self, project: Project) -> str:
|
||||
"""Generate contributing guidelines."""
|
||||
guidelines = [
|
||||
"1. Fork the repository",
|
||||
"2. Create a feature branch (`git checkout -b feature/amazing-feature`)",
|
||||
"3. Commit your changes (`git commit -m 'Add some amazing feature'`)",
|
||||
"4. Push to the branch (`git push origin feature/amazing-feature`)",
|
||||
"5. Open a Pull Request",
|
||||
]
|
||||
|
||||
if project.project_type.value == "python":
|
||||
guidelines.extend([
|
||||
"",
|
||||
"For Python development:",
|
||||
"- Run tests with `pytest`",
|
||||
"- Format code with `black` and `isort`",
|
||||
"- Check types with `mypy`",
|
||||
])
|
||||
elif project.project_type.value in ("javascript", "typescript"):
|
||||
guidelines.extend([
|
||||
"",
|
||||
"For JavaScript/TypeScript development:",
|
||||
"- Run tests with `npm test`",
|
||||
"- Format code with `npm run format`",
|
||||
"- Lint with `npm run lint`",
|
||||
])
|
||||
|
||||
return "\n".join(guidelines)
|
||||
|
||||
|
||||
class TemplateManager:
|
||||
"""Manages template operations."""
|
||||
|
||||
def __init__(self, template_dir: Optional[Path] = None):
|
||||
"""Initialize the template manager."""
|
||||
self.template_dir = template_dir
|
||||
self.renderer = TemplateRenderer(custom_template_dir=template_dir)
|
||||
|
||||
def list_templates(self) -> list[str]:
|
||||
"""List available templates."""
|
||||
return list(self.renderer.BUILTIN_TEMPLATES.keys())
|
||||
|
||||
def get_template_path(self, name: str) -> Optional[Path]:
|
||||
"""Get the path to a template."""
|
||||
if name in self.renderer.BUILTIN_TEMPLATES:
|
||||
return None
|
||||
if self.template_dir:
|
||||
return self.template_dir / name
|
||||
return None
|
||||
Reference in New Issue
Block a user