use std::{ffi::OsString, os::unix::ffi::OsStrExt, path::PathBuf};
use anyhow::{anyhow, bail, Context, Error};
#[derive(Debug, PartialEq, Eq)]
pub struct Arguments
{
pub path: PathBuf,
pub count: u16,
pub should_profile: bool,
}
enum State
{
Options,
Count,
Finished,
}
#[allow(clippy::redundant_else, clippy::collapsible_else_if)]
pub fn parse_args<I>(args: I) -> Result<Arguments, Error>
where
I: Iterator<Item = OsString>,
{
let mut path = None;
let mut count = None;
let mut should_profile = false;
let mut state = State::Options;
for arg in args.skip(1)
{
if arg.is_empty()
{
continue;
}
match state
{
State::Options =>
{
if arg.as_bytes()[0] == b'-'
{
if arg.as_bytes() == b"-p" || arg.as_bytes() == b"--profiling"
{
should_profile = true;
}
else
{
if let Some(arg) = arg.to_str()
{
bail!("Unknown option {arg:?}");
}
else
{
bail!("Unknown option {arg:?}");
}
}
}
else
{
path = Some(PathBuf::from(arg));
state = State::Count;
}
}
State::Count =>
{
let count_unparsed = arg
.to_str()
.ok_or_else(|| anyhow!("The repetition count must be a number, not {arg:?}"))?;
count =
Some(count_unparsed.parse().with_context(|| {
format!("The number that was attempted to be parsed is {count_unparsed:?}")
})?);
state = State::Finished;
}
State::Finished => bail!("No arguments are expected after the count, but were provided"),
}
}
Ok(Arguments {
path: path.ok_or_else(|| anyhow!("No path specified"))?,
count: count.unwrap_or(1),
should_profile,
})
}
#[cfg(test)]
mod tests
{
use std::path::PathBuf;
use crate::arguments::{parse_args, Arguments};
#[test]
fn argument_parsing()
{
let tests = vec![
(
vec!["vröätver", "abc/def"],
Arguments {
path: PathBuf::from("abc/def"),
count: 1,
should_profile: false,
},
),
(
vec!["", "abc/def"],
Arguments {
path: PathBuf::from("abc/def"),
count: 1,
should_profile: false,
},
),
(
vec!["", "abc/def", "5"],
Arguments {
path: PathBuf::from("abc/def"),
count: 5,
should_profile: false,
},
),
(
vec!["", "-p", "abc/def"],
Arguments {
path: PathBuf::from("abc/def"),
count: 1,
should_profile: true,
},
),
(
vec!["zbjtzbjut", "--profiling", "abc/def"],
Arguments {
path: PathBuf::from("abc/def"),
count: 1,
should_profile: true,
},
),
(
vec!["quicklatex", "--profiling", "abc/def", "0"],
Arguments {
path: PathBuf::from("abc/def"),
count: 0,
should_profile: true,
},
),
(
vec![
"quicklatex",
"",
"",
"--profiling",
"",
"abc/def",
"",
"",
"",
"",
"",
"",
"0",
"",
"",
],
Arguments {
path: PathBuf::from("abc/def"),
count: 0,
should_profile: true,
},
),
(
vec!["", "1", "2"],
Arguments {
path: PathBuf::from("1"),
count: 2,
should_profile: false,
},
),
(
vec!["", "-p", "1", "2"],
Arguments {
path: PathBuf::from("1"),
count: 2,
should_profile: true,
},
),
];
for (input, output) in tests
{
assert_eq!(parse_args(input.into_iter().map(Into::into)).unwrap(), output);
}
}
#[test]
fn failing_argument_parsing()
{
let tests = vec![
vec![""],
vec!["", ""],
vec!["", "-p"],
vec!["", "5", "-p"],
vec!["", "-a"],
vec!["", "-p", "-a", "test"],
vec!["", "1", "2", "3"],
vec!["", "-p", "1", "2", "3"],
vec!["", "-pa", "abc/def"],
vec!["", "--p", "abc/def"],
vec!["", "--profilinga", "abc/def"],
];
for input in tests
{
assert!(parse_args(input.into_iter().map(Into::into)).is_err());
}
}
}