use std::env;
use std::process;
use std::str::FromStr;
use expect_exit::ExpectedWithError;
#[macro_use]
extern crate quick_error;
use feature_check::defs;
use feature_check::expr as fexpr;
use feature_check::obtain;
quick_error! {
#[derive(Debug)]
enum OptsError {
InvalidOutputFormat(format: String) {
display("Unrecognized output format '{}'", format)
}
}
}
const USAGE: &str =
"Usage:\tfeature-check feature-check [-v] [-O optname] [-P prefix] program feature-name
\tfeature-check [-O optname] [-P prefix] program feature-name op version
\tfeature-check [-O optname] [-o json|tsv] [-P prefix] -l program
\tfeature-check -h | --help
\tfeature-check -V | --version";
const FEATURES: [(&str, &str); 4] = [
("feature-check", env!("CARGO_PKG_VERSION")),
("single", "1.0"),
("list", "1.0"),
("simple", "1.0"),
];
#[derive(Debug)]
enum OutputFormat {
Json,
Tsv,
}
impl OutputFormat {
const JSON_NAME: &'static str = "json";
const TSV_NAME: &'static str = "tsv";
}
impl FromStr for OutputFormat {
type Err = OptsError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
OutputFormat::JSON_NAME => Ok(OutputFormat::Json),
OutputFormat::TSV_NAME => Ok(OutputFormat::Tsv),
other => Err(OptsError::InvalidOutputFormat(other.to_string())),
}
}
}
impl AsRef<str> for OutputFormat {
fn as_ref(&self) -> &str {
match self {
OutputFormat::Json => OutputFormat::JSON_NAME,
OutputFormat::Tsv => OutputFormat::TSV_NAME,
}
}
}
#[derive(Debug)]
struct MainConfig {
config: defs::Config,
output_format: OutputFormat,
show_version: bool,
}
#[derive(Debug)]
enum MainMode {
Do(MainConfig),
Exit(i32),
}
fn parse_args() -> MainMode {
let args: Vec<String> = env::args().collect();
let mut optargs = getopts::Options::new();
optargs.optflag("h", "help", "display program usage output and exit");
optargs.optflag(
"l",
"",
"list the features supported by the specified program",
);
optargs.optopt(
"O",
"",
"specify the query-features option to pass to the program (default: '--features')",
"optname",
);
optargs.optopt(
"o",
"",
"specify the output format for the list of features (default: tsv)",
"format",
);
optargs.optopt(
"P",
"",
"the features prefix in the program output",
"prefix",
);
optargs.optflag("V", "version", "display program version output and exit");
optargs.optflag(
"v",
"",
"output the feature version when querying a single feature",
);
optargs.optflag("", "features", "list supported program features and exit");
match optargs.parse(&args[1..]) {
Ok(opts) => {
match opts.opt_present("V") || opts.opt_present("h") || opts.opt_present("features") {
true => {
if opts.opt_present("V") {
let (_, version) = FEATURES
.iter()
.find(|(name, _)| *name == "feature-check")
.unwrap();
println!("feature-check {}", version);
}
if opts.opt_present("h") {
println!("{}", optargs.usage(USAGE));
}
if opts.opt_present("features") {
let features: Vec<String> = FEATURES
.iter()
.map(|(name, version)| format!("{}={}", name, version))
.collect();
println!("Features: {}", features.join(" "));
}
MainMode::Exit(0)
}
false => match opts.free.get(0) {
Some(program) => match opts.free.len() > 1 {
true => match opts.opt_present("l") {
false => MainMode::Do(MainConfig {
config: defs::Config {
option_name: opts
.opt_get_default("O", defs::DEFAULT_OPTION_NAME.to_string())
.unwrap(),
prefix: opts
.opt_get_default("P", defs::DEFAULT_PREFIX.to_string())
.unwrap(),
program: program.clone(),
mode: match opts.free.len() > 2
|| opts.free[1].find(' ').is_some()
{
true => defs::Mode::Simple(opts.free[1..].join(" ")),
false => defs::Mode::Single(opts.free[1].clone()),
},
},
output_format: opts
.opt_get_default("o", OutputFormat::TSV_NAME.to_string())
.unwrap()
.parse()
.or_exit_e_("Invalid output format specified"),
show_version: opts.opt_present("v"),
}),
true => {
eprintln!("{}", optargs.usage(USAGE));
MainMode::Exit(1)
}
},
false => match opts.opt_present("l") {
true => MainMode::Do(MainConfig {
config: defs::Config {
option_name: opts
.opt_get_default("O", defs::DEFAULT_OPTION_NAME.to_string())
.unwrap(),
prefix: opts
.opt_get_default("P", defs::DEFAULT_PREFIX.to_string())
.unwrap(),
program: program.clone(),
mode: defs::Mode::List,
},
output_format: opts
.opt_get_default("o", OutputFormat::TSV_NAME.to_string())
.unwrap()
.parse()
.or_exit_e_("Invalid output format specified"),
show_version: opts.opt_present("v"),
}),
false => {
eprintln!("{}", optargs.usage(USAGE));
MainMode::Exit(1)
}
},
},
None => {
eprintln!("{}", optargs.usage(USAGE));
MainMode::Exit(1)
}
},
}
}
Err(err) => {
eprintln!("{}", err);
eprintln!("{}", optargs.usage(USAGE));
MainMode::Exit(1)
}
}
}
fn run(config: MainConfig) -> i32 {
match obtain::obtain_features(&config.config)
.or_exit_e_("Could not obtain the program's list of features")
{
defs::Obtained::Failed(_) => 2,
defs::Obtained::NotSupported => 2,
defs::Obtained::Features(features) => match config.config.mode {
defs::Mode::Single(feature) => match features.get(&feature) {
Some(value) => {
if config.show_version {
println!("{}", value);
}
0
}
None => 1,
},
defs::Mode::List => match config.output_format {
OutputFormat::Tsv => {
let mut res: Vec<String> = features
.iter()
.map(|(name, value)| format!("{}\t{}", name, value))
.collect();
res.sort();
println!("{}", res.join("\n"));
0
}
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&features).unwrap());
0
}
},
defs::Mode::Simple(expr) => {
let tree = fexpr::parse_simple(&expr).or_exit_e_("Invalid expression");
let value = tree
.get_value(&features)
.or_exit_e_("Could not evaluate the expression");
match value {
fexpr::CalcResult::Bool(true) => 0,
fexpr::CalcResult::Bool(false) => 1,
other => panic!("parse_simple().get_value() returned {:?}", other),
}
}
},
}
}
fn main() {
match parse_args() {
MainMode::Exit(rcode) => process::exit(rcode),
MainMode::Do(config) => process::exit(run(config)),
}
}