use clap::{Parser, Subcommand};
use std::fs;
use std::path::Path;
use colored::*;
use rust_db_blueprint::blueprint::{Blueprint, BlueprintConfig};
#[derive(Parser)]
#[command(name = "blueprint")]
#[command(about = "Generate Laravel code from YAML draft files", long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Build {
#[arg(default_value = "draft.yaml")]
draft: String,
#[arg(long, value_delimiter = ',')]
only: Option<Vec<String>>,
#[arg(long, value_delimiter = ',')]
skip: Option<Vec<String>>,
#[arg(long, default_value = ".")]
output: String,
},
Init {
#[arg(long, short)]
force: bool,
},
List,
}
fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
match cli.command {
Commands::Build { draft, only, skip, output } => {
handle_build(&draft, only, skip, &output)?;
}
Commands::Init { force } => {
handle_init(force)?;
}
Commands::List => {
handle_list()?;
}
}
Ok(())
}
fn handle_build(draft: &str, only: Option<Vec<String>>, skip: Option<Vec<String>>, output: &str) -> anyhow::Result<()> {
let draft_path = Path::new(draft);
if !draft_path.exists() {
eprintln!("{}: Draft file '{}' not found.", "Error".red().bold(), draft);
eprintln!("Run 'blueprint init' to create a starter draft.yaml file.");
std::process::exit(1);
}
let content = fs::read_to_string(draft_path)?;
if content.trim().is_empty() {
eprintln!("{}: Draft file '{}' is empty.", "Error".red().bold(), draft);
std::process::exit(1);
}
let mut config = BlueprintConfig::default();
if let Some(ref only_list) = only {
config.only = only_list.clone();
}
if let Some(ref skip_list) = skip {
config.skip = skip_list.clone();
}
let blueprint = Blueprint::new(config);
println!("{} blueprint from: {}", "Building".green().bold(), draft);
println!();
let files = blueprint.build(&content)?;
if files.is_empty() {
println!("{}: No files were generated.", "Warning".yellow().bold());
println!(" Ensure your draft.yaml contains valid models and/or controllers.");
return Ok(());
}
let mut created = 0;
let mut updated = 0;
for (path, code) in &files {
let full_path = Path::new(output).join(path);
if let Some(parent) = full_path.parent() {
fs::create_dir_all(parent)?;
}
if full_path.exists() {
let existing = fs::read_to_string(&full_path)?;
if existing == *code {
println!(" {} {}", "SKIP".yellow(), path);
continue;
}
fs::write(&full_path, code)?;
updated += 1;
println!(" {} {}", "UPDATE".blue().bold(), path);
} else {
fs::write(&full_path, code)?;
created += 1;
println!(" {} {}", "CREATE".green().bold(), path);
}
}
println!();
println!("{}", "Done!".green().bold());
println!(" Created: {} files", created);
println!(" Updated: {} files", updated);
Ok(())
}
fn handle_init(force: bool) -> anyhow::Result<()> {
let draft_path = Path::new("draft.yaml");
if draft_path.exists() && !force {
eprintln!("{}: draft.yaml already exists. Use --force to overwrite.", "Error".red().bold());
std::process::exit(1);
}
let content = r#"models:
Post:
title: string:400
content: longtext
published_at: nullable datetime
author_id: id:user foreign
User:
name: string:100
email: string:100 unique
password: string:100
controllers:
Post:
resource: web
User:
resource: api
seeders:
- Post
- User
"#;
fs::write(draft_path, content)?;
println!("{} Created draft.yaml", "CREATE".green().bold());
println!();
println!("Next steps:");
println!(" 1. Edit draft.yaml to define your models and controllers");
println!(" 2. Run 'blueprint build' to generate Laravel code");
Ok(())
}
fn handle_list() -> anyhow::Result<()> {
let generators = vec![
("models", "Eloquent model classes with relationships, casts, and fillable attributes"),
("migrations", "Database migration files with columns, indexes, and foreign keys"),
("factories", "Model factories with Faker data definitions"),
("controllers", "Controller classes with resourceful action methods"),
("routes", "Web and API route definitions"),
("form_requests", "Form request validation classes"),
("tests", "Feature tests for controller actions"),
("seeders", "Database seeder classes"),
];
println!("{}", "Available generators:".green().bold());
println!();
for (name, description) in generators {
println!(" {:<15} {}", name, description);
}
println!();
println!("Usage: blueprint build --only models,controllers");
println!(" blueprint build --skip tests");
Ok(())
}