#![deny(missing_docs)]
use crate::output_type::OutputType;
use crate::recipe_completer::recipe_completer;
use crate::{mkdev_error::Error, recipe::Recipe};
use std::{collections::HashMap, path::PathBuf};
use clap::{
ArgAction, CommandFactory, Parser, Subcommand, crate_authors, crate_description, crate_version,
};
use clap_complete::engine::ArgValueCompleter;
#[derive(Parser, Debug)]
#[command(
name = "mk",
version = crate_version!(),
long_version = concat!(
crate_version!(), " — ", crate_description!(),
"\n© 2026 ", crate_authors!(),
".\nThis program is free software and comes with ABSOLUTELY NO WARRANTY.",
"\nYou are welcome to redistribute this software under certain conditions.",
"\nSee <https://github.com/4jamesccraven/mkdev/blob/main/LICENSE> for more details."
),
author = crate_authors!(),
about = crate_description!(),
disable_help_subcommand = true,
)]
pub struct Cli {
#[command(subcommand)]
pub command: Option<Commands>,
#[arg(short, long)]
pub interactive: bool,
#[arg(short, long, env = "CONFIG")]
pub config: Option<PathBuf>,
#[arg(short, long)]
pub gen_config: bool,
#[arg(short, long)]
pub print_config: bool,
#[arg(long, hide = true, env = "MANPAGE")]
pub man_page: bool,
}
impl Cli {
pub fn dispatch(self, recipes: HashMap<String, Recipe>) -> Result<(), Error> {
use crate::menus::editor;
use crate::recipe::{EvocationCtx, delete_recipe, imprint_recipe, list_recipe};
match self.command {
Some(command) => match command {
Commands::Evoke(sub_args) => {
EvocationCtx::from_args(sub_args, recipes).and_then(|mut ctx| ctx.evoke())
}
Commands::Imprint(sub_args) => imprint_recipe(sub_args, recipes),
Commands::Delete(sub_args) => delete_recipe(sub_args, recipes),
Commands::List(sub_args) => list_recipe(sub_args, recipes),
Commands::Alter(sub_args) => editor(sub_args, recipes),
},
None if self.interactive => {
let fake_args = Imprint {
interactive: true,
..Default::default()
};
imprint_recipe(fake_args, recipes)
}
None => {
Cli::command().print_help().unwrap();
Ok(())
}
}
}
}
#[derive(Subcommand, Debug)]
pub enum Commands {
#[command(aliases = ["build", "conjure", "invoke", "summon"])]
Evoke(Evoke),
#[command(aliases = ["clone"])]
Imprint(Imprint),
#[command(aliases = ["remove", "rm"])]
Delete(Delete),
#[command(aliases = ["show"])]
List(List),
#[command(aliases = ["edit"])]
Alter(Alter),
}
#[derive(Parser, Clone, Debug)]
pub struct Evoke {
#[arg(add = ArgValueCompleter::new(recipe_completer))]
pub recipes: Vec<String>,
#[arg(short, long)]
pub interactive: bool,
#[arg(last = true)]
pub dir_name: Option<String>,
#[arg(short, long)]
pub name: Option<String>,
#[arg(short, long)]
pub verbose: bool,
#[arg(short, long)]
pub suppress_warnings: bool,
}
#[derive(Parser, Debug, Default)]
pub struct Imprint {
#[arg(default_value = "", required_unless_present = "interactive")]
pub recipe: String,
#[arg(short, long)]
pub interactive: bool,
#[arg(short, long)]
pub description: Option<String>,
#[arg(short, long)]
pub suppress_warnings: bool,
#[arg(short = 'n', long, value_name = "FILE")]
pub to_nix: Option<PathBuf>,
#[arg(short, long, value_name = "FILE/GLOB", action = ArgAction::Append)]
pub exclude: Vec<String>,
#[arg(long)]
pub no_filter: bool,
}
#[derive(Parser, Debug)]
pub struct Delete {
#[arg(add = ArgValueCompleter::new(recipe_completer))]
pub recipe: String,
#[arg(long)]
pub namespace: bool,
}
#[derive(Parser, Debug)]
pub struct List {
#[arg(add = ArgValueCompleter::new(recipe_completer))]
pub recipe: Option<String>,
#[arg(short, long)]
pub r#type: Option<OutputType>,
#[arg(long)]
pub no_description: bool,
}
#[derive(Parser, Debug)]
pub struct Alter {
pub recipe: String,
}