305 lines
11 KiB
Python
305 lines
11 KiB
Python
"""
|
|
Changelog generation CLI.
|
|
"""
|
|
import os
|
|
import click
|
|
from rich.console import Console
|
|
from rich.prompt import Prompt, Confirm
|
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
from .generator import ChangelogGenerator
|
|
from ..shared.config import Config
|
|
|
|
console = Console()
|
|
|
|
@click.group()
|
|
def cli():
|
|
"""Generate changelogs."""
|
|
pass
|
|
|
|
@cli.command()
|
|
@click.option("--version", "-v", required=True, help="Version number for the changelog entry")
|
|
@click.option("--from-tag", "-t", help="Generate changelog from this tag")
|
|
@click.option("--days", "-d", type=int, help="Generate changelog from the last N days")
|
|
@click.option("--commits", "-n", type=int, help="Generate changelog from the last N commits")
|
|
@click.option("--output", "-o", default="CHANGELOG.md", help="Output file path")
|
|
@click.option("--temperature", type=float, default=0.7, help="AI temperature (0.0-1.0)")
|
|
def generate(version: str, from_tag: str, days: int, commits: int, output: str, temperature: float):
|
|
"""Generate a changelog from git history."""
|
|
try:
|
|
# Get configuration
|
|
config = Config()
|
|
|
|
# Validate version format
|
|
if not version.startswith("v"):
|
|
version = f"v{version}"
|
|
|
|
# Get git history
|
|
if from_tag:
|
|
# Get commits since tag
|
|
cmd = f"git log {from_tag}..HEAD --pretty=format:%s"
|
|
elif days:
|
|
# Get commits from last N days
|
|
cmd = f"git log --since='{days} days ago' --pretty=format:%s"
|
|
elif commits:
|
|
# Get last N commits
|
|
cmd = f"git log -n {commits} --pretty=format:%s"
|
|
else:
|
|
# Get all commits
|
|
cmd = "git log --pretty=format:%s"
|
|
|
|
# Execute git command
|
|
result = os.popen(cmd).read().strip()
|
|
if not result:
|
|
console.print("[yellow]No commits found in the specified range")
|
|
return
|
|
|
|
# Split into individual commits
|
|
commit_messages = [msg.strip() for msg in result.split("\n") if msg.strip()]
|
|
|
|
# Group commits by type
|
|
groups = {
|
|
"✨ Added": [],
|
|
"🔄 Changed": [],
|
|
"🐛 Fixed": [],
|
|
"🚀 Performance": [],
|
|
"📝 Documentation": [],
|
|
"🔧 Maintenance": [],
|
|
"🗑️ Removed": [],
|
|
"🔒 Security": []
|
|
}
|
|
|
|
# Map commit types to groups
|
|
type_map = {
|
|
"feat": "✨ Added",
|
|
"add": "✨ Added",
|
|
"new": "✨ Added",
|
|
"change": "🔄 Changed",
|
|
"update": "🔄 Changed",
|
|
"fix": "🐛 Fixed",
|
|
"bug": "🐛 Fixed",
|
|
"perf": "🚀 Performance",
|
|
"docs": "📝 Documentation",
|
|
"chore": "🔧 Maintenance",
|
|
"refactor": "🔧 Maintenance",
|
|
"remove": "🗑️ Removed",
|
|
"delete": "🗑️ Removed",
|
|
"security": "🔒 Security"
|
|
}
|
|
|
|
# Categorize commits
|
|
for msg in commit_messages:
|
|
msg_lower = msg.lower()
|
|
categorized = False
|
|
|
|
# Check for type prefixes
|
|
for type_key, group in type_map.items():
|
|
if msg_lower.startswith(type_key):
|
|
groups[group].append(msg)
|
|
categorized = True
|
|
break
|
|
|
|
# If not categorized, check for keywords
|
|
if not categorized:
|
|
for type_key, group in type_map.items():
|
|
if type_key in msg_lower:
|
|
groups[group].append(msg)
|
|
break
|
|
else:
|
|
# Default to maintenance if no match
|
|
groups["🔧 Maintenance"].append(msg)
|
|
|
|
# Generate changelog
|
|
changelog = f"# Changelog\n\n## {version}\n\n"
|
|
|
|
# Add each group
|
|
for group, messages in groups.items():
|
|
if messages:
|
|
changelog += f"### {group}\n\n"
|
|
for msg in messages:
|
|
changelog += f"- {msg}\n"
|
|
changelog += "\n"
|
|
|
|
# Write to file
|
|
with open(output, "w") as f:
|
|
f.write(changelog)
|
|
|
|
console.print(f"[green]✓[/green] Changelog generated successfully at: {output}")
|
|
|
|
except Exception as e:
|
|
console.print(f"[red]Error:[/red] {str(e)}")
|
|
|
|
@cli.command()
|
|
@click.option("--version", "-v", required=True, help="Version number for the changelog entry")
|
|
@click.option("--output", "-o", default="CHANGELOG.md", help="Output file path")
|
|
@click.option("--temperature", type=float, default=0.7, help="AI temperature (0.0-1.0)")
|
|
def generate_interactive(version: str, output: str, temperature: float):
|
|
"""Generate a changelog interactively."""
|
|
try:
|
|
# Get configuration
|
|
config = Config()
|
|
|
|
# Validate version format
|
|
if not version.startswith("v"):
|
|
version = f"v{version}"
|
|
|
|
# Initialize groups
|
|
groups = {
|
|
"✨ Added": [],
|
|
"🔄 Changed": [],
|
|
"🐛 Fixed": [],
|
|
"🚀 Performance": [],
|
|
"📝 Documentation": [],
|
|
"🔧 Maintenance": [],
|
|
"🗑️ Removed": [],
|
|
"🔒 Security": []
|
|
}
|
|
|
|
console.print("\n[bold cyan]Enter your changes (press Enter twice to finish):[/bold cyan]")
|
|
console.print("[dim]Start each line with an emoji to categorize it:[/dim]")
|
|
console.print("✨ - Added")
|
|
console.print("🔄 - Changed")
|
|
console.print("🐛 - Fixed")
|
|
console.print("🚀 - Performance")
|
|
console.print("📝 - Documentation")
|
|
console.print("🔧 - Maintenance")
|
|
console.print("🗑️ - Removed")
|
|
console.print("🔒 - Security")
|
|
console.print("\n[dim]Example: ✨ Added new feature X[/dim]\n")
|
|
|
|
while True:
|
|
line = click.prompt("", default="", show_default=False)
|
|
if not line:
|
|
break
|
|
|
|
# Categorize based on emoji
|
|
for group in groups:
|
|
if line.startswith(group.split()[0]):
|
|
groups[group].append(line)
|
|
break
|
|
else:
|
|
# Default to maintenance if no emoji
|
|
groups["🔧 Maintenance"].append(line)
|
|
|
|
# Generate changelog
|
|
changelog = f"# Changelog\n\n## {version}\n\n"
|
|
|
|
# Add each group
|
|
for group, messages in groups.items():
|
|
if messages:
|
|
changelog += f"### {group}\n\n"
|
|
for msg in messages:
|
|
changelog += f"- {msg}\n"
|
|
changelog += "\n"
|
|
|
|
# Write to file
|
|
with open(output, "w") as f:
|
|
f.write(changelog)
|
|
|
|
console.print(f"[green]✓[/green] Changelog generated successfully at: {output}")
|
|
|
|
except Exception as e:
|
|
console.print(f"[red]Error:[/red] {str(e)}")
|
|
|
|
@cli.command()
|
|
@click.option("--version", "-v", required=True, help="Version number for the changelog entry")
|
|
@click.option("--input", "-i", required=True, help="Input file with changes")
|
|
@click.option("--output", "-o", default="CHANGELOG.md", help="Output file path")
|
|
@click.option("--temperature", type=float, default=0.7, help="AI temperature (0.0-1.0)")
|
|
def generate_from_file(version: str, input: str, output: str, temperature: float):
|
|
"""Generate a changelog from a file."""
|
|
try:
|
|
# Get configuration
|
|
config = Config()
|
|
|
|
# Validate version format
|
|
if not version.startswith("v"):
|
|
version = f"v{version}"
|
|
|
|
# Read input file
|
|
with open(input, "r") as f:
|
|
changes = f.read().strip()
|
|
|
|
if not changes:
|
|
console.print("[yellow]No changes found in input file")
|
|
return
|
|
|
|
# Split into lines
|
|
lines = [line.strip() for line in changes.split("\n") if line.strip()]
|
|
|
|
# Initialize groups
|
|
groups = {
|
|
"✨ Added": [],
|
|
"🔄 Changed": [],
|
|
"🐛 Fixed": [],
|
|
"🚀 Performance": [],
|
|
"📝 Documentation": [],
|
|
"🔧 Maintenance": [],
|
|
"🗑️ Removed": [],
|
|
"🔒 Security": []
|
|
}
|
|
|
|
# Categorize changes
|
|
for line in lines:
|
|
line_lower = line.lower()
|
|
categorized = False
|
|
|
|
# Check for emoji prefixes
|
|
for group in groups:
|
|
if line.startswith(group.split()[0]):
|
|
groups[group].append(line)
|
|
categorized = True
|
|
break
|
|
|
|
# If not categorized, check for keywords
|
|
if not categorized:
|
|
type_map = {
|
|
"add": "✨ Added",
|
|
"new": "✨ Added",
|
|
"change": "🔄 Changed",
|
|
"update": "🔄 Changed",
|
|
"fix": "🐛 Fixed",
|
|
"bug": "🐛 Fixed",
|
|
"perf": "🚀 Performance",
|
|
"docs": "📝 Documentation",
|
|
"chore": "🔧 Maintenance",
|
|
"refactor": "🔧 Maintenance",
|
|
"remove": "🗑️ Removed",
|
|
"delete": "🗑️ Removed",
|
|
"security": "🔒 Security"
|
|
}
|
|
|
|
for type_key, group in type_map.items():
|
|
if type_key in line_lower:
|
|
groups[group].append(line)
|
|
break
|
|
else:
|
|
# Default to maintenance if no match
|
|
groups["🔧 Maintenance"].append(line)
|
|
|
|
# Generate changelog
|
|
changelog = f"# Changelog\n\n## {version}\n\n"
|
|
|
|
# Add each group
|
|
for group, messages in groups.items():
|
|
if messages:
|
|
changelog += f"### {group}\n\n"
|
|
for msg in messages:
|
|
changelog += f"- {msg}\n"
|
|
changelog += "\n"
|
|
|
|
# Write to file
|
|
with open(output, "w") as f:
|
|
f.write(changelog)
|
|
|
|
console.print(f"[green]✓[/green] Changelog generated successfully at: {output}")
|
|
|
|
except Exception as e:
|
|
console.print(f"[red]Error:[/red] {str(e)}")
|
|
|
|
# Add command aliases
|
|
cli.add_command(generate, name="g")
|
|
cli.add_command(generate_interactive, name="i")
|
|
cli.add_command(generate_from_file, name="f")
|
|
|
|
if __name__ == "__main__":
|
|
cli() |