aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Hall <hallmatthew314@gmail.com>2024-03-30 23:09:34 +1300
committerMatthew Hall <hallmatthew314@gmail.com>2024-03-30 23:09:34 +1300
commit437b8b7a17dc09cd7281d1a09d681874fb166da8 (patch)
tree51a86482d6af12f3c0503a7c9cb46eae57ca5061
parent4ef3b4a2997e11a7969ffb02e3cda6f3b9a3b651 (diff)
More robust argument parsing
-rw-r--r--src/args.rs96
-rw-r--r--src/main.rs16
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 {