1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
use std::{
    fs::OpenOptions,
    io::{self, Write},
};

use anyhow::{ensure, Context, Result};
use camino::{Utf8Path, Utf8PathBuf};
use clap::{Args, CommandFactory, Parser, ValueHint};
use clap_complete::Shell;

#[derive(Parser)]
#[command(name = "cargo", bin_name = "cargo")]
enum Cli {
    #[command(subcommand)]
    Hatch(Command),
}

#[derive(Parser)]
#[command(about, author, version)]
pub enum Command {
    /// Initialize a new template with a sample configuration.
    Init {
        /// Name of the new template, using the current working directory if omitted.
        name: Option<String>,
    },
    /// List all configured bookmarks with name and description.
    List,
    /// Create a new project from configured bookmarks.
    New {
        /// Bookmark as defined in the global configuration.
        bookmark: String,
        #[command(flatten)]
        flags: CreationFlags,
    },
    /// Create a new project from a template located in a remote Git repository.
    Git {
        /// An optional sub-folder within the repository that contains the template.
        #[arg(long)]
        folder: Option<Utf8PathBuf>,
        /// HTTP or Git URL to the remote repository.
        url: String,
        #[command(flatten)]
        flags: CreationFlags,
    },
    /// Create a new project from a template located in the local file system.
    Local {
        /// Location of the template directory.
        #[arg(value_hint = ValueHint::DirPath)]
        path: Utf8PathBuf,
        #[command(flatten)]
        flags: CreationFlags,
    },
    /// Generate auto-completion scripts for various shells.
    Completions {
        /// Shell to generate an auto-completion script for.
        #[arg(value_enum)]
        shell: Shell,
    },
    /// Generate man pages into the given directory.
    Manpages {
        /// Target directory, that must already exist and be empty. If the any file with the same
        /// name as any of the man pages already exist, it'll not be overwritten, but instead an
        /// error be returned.
        #[arg(value_hint = ValueHint::DirPath)]
        dir: Utf8PathBuf,
    },
}

#[derive(Args)]
pub struct CreationFlags {
    /// Name of the new project, using the current working directory if omitted.
    ///
    /// If the name contains slashes `/`, it is treated as a file path and the target directory is
    /// created automatically if missing. The final project name will become the last part of the
    /// path.
    pub name: Option<Utf8PathBuf>,
    /// Update all dependencies to the latest compatible version after project creation.
    #[arg(short, long)]
    pub update_deps: bool,
}

#[must_use]
pub fn parse() -> Command {
    let Cli::Hatch(cmd) = Cli::parse();
    cmd
}

/// Generate shell completions, written to the standard output.
pub fn completions(shell: Shell) {
    clap_complete::generate(
        shell,
        &mut Cli::command(),
        env!("CARGO_PKG_NAME"),
        &mut io::stdout().lock(),
    );
}

/// Generate man pages in the target directory. The directory must already exist and none of the
/// files exist, or an error is returned.
pub fn manpages(dir: &Utf8Path) -> Result<()> {
    fn print(dir: &Utf8Path, app: &clap::Command) -> Result<()> {
        let name = app.get_display_name().unwrap_or_else(|| app.get_name());
        let out = dir.join(format!("{name}.1"));
        let mut out = OpenOptions::new()
            .write(true)
            .create_new(true)
            .open(&out)
            .with_context(|| format!("the file `{out}` already exists"))?;

        clap_mangen::Man::new(app.clone()).render(&mut out)?;
        out.flush()?;

        for sub in app.get_subcommands() {
            print(dir, sub)?;
        }

        Ok(())
    }

    ensure!(dir.try_exists()?, "target directory doesn't exist");

    let mut app = Command::command();
    app.build();

    print(dir, &app)
}

#[cfg(test)]
mod tests {
    use super::Cli;

    #[test]
    fn verify_cli() {
        use clap::CommandFactory;
        Cli::command().debug_assert();
    }
}