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
use clap::{self, AppSettings, SubCommand, App, Arg};
use std::env::{self, home_dir};
use array_tool::vec::Uniq;
use std::path::PathBuf;
use std::fs;
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct Options {
pub to_update: Vec<String>,
pub update: bool,
pub force: bool,
pub crates_file: (String, PathBuf),
pub cargo_dir: (String, PathBuf),
}
impl Options {
pub fn parse() -> Options {
let matches = App::new("cargo-install-update")
.bin_name("cargo")
.settings(&[AppSettings::ColoredHelp, AppSettings::ArgRequiredElseHelp, AppSettings::GlobalVersion, AppSettings::SubcommandRequired])
.subcommand(SubCommand::with_name("install-update")
.version(crate_version!())
.author(crate_authors!())
.about("A cargo subcommand for checking and applying updates to installed executables")
.args(&[Arg::from_usage("-c --cargo-dir=[CARGO_DIR] 'The cargo home directory. Default: $CARGO_HOME or $HOME/.cargo'")
.validator(Options::cargo_dir_validator),
Arg::from_usage("-a --all 'Update all packages'").conflicts_with("PACKAGE"),
Arg::from_usage("-l --list 'Don't update packages, only list and check if they need an update'"),
Arg::from_usage("-f --force 'Update all packages regardless if they need updating'"),
Arg::from_usage("<PACKAGE>... 'Packages to update'").conflicts_with("all").empty_values(false).min_values(1)]))
.get_matches();
let matches = matches.subcommand_matches("install-update").unwrap();
let cdir = match env::var("CARGO_HOME").map_err(|_| ()).and_then(|ch| fs::canonicalize(ch).map_err(|_| ())) {
Ok(ch) => ("$CARGO_HOME".to_string(), ch),
Err(()) =>
match home_dir().and_then(|hd| hd.canonicalize().ok()) {
Some(mut hd) => {
hd.push(".cargo");
fs::create_dir_all(&hd).unwrap();
("$HOME/.cargo".to_string(), hd)
}
None => {
clap::Error {
message: "$CARGO_HOME and home directory invalid, please specify the cargo home directory with the -c option".to_string(),
kind: clap::ErrorKind::MissingRequiredArgument,
info: None,
}
.exit()
}
},
};
Options {
to_update: if matches.is_present("all") {
vec![]
} else {
let packages: Vec<_> = matches.values_of("PACKAGE").unwrap().map(String::from).collect();
packages.unique()
},
update: !matches.is_present("list"),
force: matches.is_present("force"),
crates_file: match matches.value_of("cargo-dir") {
Some(dirs) => (dirs.to_string(), fs::canonicalize(dirs).unwrap()),
None => {
match env::var("CARGO_INSTALL_ROOT").map_err(|_| ()).and_then(|ch| fs::canonicalize(ch).map_err(|_| ())) {
Ok(ch) => ("$CARGO_INSTALL_ROOT/.crates.toml".to_string(), ch.join(".crates.toml")),
Err(()) => (format!("{}/.crates.toml", cdir.0), cdir.1.join(".crates.toml")),
}
}
},
cargo_dir: cdir,
}
}
fn cargo_dir_validator(s: String) -> Result<(), String> {
fs::canonicalize(&s).map(|_| ()).map_err(|_| format!("Cargo directory \"{}\" not found", s))
}
}