use std::{
path::{Path, PathBuf},
str::FromStr,
};
use anyhow::{anyhow, Result};
use clap::{Args, Parser};
use serde::Deserialize;
use crate::git;
mod style {
use anstyle::*;
use clap::builder::Styles;
const HEADER: Style = AnsiColor::Green.on_default().effects(Effects::BOLD);
const USAGE: Style = AnsiColor::Green.on_default().effects(Effects::BOLD);
const LITERAL: Style = AnsiColor::Cyan.on_default().effects(Effects::BOLD);
const PLACEHOLDER: Style = AnsiColor::Cyan.on_default();
const ERROR: Style = AnsiColor::Red.on_default().effects(Effects::BOLD);
const VALID: Style = AnsiColor::Cyan.on_default().effects(Effects::BOLD);
const INVALID: Style = AnsiColor::Yellow.on_default().effects(Effects::BOLD);
pub const STYLES: Styles = {
Styles::styled()
.header(HEADER)
.usage(USAGE)
.literal(LITERAL)
.placeholder(PLACEHOLDER)
.error(ERROR)
.valid(VALID)
.invalid(INVALID)
.error(ERROR)
};
}
mod heading {
pub const GIT_PARAMETERS: &str = "Git Parameters";
pub const TEMPLATE_SELECTION: &str = "Template Selection";
pub const OUTPUT_PARAMETERS: &str = "Output Parameters";
}
#[derive(Parser)]
#[command(
name = "cargo generate",
bin_name = "cargo",
arg_required_else_help(true),
version,
about,
next_line_help(false),
styles(style::STYLES)
)]
pub enum Cli {
#[command(name = "generate", visible_alias = "gen")]
Generate(GenerateArgs),
}
#[derive(Clone, Debug, Args)]
#[command(arg_required_else_help(true), version, about)]
pub struct GenerateArgs {
#[command(flatten)]
pub template_path: TemplatePath,
#[arg(
long,
action,
group("SpecificPath"),
conflicts_with_all(&[
"git", "path", "subfolder", "branch",
"name",
"force",
"silent",
"vcs",
"lib",
"bin",
"define",
"init",
"template_values_file",
"ssh_identity",
"test",
])
)]
pub list_favorites: bool,
#[arg(long, short, value_parser, help_heading = heading::OUTPUT_PARAMETERS)]
pub name: Option<String>,
#[arg(long, short, action, help_heading = heading::OUTPUT_PARAMETERS)]
pub force: bool,
#[arg(long, short, action)]
pub verbose: bool,
#[arg(long="values-file", value_parser, alias="template-values-file", value_name="FILE", help_heading = heading::OUTPUT_PARAMETERS)]
pub template_values_file: Option<String>,
#[arg(long, short, requires("name"), action)]
pub silent: bool,
#[arg(short, long, value_parser)]
pub config: Option<PathBuf>,
#[arg(long, value_parser, help_heading = heading::OUTPUT_PARAMETERS)]
pub vcs: Option<Vcs>,
#[arg(long, conflicts_with = "bin", action, help_heading = heading::OUTPUT_PARAMETERS)]
pub lib: bool,
#[arg(long, conflicts_with = "lib", action, help_heading = heading::OUTPUT_PARAMETERS)]
pub bin: bool,
#[arg(short = 'i', long = "identity", value_parser, value_name="IDENTITY", help_heading = heading::GIT_PARAMETERS)]
pub ssh_identity: Option<PathBuf>,
#[arg(long, short, number_of_values = 1, value_parser, help_heading = heading::OUTPUT_PARAMETERS)]
pub define: Vec<String>,
#[arg(long, action, help_heading = heading::OUTPUT_PARAMETERS)]
pub init: bool,
#[arg(long, value_parser, value_name="PATH", help_heading = heading::OUTPUT_PARAMETERS)]
pub destination: Option<PathBuf>,
#[arg(long, action, help_heading = heading::OUTPUT_PARAMETERS)]
pub force_git_init: bool,
#[arg(short, long, action, help_heading = heading::OUTPUT_PARAMETERS)]
pub allow_commands: bool,
#[arg(short, long, action, help_heading = heading::OUTPUT_PARAMETERS)]
pub overwrite: bool,
#[arg(long, action, help_heading = heading::GIT_PARAMETERS)]
pub skip_submodules: bool,
#[arg(skip)]
pub other_args: Option<Vec<String>>,
}
impl Default for GenerateArgs {
fn default() -> Self {
Self {
template_path: TemplatePath::default(),
list_favorites: false,
name: None,
force: false,
verbose: false,
template_values_file: None,
silent: false,
config: None,
vcs: None,
lib: true,
bin: false,
ssh_identity: None,
define: Vec::default(),
init: false,
destination: None,
force_git_init: false,
allow_commands: false,
overwrite: false,
skip_submodules: false,
other_args: None,
}
}
}
#[derive(Default, Debug, Clone, Args)]
pub struct TemplatePath {
#[arg(required_unless_present_any(&["SpecificPath"]))]
pub auto_path: Option<String>,
#[arg()]
pub subfolder: Option<String>,
#[arg(long, action, group("SpecificPath"))]
pub test: bool,
#[arg(short, long, group("SpecificPath"), help_heading = heading::TEMPLATE_SELECTION)]
pub git: Option<String>,
#[arg(short, long, conflicts_with_all = ["revision", "tag"], help_heading = heading::GIT_PARAMETERS)]
pub branch: Option<String>,
#[arg(short, long, conflicts_with_all = ["revision", "branch"], help_heading = heading::GIT_PARAMETERS)]
pub tag: Option<String>,
#[arg(short, long, conflicts_with_all = ["tag", "branch"], alias = "rev", help_heading = heading::GIT_PARAMETERS)]
pub revision: Option<String>,
#[arg(short, long, group("SpecificPath"), help_heading = heading::TEMPLATE_SELECTION)]
pub path: Option<String>,
#[arg(long, group("SpecificPath"), help_heading = heading::TEMPLATE_SELECTION)]
pub favorite: Option<String>,
}
impl TemplatePath {
pub fn any_path(&self) -> &str {
self.git
.as_ref()
.or(self.path.as_ref())
.or(self.favorite.as_ref())
.or(self.auto_path.as_ref())
.unwrap()
}
pub const fn git(&self) -> Option<&(impl AsRef<str> + '_)> {
self.git.as_ref()
}
pub const fn branch(&self) -> Option<&(impl AsRef<str> + '_)> {
self.branch.as_ref()
}
pub const fn tag(&self) -> Option<&(impl AsRef<str> + '_)> {
self.tag.as_ref()
}
pub const fn revision(&self) -> Option<&(impl AsRef<str> + '_)> {
self.revision.as_ref()
}
pub const fn path(&self) -> Option<&(impl AsRef<str> + '_)> {
self.path.as_ref()
}
pub const fn favorite(&self) -> Option<&(impl AsRef<str> + '_)> {
self.favorite.as_ref()
}
pub const fn auto_path(&self) -> Option<&(impl AsRef<str> + '_)> {
self.auto_path.as_ref()
}
pub const fn subfolder(&self) -> Option<&(impl AsRef<str> + '_)> {
if self.git.is_some() || self.path.is_some() || self.favorite.is_some() {
self.auto_path.as_ref()
} else {
self.subfolder.as_ref()
}
}
}
#[derive(Debug, Parser, Clone, Copy, PartialEq, Eq, Deserialize)]
pub enum Vcs {
None,
Git,
}
impl FromStr for Vcs {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_uppercase().as_str() {
"NONE" => Ok(Self::None),
"GIT" => Ok(Self::Git),
_ => Err(anyhow!("Must be one of 'git' or 'none'")),
}
}
}
impl Vcs {
pub fn initialize(&self, project_dir: &Path, branch: Option<&str>, force: bool) -> Result<()> {
match self {
Self::None => Ok(()),
Self::Git => git::init(project_dir, branch, force)
.map(|_| ())
.map_err(anyhow::Error::from),
}
}
pub const fn is_none(&self) -> bool {
matches!(self, Self::None)
}
}
#[cfg(test)]
mod cli_tests {
use super::*;
#[test]
fn test_cli() {
use clap::CommandFactory;
Cli::command().debug_assert()
}
}