Version 1.0
This commit is contained in:
parent
14b5cad8c1
commit
1c75b5354e
7 changed files with 123 additions and 116 deletions
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
|
||||
name = "pirate"
|
||||
version = "0.2.0"
|
||||
version = "1.0.0"
|
||||
authors = ["Zach Dziura <zcdziura@gmail.com>"]
|
||||
description = "A simple arrrguments parser"
|
||||
repository = "https://github.com/zcdziura/pirate"
|
||||
|
|
189
README.md
189
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<String, String>`. 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<String, String> // An iterator over all args passed to the program
|
||||
// An iterator over all matches found
|
||||
fn keys() -> Keys<String, String>;
|
||||
```
|
||||
|
||||
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<String> = 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::<i32>()
|
||||
.unwrap();
|
||||
|
||||
let input = matches.get("input").unwrap().parse::<i32>().unwrap();
|
||||
let num = match matches.get("n") {
|
||||
Some(n) => n.parse::<i32>().unwrap(),
|
||||
let addend: i32 = match matches.get("addend") {
|
||||
Some(a) => a.parse::<i32>().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
|
||||
-------
|
||||
|
||||
|
|
|
@ -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;
|
|
@ -23,7 +23,7 @@ use vars::Vars;
|
|||
|
||||
pub type Matches = HashMap<String, String>;
|
||||
|
||||
pub fn matches(vars: &mut Vars, env_args: &[String]) -> Result<Matches, Error> {
|
||||
pub fn matches(env_args: &[String], vars: &mut Vars) -> Result<Matches, Error> {
|
||||
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<Matches, Error> {
|
|||
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<Matches, Error> {
|
|||
}
|
||||
} 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)
|
||||
};
|
||||
|
|
17
src/token.rs
17
src/token.rs
|
@ -62,8 +62,7 @@ pub fn token(input: &str) -> Result<Token, Error> {
|
|||
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();
|
||||
|
|
|
@ -123,4 +123,4 @@ impl Vars {
|
|||
pub fn tokens(&self) -> Iter<Token>{
|
||||
self.tokens.iter()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,11 +6,11 @@ use pirate::{Matches, Match, matches, usage, vars};
|
|||
fn main() {
|
||||
let env_args: Vec<String> = 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);
|
||||
|
|
Loading…
Add table
Reference in a new issue