1use std::{
2 path::{Path, PathBuf},
3 str::FromStr,
4};
5
6use anyhow::{anyhow, Result};
7use clap::{Args, Parser};
8use serde::Deserialize;
9
10use crate::git;
11
12mod style {
14 use anstyle::*;
15 use clap::builder::Styles;
16
17 const HEADER: Style = AnsiColor::Green.on_default().effects(Effects::BOLD);
18 const USAGE: Style = AnsiColor::Green.on_default().effects(Effects::BOLD);
19 const LITERAL: Style = AnsiColor::Cyan.on_default().effects(Effects::BOLD);
20 const PLACEHOLDER: Style = AnsiColor::Cyan.on_default();
21 const ERROR: Style = AnsiColor::Red.on_default().effects(Effects::BOLD);
22 const VALID: Style = AnsiColor::Cyan.on_default().effects(Effects::BOLD);
23 const INVALID: Style = AnsiColor::Yellow.on_default().effects(Effects::BOLD);
24
25 pub const STYLES: Styles = {
26 Styles::styled()
27 .header(HEADER)
28 .usage(USAGE)
29 .literal(LITERAL)
30 .placeholder(PLACEHOLDER)
31 .error(ERROR)
32 .valid(VALID)
33 .invalid(INVALID)
34 .error(ERROR)
35 };
36}
37
38mod heading {
39 pub const GIT_PARAMETERS: &str = "Git Parameters";
40 pub const TEMPLATE_SELECTION: &str = "Template Selection";
41 pub const OUTPUT_PARAMETERS: &str = "Output Parameters";
42}
43
44#[derive(Parser)]
45#[command(
46 name = "cargo generate",
47 bin_name = "cargo",
48 arg_required_else_help(true),
49 version,
50 about,
51 next_line_help(false),
52 styles(style::STYLES)
53)]
54pub enum Cli {
55 #[command(name = "generate", visible_alias = "gen")]
56 Generate(GenerateArgs),
57}
58
59#[derive(Clone, Debug, Args)]
60#[command(arg_required_else_help(true), version, about)]
61pub struct GenerateArgs {
62 #[command(flatten)]
63 pub template_path: TemplatePath,
64
65 #[arg(
67 long,
68 action,
69 group("SpecificPath"),
70 conflicts_with_all(&[
71 "git", "path", "subfolder", "branch",
72 "name",
73 "force",
74 "silent",
75 "vcs",
76 "lib",
77 "bin",
78 "define",
79 "init",
80 "template_values_file",
81 "ssh_identity",
82 "test",
83 ])
84 )]
85 pub list_favorites: bool,
86
87 #[arg(long, short, value_parser, help_heading = heading::OUTPUT_PARAMETERS)]
90 pub name: Option<String>,
91
92 #[arg(long, short, action, help_heading = heading::OUTPUT_PARAMETERS)]
95 pub force: bool,
96
97 #[arg(long, short, action, conflicts_with = "quiet")]
99 pub verbose: bool,
100
101 #[arg(
104 long,
105 short,
106 action,
107 conflicts_with = "verbose",
108 requires = "continue_on_error"
109 )]
110 pub quiet: bool,
111
112 #[arg(long, action)]
114 pub continue_on_error: bool,
115
116 #[arg(long="values-file", value_parser, alias="template-values-file", value_name="FILE", help_heading = heading::OUTPUT_PARAMETERS)]
119 pub template_values_file: Option<String>,
120
121 #[arg(long, short, requires("name"), action)]
124 pub silent: bool,
125
126 #[arg(short, long, value_parser)]
129 pub config: Option<PathBuf>,
130
131 #[arg(long, value_parser, help_heading = heading::OUTPUT_PARAMETERS)]
133 pub vcs: Option<Vcs>,
134
135 #[arg(long, conflicts_with = "bin", action, help_heading = heading::OUTPUT_PARAMETERS)]
137 pub lib: bool,
138
139 #[arg(long, conflicts_with = "lib", action, help_heading = heading::OUTPUT_PARAMETERS)]
141 pub bin: bool,
142
143 #[arg(short = 'i', long = "identity", value_parser, value_name="IDENTITY", help_heading = heading::GIT_PARAMETERS)]
145 pub ssh_identity: Option<PathBuf>,
146
147 #[arg(long = "gitconfig", value_parser, value_name="GITCONFIG_FILE", help_heading = heading::GIT_PARAMETERS)]
149 pub gitconfig: Option<PathBuf>,
150
151 #[arg(long, short, number_of_values = 1, value_parser, help_heading = heading::OUTPUT_PARAMETERS)]
153 pub define: Vec<String>,
154
155 #[arg(long, action, help_heading = heading::OUTPUT_PARAMETERS)]
158 pub init: bool,
159
160 #[arg(long, value_parser, value_name="PATH", help_heading = heading::OUTPUT_PARAMETERS)]
162 pub destination: Option<PathBuf>,
163
164 #[arg(long, action, help_heading = heading::OUTPUT_PARAMETERS)]
166 pub force_git_init: bool,
167
168 #[arg(short, long, action, help_heading = heading::OUTPUT_PARAMETERS)]
172 pub allow_commands: bool,
173
174 #[arg(short, long, action, help_heading = heading::OUTPUT_PARAMETERS)]
176 pub overwrite: bool,
177
178 #[arg(long, action, help_heading = heading::GIT_PARAMETERS)]
180 pub skip_submodules: bool,
181
182 #[arg(long, action, help_heading = heading::OUTPUT_PARAMETERS)]
185 pub no_workspace: bool,
186
187 #[arg(skip)]
189 pub other_args: Option<Vec<String>>,
190}
191
192impl Default for GenerateArgs {
193 fn default() -> Self {
194 Self {
195 template_path: TemplatePath::default(),
196 list_favorites: false,
197 name: None,
198 force: false,
199 verbose: false,
200 quiet: false,
201 continue_on_error: false,
202 template_values_file: None,
203 silent: false,
204 config: None,
205 vcs: None,
206 lib: true,
207 bin: false,
208 ssh_identity: None,
209 gitconfig: None,
210 define: Vec::default(),
211 init: false,
212 destination: None,
213 force_git_init: false,
214 allow_commands: false,
215 overwrite: false,
216 skip_submodules: false,
217 no_workspace: false,
218 other_args: None,
219 }
220 }
221}
222
223#[derive(Default, Debug, Clone, Args)]
224pub struct TemplatePath {
225 #[arg(required_unless_present_any(&["SpecificPath"]))]
228 pub auto_path: Option<String>,
229
230 #[arg()]
232 pub subfolder: Option<String>,
233
234 #[arg(long, action, group("SpecificPath"))]
241 pub test: bool,
242
243 #[arg(short, long, group("SpecificPath"), help_heading = heading::TEMPLATE_SELECTION)]
250 pub git: Option<String>,
251
252 #[arg(short, long, conflicts_with_all = ["revision", "tag"], help_heading = heading::GIT_PARAMETERS)]
254 pub branch: Option<String>,
255
256 #[arg(short, long, conflicts_with_all = ["revision", "branch"], help_heading = heading::GIT_PARAMETERS)]
258 pub tag: Option<String>,
259
260 #[arg(short, long, conflicts_with_all = ["tag", "branch"], alias = "rev", help_heading = heading::GIT_PARAMETERS)]
262 pub revision: Option<String>,
263
264 #[arg(short, long, group("SpecificPath"), help_heading = heading::TEMPLATE_SELECTION)]
266 pub path: Option<String>,
267
268 #[arg(long, group("SpecificPath"), help_heading = heading::TEMPLATE_SELECTION)]
271 pub favorite: Option<String>,
272}
273
274impl TemplatePath {
275 pub fn any_path(&self) -> &str {
279 self.git
280 .as_ref()
281 .or(self.path.as_ref())
282 .or(self.favorite.as_ref())
283 .or(self.auto_path.as_ref())
284 .unwrap()
285 }
286
287 pub const fn git(&self) -> Option<&(impl AsRef<str> + '_)> {
288 self.git.as_ref()
289 }
290
291 pub const fn branch(&self) -> Option<&(impl AsRef<str> + '_)> {
292 self.branch.as_ref()
293 }
294
295 pub const fn tag(&self) -> Option<&(impl AsRef<str> + '_)> {
296 self.tag.as_ref()
297 }
298
299 pub const fn revision(&self) -> Option<&(impl AsRef<str> + '_)> {
300 self.revision.as_ref()
301 }
302
303 pub const fn path(&self) -> Option<&(impl AsRef<str> + '_)> {
304 self.path.as_ref()
305 }
306
307 pub const fn favorite(&self) -> Option<&(impl AsRef<str> + '_)> {
308 self.favorite.as_ref()
309 }
310
311 pub const fn auto_path(&self) -> Option<&(impl AsRef<str> + '_)> {
312 self.auto_path.as_ref()
313 }
314
315 pub const fn subfolder(&self) -> Option<&(impl AsRef<str> + '_)> {
316 if self.git.is_some() || self.path.is_some() || self.favorite.is_some() {
317 self.auto_path.as_ref()
318 } else {
319 self.subfolder.as_ref()
320 }
321 }
322}
323
324#[derive(Debug, Parser, Clone, Copy, PartialEq, Eq, Deserialize)]
325pub enum Vcs {
326 None,
327 Git,
328}
329
330impl FromStr for Vcs {
331 type Err = anyhow::Error;
332
333 fn from_str(s: &str) -> Result<Self, Self::Err> {
334 match s.to_uppercase().as_str() {
335 "NONE" => Ok(Self::None),
336 "GIT" => Ok(Self::Git),
337 _ => Err(anyhow!("Must be one of 'git' or 'none'")),
338 }
339 }
340}
341
342impl Vcs {
343 pub fn initialize(&self, project_dir: &Path, branch: Option<&str>, force: bool) -> Result<()> {
344 match self {
345 Self::None => Ok(()),
346 Self::Git => git::init(project_dir, branch, force)
347 .map(|_| ())
348 .map_err(anyhow::Error::from),
349 }
350 }
351
352 pub const fn is_none(&self) -> bool {
353 matches!(self, Self::None)
354 }
355}
356
357#[cfg(test)]
358mod cli_tests {
359 use super::*;
360
361 #[test]
362 fn test_cli() {
363 use clap::CommandFactory;
364 Cli::command().debug_assert()
365 }
366}