use std::{ default::Default, fmt, path::{Path, PathBuf}, }; pub static HELP_TEXT: &str = " Usage ----- pala new -d WIDTHxHEIGHT Options ------- -h, --help Prints help information -d, Specify dimensions for new file (default: 200x200) "; #[derive(Debug)] pub enum CliError { NoDimensions, FileAlreadyExists, FileDoesNotExist, DimensionParseError, NewRequiresFile, InvalidSubCommand(String), SubCommandParseError, } impl fmt::Display for CliError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::NoDimensions => write!(f, "no dimensions provided"), Self::FileAlreadyExists => { write!(f, "cannot initialize project, file already exists") } Self::FileDoesNotExist => { write!(f, "cannot open existing project, file does not exist") } Self::DimensionParseError => write!(f, "invalid dimension string"), Self::NewRequiresFile => write!(f, "new subcommand requires file name"), Self::InvalidSubCommand(s) => write!(f, "invalid subcommand `{}`", s), Self::SubCommandParseError => write!(f, "error parsing subcommand"), } } } impl std::error::Error for CliError {} #[derive(Debug)] pub enum Config { NewProject { file_name: Option, dimensions: (u32, u32), }, ExistingProject { file_name: PathBuf, }, Help, } impl Default for Config { fn default() -> Self { Self::NewProject { file_name: None, dimensions: (200, 200), } } } pub fn parse_args() -> Result { let mut args = pico_args::Arguments::from_env(); if args.contains(["-h", "--help"]) { return Ok(Config::Help); } match args.subcommand() { Ok(cmd) => match cmd.as_deref() { Some("new") => new_project(&mut args), Some(s) => { if !Path::new(s).is_file() { return Err(CliError::FileDoesNotExist); } Ok(Config::ExistingProject { file_name: PathBuf::from(s), }) } None => Ok(Config::Help), }, _ => Err(CliError::SubCommandParseError), } } fn new_project(args: &mut pico_args::Arguments) -> Result { let file_name = args.free_from_str(); if let Ok(f) = &file_name { if Path::new(&f).is_file() { return Err(CliError::FileAlreadyExists); } } let dimensions = args .opt_value_from_fn(["-d", "--dimensions"], parse_dimensions) .map_err(|_| CliError::DimensionParseError)? .unwrap_or((200, 200)); Ok(Config::NewProject { file_name: file_name.ok(), dimensions, }) } fn parse_dimensions(input: &str) -> Result<(u32, u32), CliError> { let dimensions: Vec<&str> = input.split('x').collect(); match &dimensions[..] { [width, height] => Ok(( width .parse::() .map_err(|_| CliError::DimensionParseError)?, height .parse::() .map_err(|_| CliError::DimensionParseError)?, )), _ => Err(CliError::DimensionParseError), } }