use std::fs;
use std::path::Path;
use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
const COMMIT_TWIDDLE_TEMPLATE: &str = include_str!("../templates/commit-twiddle.md");
const PR_CREATE_TEMPLATE: &str = include_str!("../templates/pr-create.md");
const PR_UPDATE_TEMPLATE: &str = include_str!("../templates/pr-update.md");
#[derive(Parser)]
pub struct CommandsCommand {
#[command(subcommand)]
pub command: CommandsSubcommands,
}
#[derive(Subcommand)]
pub enum CommandsSubcommands {
Generate(GenerateCommand),
}
#[derive(Parser)]
pub struct GenerateCommand {
#[command(subcommand)]
pub command: GenerateSubcommands,
}
#[derive(Subcommand)]
pub enum GenerateSubcommands {
#[command(name = "commit-twiddle")]
CommitTwiddle,
#[command(name = "pr-create")]
PrCreate,
#[command(name = "pr-update")]
PrUpdate,
All,
}
impl CommandsCommand {
pub fn execute(self) -> Result<()> {
match self.command {
CommandsSubcommands::Generate(generate_cmd) => generate_cmd.execute(),
}
}
}
impl GenerateCommand {
pub fn execute(self) -> Result<()> {
match self.command {
GenerateSubcommands::CommitTwiddle => {
generate_commit_twiddle()?;
println!("✅ Generated .claude/commands/commit-twiddle.md");
}
GenerateSubcommands::PrCreate => {
generate_pr_create()?;
println!("✅ Generated .claude/commands/pr-create.md");
}
GenerateSubcommands::PrUpdate => {
generate_pr_update()?;
println!("✅ Generated .claude/commands/pr-update.md");
}
GenerateSubcommands::All => {
generate_commit_twiddle()?;
generate_pr_create()?;
generate_pr_update()?;
println!("✅ Generated all command templates:");
println!(" - .claude/commands/commit-twiddle.md");
println!(" - .claude/commands/pr-create.md");
println!(" - .claude/commands/pr-update.md");
}
}
Ok(())
}
}
fn generate_commit_twiddle() -> Result<()> {
ensure_claude_commands_dir()?;
fs::write(
".claude/commands/commit-twiddle.md",
COMMIT_TWIDDLE_TEMPLATE,
)
.context("Failed to write .claude/commands/commit-twiddle.md")?;
Ok(())
}
fn generate_pr_create() -> Result<()> {
ensure_claude_commands_dir()?;
fs::write(".claude/commands/pr-create.md", PR_CREATE_TEMPLATE)
.context("Failed to write .claude/commands/pr-create.md")?;
Ok(())
}
fn generate_pr_update() -> Result<()> {
ensure_claude_commands_dir()?;
fs::write(".claude/commands/pr-update.md", PR_UPDATE_TEMPLATE)
.context("Failed to write .claude/commands/pr-update.md")?;
Ok(())
}
fn ensure_claude_commands_dir() -> Result<()> {
let commands_dir = Path::new(".claude/commands");
if !commands_dir.exists() {
fs::create_dir_all(commands_dir).context("Failed to create .claude/commands directory")?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn commit_twiddle_template_has_content() {
assert!(COMMIT_TWIDDLE_TEMPLATE.len() > 10);
}
#[test]
fn pr_create_template_has_content() {
assert!(PR_CREATE_TEMPLATE.len() > 10);
}
#[test]
fn pr_update_template_has_content() {
assert!(PR_UPDATE_TEMPLATE.len() > 10);
}
#[test]
fn templates_contain_expected_content() {
assert!(
COMMIT_TWIDDLE_TEMPLATE.contains("commit")
|| COMMIT_TWIDDLE_TEMPLATE.contains("twiddle")
);
assert!(
PR_CREATE_TEMPLATE.contains("pull request")
|| PR_CREATE_TEMPLATE.contains("PR")
|| PR_CREATE_TEMPLATE.contains("pr")
);
assert!(
PR_UPDATE_TEMPLATE.contains("update")
|| PR_UPDATE_TEMPLATE.contains("PR")
|| PR_UPDATE_TEMPLATE.contains("pr")
);
}
#[test]
fn templates_are_valid_markdown() {
assert!(COMMIT_TWIDDLE_TEMPLATE.is_ascii() || COMMIT_TWIDDLE_TEMPLATE.contains('#'));
assert!(PR_CREATE_TEMPLATE.is_ascii() || PR_CREATE_TEMPLATE.contains('#'));
assert!(PR_UPDATE_TEMPLATE.is_ascii() || PR_UPDATE_TEMPLATE.contains('#'));
}
#[test]
fn templates_are_distinct() {
assert_ne!(COMMIT_TWIDDLE_TEMPLATE, PR_CREATE_TEMPLATE);
assert_ne!(COMMIT_TWIDDLE_TEMPLATE, PR_UPDATE_TEMPLATE);
assert_ne!(PR_CREATE_TEMPLATE, PR_UPDATE_TEMPLATE);
}
}