diff options
| author | Matthew Hall <hallmatthew314@gmail.com> | 2024-03-30 23:09:34 +1300 |
|---|---|---|
| committer | Matthew Hall <hallmatthew314@gmail.com> | 2024-03-30 23:09:34 +1300 |
| commit | 437b8b7a17dc09cd7281d1a09d681874fb166da8 (patch) | |
| tree | 51a86482d6af12f3c0503a7c9cb46eae57ca5061 | |
| parent | 4ef3b4a2997e11a7969ffb02e3cda6f3b9a3b651 (diff) | |
More robust argument parsing
| -rw-r--r-- | src/args.rs | 96 | ||||
| -rw-r--r-- | src/main.rs | 16 |
2 files changed, 90 insertions, 22 deletions
diff --git a/src/args.rs b/src/args.rs index 6753664..5e99dc5 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,4 +1,44 @@ use std::env; +use std::fmt; +use std::process; + +const UNIQUE_DEFAULT: bool = false; +const N_SENTENCES_DEFAULT: u32 = 1; +pub const HELP_STR: &str = "Usage: spout [OPTIONS] <DIRECTORY> + +Arguments: + <DIRECTORY> Path to data directory + +Options: + -n, --sentences Number of sentences to generate [default: 1] + --unique Don't re-use entries in data files (program will fail upon running out) + -h, --help Print help"; + +#[derive(Debug)] +pub enum ArgError { + NoDirectory, + UnknownArg(String), + OutOfArgs(String), + BadNumber(String), + FlagReuse(String), +} + +impl fmt::Display for ArgError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ArgError::NoDirectory => + write!(f, "no directory path provided"), + ArgError::OutOfArgs(last) => + write!(f, "expected one or more arguments after '{}'", last), + ArgError::UnknownArg(s) => + write!(f, "unknown argument: '{}'", s), + ArgError::BadNumber(s) => + write!(f, "'{}' is not a valid number", s), + ArgError::FlagReuse(s) => + write!(f, "multiple uses of the '{}' flag", s), + } + } +} pub struct Args { pub directory: String, @@ -7,41 +47,63 @@ pub struct Args { } impl Args { - pub fn parse() -> Option<Args> { + pub fn parse() -> Result<Args, ArgError> { let arg_vec: Vec<String> = env::args().collect(); let mut i = 1; //skip first arg (execution path) - let mut n_sentences = 1; - let mut unique = false; + let mut opt_n_sentences: Option<u32> = None; + let mut opt_unique: Option<bool> = None; let mut opt_directory: Option<String> = None; while i < arg_vec.len() { match arg_vec[i].as_str() { - "-n" | "--sentences" => { - if i + 1 < arg_vec.len() { - n_sentences = arg_vec[i+1].parse::<u32>().ok()?; - } else { - return None; - } - i += 2; + "-h" | "--help" => { + println!("{}", HELP_STR); + process::exit(0); }, - "--unique" => { - unique = true; - i += 1; + "-n" | "--sentences" => match opt_n_sentences { + Some(_) => { + return Err(ArgError::FlagReuse(arg_vec[i].to_string())); + }, + None => { + if i + 1 < arg_vec.len() { + // manually converting the error because it + // gives more useful information than ParseIntError + let n = arg_vec[i+1].parse::<u32>() + .map_err(|_| ArgError::BadNumber(arg_vec[i+1].to_string()))?; + opt_n_sentences = Some(n); + } else { + return Err(ArgError::OutOfArgs(arg_vec[i].to_string())); + } + i += 2; + }, + }, + "--unique" => match opt_unique { + Some(_) => { + return Err(ArgError::FlagReuse(arg_vec[i].to_string())); + }, + None => { + opt_unique = Some(true); + i += 1; + }, }, other => { opt_directory = match opt_directory { - Some(_) => { return None; }, - None => Some(other.to_string()), + Some(_) => { + return Err(ArgError::UnknownArg(other.to_string())); + }, + None => Some(other.to_string()), }; i += 1; }, } } - let directory = opt_directory?; + let directory = opt_directory.ok_or(ArgError::NoDirectory)?; + let n_sentences = opt_n_sentences.unwrap_or(N_SENTENCES_DEFAULT); + let unique = opt_unique.unwrap_or(UNIQUE_DEFAULT); - Some(Args { + Ok(Args { directory, n_sentences, unique, diff --git a/src/main.rs b/src/main.rs index 8c74322..a96f1a5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,11 +9,11 @@ use std::process; pub mod args; use rand::{thread_rng, seq::{SliceRandom, IteratorRandom}}; -use crate::args::Args; +use crate::args::{Args, ArgError, HELP_STR}; #[derive(Debug)] enum Error { - BadArguments, + BadArguments(ArgError), NoSentences, MultipleSentences, BadTemplate(String), @@ -30,8 +30,8 @@ enum Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Error::BadArguments => - write!(f, "error while parsing arguments"), + Error::BadArguments(e) => + write!(f, "Error while parsing arguments: {}\n{}", e, HELP_STR), Error::NoSentences => write!(f, "no sentences.txt file found"), Error::MultipleSentences => @@ -64,6 +64,12 @@ impl From<io::Error> for Error { } } +impl From<ArgError> for Error { + fn from(error: ArgError) -> Self { + Error::BadArguments(error) + } +} + type ProgResult<T> = Result<T, Error>; #[derive(Debug, Clone)] @@ -316,7 +322,7 @@ fn crash(e: Error) -> ! { } fn sub_main() -> ProgResult<()> { - let args = Args::parse().ok_or(Error::BadArguments)?; + let args = Args::parse()?; let (mut sentences, mut categories) = read_files(&args.directory, args.unique)?; for _i in 0..args.n_sentences { |
