diff --git a/Cargo.toml b/Cargo.toml index bef9319..bce33a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "pirate" -version = "0.2.0" +version = "1.0.0" authors = ["Zach Dziura "] description = "A simple arrrguments parser" repository = "https://github.com/zcdziura/pirate" diff --git a/README.md b/README.md index c8136d6..855fe8f 100644 --- a/README.md +++ b/README.md @@ -6,28 +6,21 @@ A command-line arrrrguments parser, written in Rust. Synopsis -------- -Most programs that provide a command-line interface use a special-purpose library to make the -process easier, such as the GNU Project's `getopt` library. The Rust team provides their own -alternative to `getopt`: `getopts`, which should win an award for the Most Originally Named Project -Ever. +Most programs that provide a command-line interface use a special-purpose library to make the process easier, such as the GNU Project's `getopt` library. The Rust team provides their own alternative, `getoptions`, which deserves an award for the Most Originally Named Project Ever. -In all seriousness, `getopts` is a fantastic library that gives the developers all of the tools -necessary to create and interface with command-line arguments. However, with all that power comes -complexity. `getopts` -- while straight forward to use -- is verbose. The developer has to call -different functions repeatedly in order to add different command-line options to their programs. And -while the only victim here is the developer's wrists due to carpal tunnel, I felt that there was a +In all seriousness, `getoptions` is a fantastic library that gives the developers all of the power necessary to create and interface with command-line arguments. However, with all that power comes complexity. `getoptions` -- while straight forward to use -- is verbose. The developer has to call different functions repeatedly in order to add different command-line options to their programs. While the only victim here is the developer's wrists due to carpal tunnel, I felt that there was a better way to do things. -Enter Pirate (which should totally usurp `getopts` for the award of Most Originally Named Project Ever). +Enter Pirate (which should totally usurp `getoptions` for the award of Most Originally Named Project Ever). Installation ------------ -Add this to your `Cargo.toml: +Add this to your project's `Cargo.toml` file: ``` [dependencies] -pirate = "0.2.0" +pirate = "1.0.0" ``` and this to your crate root: @@ -39,50 +32,76 @@ extern crate pirate; Usage ----- -Using Pirate is simple. First, create a vector defining all of the valid opts that your -program accepts: - -`let opts = vec!["o:", "l/long", ":arg"];` - -Opts are defined in a specific format: - - * Opts that have an associated argument must be followed by a colon (:). - * Opts with both a short and long form are separated by a slash (/). If an opt has an associated - argument, the colon must come after the long form, e.g. `"l/long:"`. - * Required program arguments have a preceding colon, e.g. `":arg"`. - * All other opts are defined normally, e.g. `"l"` is an opt in short form, `"long"` is an opt in - long form. - -Next, call the `pirate::parse()` function, passing in the environment arguments along with a reference -to the opts that you defined: - -`let matches = pirate::parse(env::args(), &opts);` - -Now, handle any errors that may have arisen from parsing: +Using Pirate is simple. First, create a vector defining all of the valid options that your program accepts: +```rust +let options = vec![ + "a/addend#The right side of the addition equation; default=1:", + "#Required Arguments", + ":augend#The left side of an addition equation" +]; ``` -let matches: Matches = match pirate::parse(env::args(), &opts) { - Err(ref e) => { - println!("Error: {}", e); - help(); + +Options are defined in a very specific format: + + * Options that have an associated argument must be followed by a colon (:). The colon must be the last character of the option (see above for example). + * Long-form options are denoted by a preceding slash (/). Options are able to have short- and long-forms. Options which are only long-form still need a preceding slash, e.g. `"/addend"`. + * Required program arguments must have a preceding colon as the first character of the opt, e.g. `":augend"`. + * Option descriptions are denoted by a proceding hash (#). Descriptions are optional and are used to display helpful information about the option when displaying a program's usage information (typically when the `--help` flag is passed). Options with **only** a description (i.e. no short- or long-form name) are called "Groups", and are used to group options together when displaying usage. + +Next, create a `Vars` struct, which is responsible for keeping track of all of the options, along with the program's name, defined for the program: + +```rust +let vars: Vars = match pirate::vars("program-name", &options) { + Ok(v) => v, + Err(why) => panic!("Error: {}", why) +} +``` + +Next, call the `pirate::matches()` function, passing in a vector of the program's environment arguments, along with a mutable reference to the `Vars` struct that you previously defined: + +```rust +let matches: Matches = match pirate::matches(env::args().collect(), + &mut vars) { + Ok(m) => m, + Err(why) => { + println!("Error: {}", why); + pirate::usage(&vars); return; - }, - Ok(m) => m -}; + } +} ``` +`Matches` is nothing more than a type alias to a `HashMap`. All of the custom methods that make the type easier to use are defined by the `Match` trait. -Finally, you may want to check which arguments were passed to the program. Luckily, the `Matches` -struct provides several helpful methods for querying whether an argument was passed to the program -and what its value is. +And finally, check which arguments were passed to the program. ``` -fn get(arg: &str) -> Option<&String> // Returns a reference to the given arg, or None if not found +// Returns a reference to the given arg, or None if not found +fn get(arg: &str) -> Option<&String>; -fn has_arg(arg: &str) -> bool // Returns true if the arg exists, false if not +// Returns true if the match exists, false if not +fn has_match(arg: &str) -> bool; -fn keys() -> Keys // An iterator over all args passed to the program +// An iterator over all matches found +fn keys() -> Keys; ``` +Something to remember when using the `get()` function: by default, the `pirate::matches()` function stores the opt's long-form name as the key, by default, should the long-form exist; otherwise the short-form is used. So, should you define an opt with both a short- and long-form name, when querying for it, pass the long-form as the argument. For example: + +```rust +let options = vec!["l/long#An example opt"]; +let vars = pirate::vars("program-name", &options); +let matches = pirate::matches(&env::args().collect(), + &mut vars).unwrap(); + +let short = matches.get("l").unwrap(); // Error! This won't work! +let long = matches.get("long").unwrap(); // Success! + +// Usage: program-name -l +``` + +As shown in a previous example, should you ever want to display the program's usage data, simply call the `pirate::usage()` function, passing in a reverence to your `Vars` struct as an argument. E.g. `pirate::usage(&vars)` + Example ------- @@ -91,65 +110,51 @@ Here is a trivial example that gives a general idea about how to use `pirate`: ```rust extern crate pirate; -use std::env; - -use pirate::Matches; - +use pirate::{Matches, Match, matches, usage, vars}; fn main() { - let opts = vec!["n:", "b/boop", ":input"]; - - let matches: Matches = match pirate::parse(env::args(), &opts) { - Err(ref e) => { - println!("Error: {}", e); - help(); + let env_args: Vec = vec![ + String::from("test"), + String::from("-a"), String::from("2"), + String::from("3") + ]; + let options = vec![ + "a/addend#The right side of the addition equation; default=1:", + "#Required Arguments", + ":augend#The left side of an addition equation" + ]; + let mut vars = vars("test", &options).unwrap(); + + let matches: Matches = match matches(&env_args, &mut vars) { + Ok(m) => m, + Err(why) => { + println!("Error: {}", why); + usage(&vars); return; - }, - Ok(m) => m + } }; - - // Print the program help if necessary - if matches.has_arg("h") || matches.has_arg("help") { - help(); + + if matches.has_match("help") { + usage(&vars); return; } + + let augend: i32 = matches.get("augend") + .unwrap() + .parse::() + .unwrap(); - let input = matches.get("input").unwrap().parse::().unwrap(); - let num = match matches.get("n") { - Some(n) => n.parse::().unwrap(), + let addend: i32 = match matches.get("addend") { + Some(a) => a.parse::().unwrap(), None => 1 }; - - let sum = input + num; - - println!("{} + {} = {}", input, num, sum); - - if matches.has_arg("b") || matches.has_arg("boop") { - println!("Boop!!"); - } -} - -fn help() { - println!("usage: pirate-test [-n NUM] [-b|--boop] INPUT\n"); - - println!("Options:"); - println!(" -n NUM\tChange the default number that's added to the input"); - println!(" -b, --boop\tIt's a surprise!"); - - println!("\nRequired arguments:"); - println!(" INPUT\tWe're gonna manipulate this somehow, you'll see!"); + + let sum = augend + addend; + + println!("{} + {} = {}", augend, addend, sum); } ``` -To Do ------ - -- [ ] Create a helper function for generating `--help` output, rather than having the user create it -manually. - - [ ] Also create helper functions for defining the description section of the `--help` output. -- [x] Refactor the `ErrorKind` enum into a struct that is able to represent more complex data (such - giving the value of the invalid argument passed to the program). - License ------- diff --git a/src/lib.rs b/src/lib.rs index 970da74..0dcc20e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,4 +23,4 @@ mod vars; pub use matches::{Matches, Match, matches}; pub use vars::{Vars, vars}; -pub use usage::usage; +pub use usage::usage; \ No newline at end of file diff --git a/src/matches.rs b/src/matches.rs index 454f6f6..65910c4 100644 --- a/src/matches.rs +++ b/src/matches.rs @@ -23,7 +23,7 @@ use vars::Vars; pub type Matches = HashMap; -pub fn matches(vars: &mut Vars, env_args: &[String]) -> Result { +pub fn matches(env_args: &[String], vars: &mut Vars) -> Result { let mut matches: Matches = HashMap::new(); let mut args = env_args.iter(); @@ -56,7 +56,7 @@ pub fn matches(vars: &mut Vars, env_args: &[String]) -> Result { Some(a) => a }; - matches.insert(token.name(), current_arg.clone()); + matches.insert(token.name(), (*current_arg).clone()); } else { matches.insert(token.name(), String::new()); } @@ -66,7 +66,7 @@ pub fn matches(vars: &mut Vars, env_args: &[String]) -> Result { } } else { // Probably a required arg let arg = vars.get_arg().unwrap(); - matches.insert(arg.name(), current_arg.clone()); + matches.insert(arg.name(), (*current_arg).clone()); } } @@ -101,19 +101,21 @@ impl Match for Matches { #[cfg(test)] mod tests { - use super::matches; + use super::{Match, matches}; use super::super::vars::vars; #[test] fn test_matches_good() { - let opts = vec!["o/opt(An option)", "a(An argument):"]; let env_args = vec![String::from("test"), String::from("-a"), String::from("Test")]; - let mut vars = vars("Test", &opts).unwrap(); - let matches = match matches(&mut vars, &env_args) { + let opts = vec!["o/opt#An option", "a#An argument:"]; + + let mut var = match vars("Test", &opts) { Ok(m) => m, Err(why) => panic!("An error occurred: {}", why) }; + let matches = matches(&env_args, &mut var).unwrap(); + let has_opt = match matches.get("opt") { Some(_) => true, None => false @@ -126,10 +128,11 @@ mod tests { #[test] #[should_panic] fn test_matches_bad() { - let opts = vec!["o/opt(An option)", "a(An argument):"]; let env_args = vec![String::from("test"), String::from("-a")]; + let opts = vec!["o/opt#An option", "a#An argument:"]; + let mut vars = vars("Test", &opts).unwrap(); - match matches(&mut vars, &env_args) { + match matches(&env_args, &mut vars) { Ok(m) => m, Err(why) => panic!("An error occurred: {}", why) }; diff --git a/src/token.rs b/src/token.rs index ad6d2dd..22dc9fc 100644 --- a/src/token.rs +++ b/src/token.rs @@ -62,8 +62,7 @@ pub fn token(input: &str) -> Result { for c in option.chars() { match c { '/' => current_stage = AnalysisStage::LongName, - '(' => current_stage = AnalysisStage::Description, - ')' => (), + '#' => current_stage = AnalysisStage::Description, _ => { match current_stage { AnalysisStage::ShortName => short_name.push(c), @@ -195,7 +194,7 @@ mod tests { #[test] fn test_new_token() { - let opt = "h/help(Display the program usage)"; + let opt = "h/help#Display the program usage"; let token = match token(opt) { Ok(t) => t, Err(why) => panic!("Received error: {}", why) @@ -215,7 +214,7 @@ mod tests { #[test] fn test_new_group() { - let opt = "(This is a group)"; + let opt = "#This is a group"; let token = match token(opt) { Ok(t) => t, Err(why) => panic!("Received error: {}", why) @@ -235,7 +234,7 @@ mod tests { #[test] fn test_new_token_with_arg() { - let opt = "o/option(An option with an argument):"; + let opt = "o/option#An option with an argument:"; let token = match token(opt) { Ok(t) => t, Err(why) => panic!("Received error: {}", why) @@ -255,7 +254,7 @@ mod tests { #[test] fn test_new_token_as_arg() { - let opt = ":a/arg(An argument)"; + let opt = ":a/arg#An argument"; let token = match token(opt) { Ok(t) => t, Err(why) => panic!("Received error: {}", why) @@ -276,7 +275,7 @@ mod tests { #[test] #[should_panic] fn test_invalid_token_format() { - let input = ":w/wrong(Wrong format):"; + let input = ":w/wrong#Wrong format:"; match token(input) { Ok(t) => t, Err(why) => panic!("Received error: {}", why) @@ -286,8 +285,8 @@ mod tests { #[test] fn test_name() { let short_name = "o"; - let long_name = "o/out"; - let group = "(Output)"; + let long_name = "/out"; + let group = "#Output"; let short_token = token(short_name).unwrap(); let long_token = token(long_name).unwrap(); diff --git a/src/vars.rs b/src/vars.rs index 979cc88..1d67ced 100644 --- a/src/vars.rs +++ b/src/vars.rs @@ -123,4 +123,4 @@ impl Vars { pub fn tokens(&self) -> Iter{ self.tokens.iter() } -} +} \ No newline at end of file diff --git a/tests/main.rs b/tests/main.rs index b1a6fc7..4563448 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -6,11 +6,11 @@ use pirate::{Matches, Match, matches, usage, vars}; fn main() { let env_args: Vec = vec![String::from("test"), String::from("-a"), String::from("2"), String::from("3")]; - let opts = vec!["a/addend(The right side of the addition equation; default=1):", "(Required Arguments)", - ":augend(The left side of an addition equation)"]; + let opts = vec!["a/addend#The right side of the addition equation; default=1:", "#Required Arguments", + ":augend#The left side of an addition equation"]; let mut vars = vars("test", &opts).unwrap(); - let matches: Matches = match matches(&mut vars, &env_args) { + let matches: Matches = match matches(&env_args, &mut vars) { Ok(m) => m, Err(why) => { println!("Error: {}", why);