feat(hooks): add git hooks management CLI
This commit is contained in:
24 files changed
+1221
-531
No files matched your search
+73
-50
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Changelog generation CLI.
|
||||
"""
|
||||
|
||||
import os
|
||||
import click
|
||||
from rich.console import Console
|
||||
@@ -11,28 +12,41 @@ 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(
|
||||
"--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(
|
||||
"--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):
|
||||
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
|
||||
@@ -46,16 +60,16 @@ def generate(version: str, from_tag: str, days: int, commits: int, output: str,
|
||||
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": [],
|
||||
@@ -65,9 +79,9 @@ def generate(version: str, from_tag: str, days: int, commits: int, output: str,
|
||||
"📝 Documentation": [],
|
||||
"🔧 Maintenance": [],
|
||||
"🗑️ Removed": [],
|
||||
"🔒 Security": []
|
||||
"🔒 Security": [],
|
||||
}
|
||||
|
||||
|
||||
# Map commit types to groups
|
||||
type_map = {
|
||||
"feat": "✨ Added",
|
||||
@@ -83,21 +97,21 @@ def generate(version: str, from_tag: str, days: int, commits: int, output: str,
|
||||
"refactor": "🔧 Maintenance",
|
||||
"remove": "🗑️ Removed",
|
||||
"delete": "🗑️ Removed",
|
||||
"security": "🔒 Security"
|
||||
"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():
|
||||
@@ -107,10 +121,10 @@ def generate(version: str, from_tag: str, days: int, commits: int, output: str,
|
||||
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:
|
||||
@@ -118,18 +132,21 @@ def generate(version: str, from_tag: str, days: int, commits: int, output: str,
|
||||
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(
|
||||
"--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):
|
||||
@@ -137,11 +154,11 @@ def generate_interactive(version: str, output: str, temperature: float):
|
||||
try:
|
||||
# Get configuration
|
||||
config = Config()
|
||||
|
||||
|
||||
# Validate version format
|
||||
if not version.startswith("v"):
|
||||
version = f"v{version}"
|
||||
|
||||
|
||||
# Initialize groups
|
||||
groups = {
|
||||
"✨ Added": [],
|
||||
@@ -151,10 +168,12 @@ def generate_interactive(version: str, output: str, temperature: float):
|
||||
"📝 Documentation": [],
|
||||
"🔧 Maintenance": [],
|
||||
"🗑️ Removed": [],
|
||||
"🔒 Security": []
|
||||
"🔒 Security": [],
|
||||
}
|
||||
|
||||
console.print("\n[bold cyan]Enter your changes (press Enter twice to finish):[/bold cyan]")
|
||||
|
||||
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")
|
||||
@@ -165,12 +184,12 @@ def generate_interactive(version: str, output: str, temperature: float):
|
||||
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]):
|
||||
@@ -179,10 +198,10 @@ def generate_interactive(version: str, output: str, temperature: float):
|
||||
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:
|
||||
@@ -190,18 +209,21 @@ def generate_interactive(version: str, output: str, temperature: float):
|
||||
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(
|
||||
"--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)")
|
||||
@@ -210,22 +232,22 @@ def generate_from_file(version: str, input: str, output: str, temperature: float
|
||||
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": [],
|
||||
@@ -235,21 +257,21 @@ def generate_from_file(version: str, input: str, output: str, temperature: float
|
||||
"📝 Documentation": [],
|
||||
"🔧 Maintenance": [],
|
||||
"🗑️ Removed": [],
|
||||
"🔒 Security": []
|
||||
"🔒 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 = {
|
||||
@@ -265,9 +287,9 @@ def generate_from_file(version: str, input: str, output: str, temperature: float
|
||||
"refactor": "🔧 Maintenance",
|
||||
"remove": "🗑️ Removed",
|
||||
"delete": "🗑️ Removed",
|
||||
"security": "🔒 Security"
|
||||
"security": "🔒 Security",
|
||||
}
|
||||
|
||||
|
||||
for type_key, group in type_map.items():
|
||||
if type_key in line_lower:
|
||||
groups[group].append(line)
|
||||
@@ -275,10 +297,10 @@ def generate_from_file(version: str, input: str, output: str, temperature: float
|
||||
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:
|
||||
@@ -286,20 +308,21 @@ def generate_from_file(version: str, input: str, output: str, temperature: float
|
||||
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()
|
||||
cli()
|
||||
Reference in new issue
Block a user