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();
}
}