use std::path::PathBuf;
pub const BIN_NAME: &str = "tree";
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Command {
Run { dir: PathBuf },
Version,
Help,
}
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum CliError {
#[error("unexpected extra argument: {0:?}")]
ExtraArg(String),
#[error("unknown flag: {0}")]
UnknownFlag(String),
}
pub fn parse<I, S>(args: I) -> Result<Command, CliError>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let mut dir: Option<String> = None;
for arg in args {
let arg = arg.into();
match arg.as_str() {
"-V" | "--version" => return Ok(Command::Version),
"-h" | "--help" => return Ok(Command::Help),
s if s.starts_with('-') && s != "-" => return Err(CliError::UnknownFlag(arg)),
_ if dir.is_some() => return Err(CliError::ExtraArg(arg)),
_ => dir = Some(arg),
}
}
Ok(Command::Run {
dir: PathBuf::from(dir.unwrap_or_else(|| ".".to_string())),
})
}
pub fn usage() -> String {
format!(
"{BIN_NAME} — interactive directory visualizer (code, size, git)\n\
\n\
usage:\n \
{BIN_NAME} [dir] explore [dir] (default: .) through swappable lenses\n \
{BIN_NAME} -V, --version print version and build info\n \
{BIN_NAME} -h, --help print this help"
)
}
#[cfg(test)]
mod tests {
use super::*;
fn parse_args(args: &[&str]) -> Result<Command, CliError> {
parse(args.iter().map(|s| s.to_string()))
}
#[test]
fn parses_a_directory() {
assert_eq!(
parse_args(&["src"]).unwrap(),
Command::Run { dir: "src".into() }
);
}
#[test]
fn version_flags() {
assert_eq!(parse_args(&["-V"]).unwrap(), Command::Version);
assert_eq!(parse_args(&["--version"]).unwrap(), Command::Version);
}
#[test]
fn help_flags() {
assert_eq!(parse_args(&["-h"]).unwrap(), Command::Help);
assert_eq!(parse_args(&["--help"]).unwrap(), Command::Help);
}
#[test]
fn terminal_flag_wins_over_positional() {
assert_eq!(parse_args(&["src", "-V"]).unwrap(), Command::Version);
}
#[test]
fn missing_dir_defaults_to_cwd() {
assert_eq!(parse_args(&[]).unwrap(), Command::Run { dir: ".".into() });
}
#[test]
fn extra_positional_is_an_error() {
assert_eq!(
parse_args(&["a", "b"]),
Err(CliError::ExtraArg("b".to_string()))
);
}
#[test]
fn unknown_flag_is_an_error() {
assert_eq!(
parse_args(&["--nope"]),
Err(CliError::UnknownFlag("--nope".to_string()))
);
}
}