1use crate::registry::HoistRegistry;
4use anyhow::Result;
5use clap::{ArgAction, Parser, Subcommand};
6
7#[derive(Debug, Parser)]
8#[clap(name = "cargo-hoist", author, bin_name = "cargo", version)]
9enum Cargo {
10 #[clap(alias = "h")]
11 Hoist(Args),
12}
13
14#[derive(Parser, Debug)]
16#[command(author, version, about, long_about = None)]
17pub struct Args {
18 #[clap(flatten)]
20 pub globals: GlobalOpts,
21
22 #[clap(subcommand)]
24 pub command: Option<Command>,
25}
26
27#[derive(Debug, clap::Args)]
29pub struct GlobalOpts {
30 #[arg(long, short, action = ArgAction::Count, default_value = "0")]
32 pub verbosity: u8,
33
34 #[arg(long, short)]
36 pub quiet: bool,
37}
38
39#[derive(Subcommand, Debug)]
41pub enum Command {
42 Hoist {
44 bins: Option<Vec<String>>,
46
47 #[clap(short, long)]
50 binaries: Option<Vec<String>>,
51 },
52 List,
54 #[clap(alias = "find")]
56 Search {
57 binary: String,
59 },
60 Nuke,
62 #[clap(alias = "install")]
64 Register {
65 bins: Option<Vec<String>>,
67
68 #[clap(short, long)]
71 binaries: Option<Vec<String>>,
72 },
73}
74
75pub fn run() -> Result<()> {
77 let Cargo::Hoist(arg) = Cargo::parse();
78
79 crate::telemetry::init_tracing_subscriber(arg.globals.verbosity)?;
80
81 HoistRegistry::create_pre_hook(true, false)?;
82
83 let res = match arg.command {
84 None => HoistRegistry::install(None, Vec::new(), arg.globals.quiet),
85 Some(c) => match c {
86 Command::Hoist { binaries, bins } => HoistRegistry::hoist(
87 crate::utils::merge_and_dedup_vecs(binaries, bins),
88 arg.globals.quiet,
89 ),
90 Command::Search { binary } => HoistRegistry::find(binary),
91 Command::List => HoistRegistry::list(false),
92 Command::Register { binaries, bins } => HoistRegistry::install(
93 None,
94 crate::utils::merge_and_dedup_vecs(binaries, bins),
95 arg.globals.quiet,
96 ),
97 Command::Nuke => HoistRegistry::nuke(false),
98 },
99 };
100 if let Err(e) = res {
101 if !arg.globals.quiet {
102 eprintln!("Error: {e:?}");
103 }
104 std::process::exit(1);
105 }
106 Ok(())
107}
108
109#[cfg(test)]
110mod tests {
111 use assert_cmd::Command;
112 use rand::{distributions::Alphanumeric, Rng};
113 use serial_test::serial;
114 use std::path::PathBuf;
115 use tempfile::TempDir;
116
117 const HOIST_BIN: &str = "cargo-hoist";
118
119 #[test]
120 #[serial]
121 fn test_cli_no_args() {
122 let tempdir = tempfile::tempdir().unwrap();
123 let _ = setup_test_dir(&tempdir);
124 let mut cmd = Command::cargo_bin(HOIST_BIN).unwrap();
125 let assert = cmd.arg("hoist").assert();
126 assert.success().stdout("");
127 }
128
129 #[test]
130 #[serial]
131 fn test_cli_nuke() {
132 let tempdir = tempfile::tempdir().unwrap();
133 let _ = setup_test_dir(&tempdir);
134 let mut cmd = Command::cargo_bin(HOIST_BIN).unwrap();
135 cmd.arg("hoist").arg("nuke").assert().success().stdout("");
136 }
137
138 #[test]
139 #[serial]
140 fn test_cli_install() {
141 let tempdir = tempfile::tempdir().unwrap();
142 let _ = setup_test_dir(&tempdir);
143 let mut cmd = Command::cargo_bin(HOIST_BIN).unwrap();
144 cmd.arg("hoist")
145 .arg("install")
146 .assert()
147 .success()
148 .stdout("");
149 }
150
151 #[test]
152 #[serial]
153 fn test_cli_list() {
154 let tempdir = tempfile::tempdir().unwrap();
155 let _ = setup_test_dir(&tempdir);
156 let mut cmd = Command::cargo_bin(HOIST_BIN).unwrap();
157 cmd.arg("hoist").arg("list").assert().success();
158 }
159
160 #[test]
161 #[serial]
162 fn test_cli_unrecognized_subcommand() {
163 let tempdir = tempfile::tempdir().unwrap();
164 let _ = setup_test_dir(&tempdir);
165 let mut cmd = Command::cargo_bin(HOIST_BIN).unwrap();
166 let assert = cmd.arg("hoist").arg("foobar").assert();
167 assert.failure().code(2).stderr(
168 r#"error: unrecognized subcommand 'foobar'
169
170Usage: cargo hoist [OPTIONS] [COMMAND]
171
172For more information, try '--help'.
173"#,
174 );
175 }
176
177 fn setup_test_dir(tempdir: &TempDir) -> PathBuf {
179 let s: String = rand::thread_rng()
181 .sample_iter(&Alphanumeric)
182 .take(7)
183 .map(char::from)
184 .collect();
185 let test_tempdir = tempdir.path().join(s);
186 std::fs::create_dir(&test_tempdir).unwrap();
187
188 let backup = match std::env::current_dir() {
191 Ok(d) => {
192 if d.join("target/debug/cargo-hoist").exists() {
193 std::fs::copy(
194 d.join("target/debug/cargo-hoist"),
195 test_tempdir.join("cargo-hoist"),
196 )
197 .unwrap();
198 false
199 } else {
200 true
201 }
202 }
203 Err(_) => true,
204 };
205 if backup {
206 let _ = std::process::Command::new("cargo")
207 .args(["install", "--path", "."])
208 .current_dir(&test_tempdir)
209 .output()
210 .unwrap();
211 }
212
213 std::env::set_current_dir(&test_tempdir).unwrap();
216
217 test_tempdir
218 }
219}