use clap::{
App,
AppSettings,
Arg,
};
use errors::*;
use std::env;
use std::ffi::OsString;
use std::path::PathBuf;
use which::which_in;
arg_enum!{
#[derive(Debug)]
#[allow(non_camel_case_types)]
pub enum StatsFormat {
text,
json
}
}
pub enum Command {
ShowStats(StatsFormat),
ZeroStats,
InternalStartServer,
StartServer,
StopServer,
Compile {
exe: OsString,
cmdline: Vec<OsString>,
cwd: PathBuf,
env_vars: Vec<(OsString, OsString)>,
},
}
pub fn get_app<'a, 'b>() -> App<'a, 'b> {
App::new(env!("CARGO_PKG_NAME"))
.version(env!("CARGO_PKG_VERSION"))
.setting(AppSettings::TrailingVarArg)
.after_help(concat!(
"Enabled features:\n",
" S3: ", cfg!(feature = "s3"), "\n",
" Redis: ", cfg!(feature = "redis"), "\n",
" GCS: ", cfg!(feature = "gcs"), "\n")
)
.args_from_usage(
"-s --show-stats 'show cache statistics'
-z, --zero-stats 'zero statistics counters'
--start-server 'start background server'
--stop-server 'stop background server'"
)
.arg(Arg::from_usage("--stats-format 'set output format of statistics'")
.possible_values(&StatsFormat::variants())
.default_value("text"))
.arg(
Arg::with_name("cmd")
.multiple(true)
.use_delimiter(false)
)
}
pub fn parse() -> Result<Command> {
trace!("parse");
let cwd = try!(env::current_dir().chain_err(|| "sccache: Couldn't determine current working directory"));
let internal_start_server = match env::var("SCCACHE_START_SERVER") {
Ok(val) => val == "1",
Err(_) => false,
};
let mut args: Vec<_> = env::args_os().collect();
if ! internal_start_server {
if let Ok(exe) = env::current_exe() {
match exe.file_stem().and_then(|s| s.to_str()).map(|s| s.to_lowercase()) {
Some(ref e) if e == env!("CARGO_PKG_NAME") => {}
_ => {
if let (Some(path), Some(exe_filename)) = (env::var_os("PATH"), exe.file_name()) {
match which_in(exe_filename, Some(&path), &cwd) {
Ok(ref full_path) if try!(full_path.canonicalize()) == try!(exe.canonicalize()) => {
if let Some(dir) = full_path.parent() {
let path = env::join_paths(env::split_paths(&path).filter(|p| p != dir)).ok();
match which_in(exe_filename, path, &cwd) {
Ok(full_path) => args[0] = full_path.into(),
Err(_) => { }
}
}
}
Ok(full_path) => args[0] = full_path.into(),
Err(_) => { }
}
args.insert(0, env!("CARGO_PKG_NAME").into());
}
}
}
}
}
let matches = get_app().get_matches_from(args);
let show_stats = matches.is_present("show-stats");
let start_server = matches.is_present("start-server");
let stop_server = matches.is_present("stop-server");
let zero_stats = matches.is_present("zero-stats");
let cmd = matches.values_of_os("cmd");
fn is_some<T>(x : &Option<T>) -> bool {
x.is_some()
}
if [
internal_start_server,
show_stats,
start_server,
stop_server,
is_some(&cmd),
].iter()
.fold(0, |acc, &x| acc + (x as usize)) > 1 {
bail!("Too many commands specified");
}
if internal_start_server {
Ok(Command::InternalStartServer)
} else if show_stats {
let fmt = value_t!(matches.value_of("stats-format"), StatsFormat)
.unwrap_or_else(|e| e.exit());
Ok(Command::ShowStats(fmt))
} else if start_server {
Ok(Command::StartServer)
} else if stop_server {
Ok(Command::StopServer)
} else if zero_stats {
Ok(Command::ZeroStats)
} else if let Some(mut args) = cmd {
if let Some(exe) = args.next() {
let cmdline = args.map(|s| s.to_owned()).collect::<Vec<_>>();
Ok(Command::Compile {
exe: exe.to_owned(),
cmdline: cmdline,
cwd: cwd,
env_vars: env::vars_os().collect(),
})
} else {
bail!("No compile command");
}
} else {
bail!("No command specified");
}
}