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