diff --git a/.travis.yml b/.travis.yml index fc8501a..e6e05dc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: rust +script: cargo run check # check that the advisory-db is well-formed branches: only: diff --git a/Cargo.toml b/Cargo.toml index 98d643a..9e28075 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,5 +9,10 @@ documentation = "https://github.com/rustsec/advisory-db" categories = ["api-bindings", "development-tools"] keywords = ["rustsec", "security", "advisory", "vulnerability"] +[[bin]] +name = "rustsec-advisory-db" + [dependencies] +gumdrop = "0.4" +gumdrop_derive = "0.4" rustsec = "0.7" diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 5bc4333..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,20 +0,0 @@ -extern crate rustsec; - -#[cfg(test)] -mod tests { - use rustsec::{AdvisoryDatabase, Repository}; - - #[test] - fn advisories_toml_is_well_formed() { - let repo = Repository::open(".").unwrap(); - - // Ensure Advisories.toml parses - let advisory_count = AdvisoryDatabase::from_repository(&repo) - .unwrap() - .advisories() - .count(); - - // Ensure we're parsing some advisories - assert!(advisory_count > 5); - } -} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..b743605 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,85 @@ +extern crate gumdrop; +#[macro_use] +extern crate gumdrop_derive; +extern crate rustsec; + +use gumdrop::Options; +use rustsec::{AdvisoryDatabase, Repository}; +use std::{env, process::exit}; + +const MIN_EXPECTED_ADVISORIES: usize = 5; + +/// Subcommands +#[derive(Debug, Options)] +enum Opts { + #[options(help = "show help for a command")] + Help(HelpOpts), + + #[options(help = "check the advisory DB is well-formed")] + Check(CheckOpts), +} + +/// Options for the `help` command +#[derive(Debug, Default, Options)] +struct HelpOpts { + #[options(free)] + commands: Vec, +} + +/// Options for the `check` command +#[derive(Debug, Default, Options)] +struct CheckOpts {} + +fn main() { + let args: Vec<_> = env::args().collect(); + + let opts = Opts::parse_args_default(&args[1..]).unwrap_or_else(|e| { + match e.to_string().as_ref() { + // Show usage if no command name is given or if "help" is given + "missing command name" => help(&[]), + string => eprintln!("{}: {}", args[0], string), + } + + exit(2); + }); + + match opts { + Opts::Help(opts) => help(&opts.commands), + Opts::Check(_) => check(), + } + + exit(0); +} + +/// Print help message +fn help(_commands: &[String]) { + println!("Usage: {} [COMMAND] [OPTIONS]", env::args().next().unwrap()); + println!(); + println!("Available commands:"); + println!(); + println!("{}", Opts::command_list().unwrap()); + println!(); +} + +fn check() { + let repo = Repository::open(".").unwrap(); + + // Ensure Advisories.toml parses + let advisory_count = AdvisoryDatabase::from_repository(&repo) + .unwrap() + .advisories() + .count(); + + // Ensure we're parsing some advisories + if advisory_count > MIN_EXPECTED_ADVISORIES { + println!( + "*** Check succeeded! Successfully parsed {} advisories.", + advisory_count + ); + } else { + panic!( + "Missing advisories! Expected at least {}, but got {}", + MIN_EXPECTED_ADVISORIES, advisory_count + ); + } +}