]>
Commit | Line | Data |
---|---|---|
ee5e24ff | 1 | #![feature(collections, core, io, path, env)] |
92b449b6 | 2 | |
bd70721d | 3 | extern crate "rustc-serialize" as rustc_serialize; |
55321111 | 4 | extern crate cargo; |
98854f6f | 5 | extern crate env_logger; |
ee5e24ff | 6 | #[macro_use] extern crate log; |
3a15f5b7 | 7 | |
8ab8b58b | 8 | use std::collections::BTreeSet; |
ee5e24ff | 9 | use std::env; |
af45e088 GSF |
10 | use std::old_io::fs::{self, PathExtensions}; |
11 | use std::old_io::process::{Command,InheritFd,ExitStatus,ExitSignal}; | |
ee5e24ff | 12 | use std::old_io; |
ba273af5 AC |
13 | |
14 | use cargo::{execute_main_without_stdin, handle_error, shell}; | |
19bea0ad | 15 | use cargo::core::MultiShell; |
5d0cb3f2 | 16 | use cargo::util::{CliError, CliResult, lev_distance, Config}; |
3a15f5b7 | 17 | |
ba280047 | 18 | #[derive(RustcDecodable)] |
3512d997 AC |
19 | struct Flags { |
20 | flag_list: bool, | |
21 | flag_verbose: bool, | |
22 | arg_command: String, | |
23 | arg_args: Vec<String>, | |
3a15f5b7 YK |
24 | } |
25 | ||
3512d997 | 26 | const USAGE: &'static str = " |
ba273af5 AC |
27 | Rust's package manager |
28 | ||
29 | Usage: | |
30 | cargo <command> [<args>...] | |
fff5de37 | 31 | cargo [options] |
ba273af5 AC |
32 | |
33 | Options: | |
34 | -h, --help Display this message | |
35 | -V, --version Print version info and exit | |
a3f6a404 | 36 | --list List installed commands |
ba273af5 AC |
37 | -v, --verbose Use verbose output |
38 | ||
39 | Some common cargo commands are: | |
40 | build Compile the current project | |
41 | clean Remove the target directory | |
42 | doc Build this project's and its dependencies' documentation | |
43 | new Create a new cargo project | |
44 | run Build and execute src/main.rs | |
45 | test Run the tests | |
92e40c43 | 46 | bench Run the benchmarks |
620b2fe1 | 47 | update Update dependencies listed in Cargo.lock |
ba273af5 AC |
48 | |
49 | See 'cargo help <command>' for more information on a specific command. | |
3512d997 AC |
50 | "; |
51 | ||
52 | fn main() { | |
98854f6f | 53 | env_logger::init().unwrap(); |
3512d997 AC |
54 | execute_main_without_stdin(execute, true, USAGE) |
55 | } | |
3a15f5b7 | 56 | |
55321111 AC |
57 | macro_rules! each_subcommand{ ($mac:ident) => ({ |
58 | $mac!(bench); | |
59 | $mac!(build); | |
60 | $mac!(clean); | |
55321111 AC |
61 | $mac!(doc); |
62 | $mac!(fetch); | |
63 | $mac!(generate_lockfile); | |
64 | $mac!(git_checkout); | |
65 | $mac!(help); | |
66 | $mac!(locate_project); | |
67 | $mac!(login); | |
68 | $mac!(new); | |
69 | $mac!(owner); | |
70 | $mac!(package); | |
71 | $mac!(pkgid); | |
72 | $mac!(publish); | |
73 | $mac!(read_manifest); | |
74 | $mac!(run); | |
75 | $mac!(search); | |
76 | $mac!(test); | |
77 | $mac!(update); | |
78 | $mac!(verify_project); | |
79 | $mac!(version); | |
80 | $mac!(yank); | |
157d639a | 81 | }) } |
8cce8996 | 82 | |
739b1309 YK |
83 | /** |
84 | The top-level `cargo` command handles configuration and project location | |
85 | because they are fundamental (and intertwined). Other commands can rely | |
86 | on this top-level information. | |
87 | */ | |
5d0cb3f2 | 88 | fn execute(flags: Flags, config: &Config) -> CliResult<Option<()>> { |
5d0cb3f2 | 89 | config.shell().set_verbose(flags.flag_verbose); |
8cce8996 | 90 | |
a3f6a404 | 91 | if flags.flag_list { |
92 | println!("Installed Commands:"); | |
03e3dba1 | 93 | for command in list_commands().into_iter() { |
a3f6a404 | 94 | println!(" {}", command); |
a3f6a404 | 95 | }; |
96 | return Ok(None) | |
97 | } | |
8cce8996 | 98 | |
25e537aa | 99 | let (mut args, command) = match &flags.arg_command[] { |
6968611f | 100 | "" | "help" if flags.arg_args.len() == 0 => { |
5d0cb3f2 | 101 | config.shell().set_verbose(true); |
ee5e24ff | 102 | let args = &["foo".to_string(), "-h".to_string()]; |
5d0cb3f2 | 103 | let r = cargo::call_main_without_stdin(execute, config, USAGE, args, |
e602263a | 104 | false); |
5d0cb3f2 | 105 | cargo::process_executed(r, &mut **config.shell()); |
8cce8996 | 106 | return Ok(None) |
a3f6a404 | 107 | } |
25e537aa AC |
108 | "help" if flags.arg_args[0] == "-h" || |
109 | flags.arg_args[0] == "--help" => | |
8a3706ac | 110 | (flags.arg_args, "help"), |
25e537aa | 111 | "help" => (vec!["-h".to_string()], &flags.arg_args[0][]), |
8cce8996 AC |
112 | s => (flags.arg_args.clone(), s), |
113 | }; | |
114 | args.insert(0, command.to_string()); | |
ee5e24ff | 115 | args.insert(0, "foo".to_string()); |
8cce8996 | 116 | |
157d639a | 117 | macro_rules! cmd{ ($name:ident) => ( |
25e537aa | 118 | if command == stringify!($name).replace("_", "-") { |
8cce8996 | 119 | mod $name; |
5d0cb3f2 AC |
120 | config.shell().set_verbose(true); |
121 | let r = cargo::call_main_without_stdin($name::execute, config, | |
3512d997 | 122 | $name::USAGE, |
25e537aa | 123 | &args, |
8cce8996 | 124 | false); |
5d0cb3f2 | 125 | cargo::process_executed(r, &mut **config.shell()); |
8cce8996 AC |
126 | return Ok(None) |
127 | } | |
157d639a AC |
128 | ) } |
129 | each_subcommand!(cmd); | |
8cce8996 | 130 | |
25e537aa | 131 | execute_subcommand(&command, &args, &mut config.shell()); |
a3f6a404 | 132 | Ok(None) |
133 | } | |
134 | ||
12f5de8e PW |
135 | fn find_closest(cmd: &str) -> Option<String> { |
136 | match list_commands().iter() | |
ee5e24ff AC |
137 | // doing it this way (instead of just .min_by(|c| |
138 | // c.lev_distance(cmd))) allows us to only make | |
139 | // suggestions that have an edit distance of | |
12f5de8e | 140 | // 3 or less |
25e537aa | 141 | .map(|c| (lev_distance(&c, cmd), c)) |
55321111 | 142 | .filter(|&(d, _): &(usize, &String)| d < 4) |
12f5de8e PW |
143 | .min_by(|&(d, _)| d) { |
144 | Some((_, c)) => { | |
145 | Some(c.to_string()) | |
146 | }, | |
147 | None => None | |
148 | } | |
149 | } | |
150 | ||
8cce8996 AC |
151 | fn execute_subcommand(cmd: &str, args: &[String], shell: &mut MultiShell) { |
152 | let command = match find_command(cmd) { | |
153 | Some(command) => command, | |
12f5de8e PW |
154 | None => { |
155 | let msg = match find_closest(cmd) { | |
5d0cb3f2 AC |
156 | Some(closest) => format!("No such subcommand\n\n\t\ |
157 | Did you mean `{}`?\n", closest), | |
12f5de8e PW |
158 | None => "No such subcommand".to_string() |
159 | }; | |
25e537aa | 160 | return handle_error(CliError::new(&msg, 127), shell) |
12f5de8e | 161 | } |
8cce8996 AC |
162 | }; |
163 | let status = Command::new(command) | |
164 | .args(args) | |
165 | .stdin(InheritFd(0)) | |
166 | .stdout(InheritFd(1)) | |
167 | .stderr(InheritFd(2)) | |
168 | .status(); | |
169 | ||
170 | match status { | |
171 | Ok(ExitStatus(0)) => (), | |
172 | Ok(ExitStatus(i)) => { | |
ee5e24ff | 173 | handle_error(CliError::new("", i as i32), shell) |
8cce8996 AC |
174 | } |
175 | Ok(ExitSignal(i)) => { | |
176 | let msg = format!("subcommand failed with signal: {}", i); | |
25e537aa | 177 | handle_error(CliError::new(&msg, i as i32), shell) |
8cce8996 | 178 | } |
af45e088 | 179 | Err(old_io::IoError{kind, ..}) if kind == old_io::FileNotFound => |
8cce8996 AC |
180 | handle_error(CliError::new("No such subcommand", 127), shell), |
181 | Err(err) => handle_error( | |
182 | CliError::new( | |
25e537aa | 183 | &format!("Subcommand failed to run: {}", err), 127), |
8cce8996 | 184 | shell) |
a3f6a404 | 185 | } |
186 | } | |
187 | ||
188 | /// List all runnable commands. find_command should always succeed | |
189 | /// if given one of returned command. | |
8ab8b58b | 190 | fn list_commands() -> BTreeSet<String> { |
a3f6a404 | 191 | let command_prefix = "cargo-"; |
8ab8b58b | 192 | let mut commands = BTreeSet::new(); |
a3f6a404 | 193 | for dir in list_command_directory().iter() { |
194 | let entries = match fs::readdir(dir) { | |
195 | Ok(entries) => entries, | |
196 | _ => continue | |
197 | }; | |
198 | for entry in entries.iter() { | |
199 | let filename = match entry.filename_str() { | |
200 | Some(filename) => filename, | |
201 | _ => continue | |
202 | }; | |
203 | if filename.starts_with(command_prefix) && | |
ee5e24ff | 204 | filename.ends_with(env::consts::EXE_SUFFIX) && |
a3f6a404 | 205 | is_executable(entry) { |
d73a110d AC |
206 | let command = &filename[ |
207 | command_prefix.len().. | |
ee5e24ff | 208 | filename.len() - env::consts::EXE_SUFFIX.len()]; |
a3f6a404 | 209 | commands.insert(String::from_str(command)); |
64ff29ff | 210 | } |
6a809efb | 211 | } |
92b449b6 | 212 | } |
8cce8996 | 213 | |
157d639a | 214 | macro_rules! add_cmd{ ($cmd:ident) => ({ |
8cce8996 | 215 | commands.insert(stringify!($cmd).replace("_", "-")); |
157d639a | 216 | }) } |
8cce8996 | 217 | each_subcommand!(add_cmd); |
a3f6a404 | 218 | commands |
219 | } | |
220 | ||
221 | fn is_executable(path: &Path) -> bool { | |
222 | match fs::stat(path) { | |
af45e088 GSF |
223 | Ok(old_io::FileStat{ kind: old_io::FileType::RegularFile, perm, ..}) => |
224 | perm.contains(old_io::OTHER_EXECUTE), | |
a3f6a404 | 225 | _ => false |
226 | } | |
227 | } | |
228 | ||
229 | /// Get `Command` to run given command. | |
230 | fn find_command(cmd: &str) -> Option<Path> { | |
ee5e24ff | 231 | let command_exe = format!("cargo-{}{}", cmd, env::consts::EXE_SUFFIX); |
a3f6a404 | 232 | let dirs = list_command_directory(); |
25e537aa | 233 | let mut command_paths = dirs.iter().map(|dir| dir.join(&command_exe)); |
a3f6a404 | 234 | command_paths.find(|path| path.exists()) |
235 | } | |
236 | ||
237 | /// List candidate locations where subcommands might be installed. | |
238 | fn list_command_directory() -> Vec<Path> { | |
239 | let mut dirs = vec![]; | |
ee5e24ff AC |
240 | if let Ok(mut path) = env::current_exe() { |
241 | path.pop(); | |
242 | dirs.push(path.join("../lib/cargo")); | |
243 | dirs.push(path); | |
244 | } | |
245 | if let Some(val) = env::var("PATH") { | |
246 | dirs.extend(env::split_paths(&val)); | |
247 | } | |
a3f6a404 | 248 | dirs |
3a15f5b7 | 249 | } |