From b36a30becd0fe7397dd850f1e7f07bc6b180ba3c Mon Sep 17 00:00:00 2001 From: holly sparkles Date: Sat, 20 Jan 2024 11:13:54 +0100 Subject: [PATCH] feat: add proper parsing of cli args with corresponding error type --- src/error.rs | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 140 insertions(+), 12 deletions(-) create mode 100644 src/error.rs diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..d2d665a --- /dev/null +++ b/src/error.rs @@ -0,0 +1,75 @@ +use std::fmt::Display; + +use humantime::DurationError; + +/// A custom error type for parsing-related errors. +/// +/// This error is used throughout the application to represent +/// failures in parsing command line arguments. +#[derive(Debug)] +pub struct ParseError { + pub message: String, +} + +impl ParseError { + /// Creates a new `ParseError` with the given message. + /// + /// # Arguments + /// + /// - `message` - A string slice that holds the message for the error. + pub fn new(message: &str) -> ParseError { + ParseError { + message: message.into(), + } + } +} + +impl Display for ParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.message) + } +} + +impl std::error::Error for ParseError {} + +impl From for ParseError { + /// Converts a `humantime::DurationError` into a `ParseError`. + fn from(value: DurationError) -> Self { + ParseError::new(&value.to_string()) + } +} + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use super::*; + + #[test] + fn test_parse_error_new() { + let error_message = "test error"; + let error = ParseError::new(error_message); + assert_eq!(error.message, error_message); + } + + #[test] + fn test_parse_duration() { + let good_duration = "35s"; + let bad_duration = "35mi"; + + // this should pass + let first_test = humantime::parse_duration(good_duration).unwrap(); + assert_eq!(first_test, Duration::new(35, 0)); + + // this should fail + let second_test = humantime::parse_duration(bad_duration); + assert!(second_test.is_err()); + } + + #[test] + fn test_parse_error_display() { + let error_message = "display error"; + let error = ParseError::new(error_message); + assert_eq!(format!("{}", error), error_message); + } +} diff --git a/src/main.rs b/src/main.rs index c78dad9..e55302a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,35 @@ +use std::path::PathBuf; + use clap::Parser; +use humantime::Duration; use tokio::time::sleep; -#[derive(Debug, Parser, Default)] +use crate::error::ParseError; + +mod error; + +#[derive(Debug, Parser)] struct CliArgs { /// The time to wait before refreshing repositories. /// eg. 10m, 60s, etc. #[clap(short, long, default_value = "10m")] - pub refresh_time: String, + #[arg(value_parser = parse_refresh_time)] + pub refresh_time: Duration, + /// The directory to search for git repositories. + /// This must be valid, and accessible by the application. #[clap(required = true)] - pub directory: String, + #[arg(value_parser = parse_directory)] + pub directory: PathBuf, +} + +impl Default for CliArgs { + fn default() -> Self { + Self { + refresh_time: parse_refresh_time("10m").unwrap(), + directory: Default::default(), + } + } } #[tokio::main] @@ -21,13 +41,46 @@ async fn main() { async fn do_sync_task(args: CliArgs) { // TODO: sync with git2 - println!("I am awake! {}", args.directory); - - sleep( - args.refresh_time - .parse::() - .expect("Unable to convert refresh_time") - .into(), - ) - .await; + println!("I am awake! {:?}", args); + + sleep(args.refresh_time.into()).await; +} + +/// Parses the given string argument into a `PathBuf` and checks if it is a valid directory. +/// +/// # Arguments +/// +/// * `arg` - A string slice that holds the path to be parsed. +/// +/// # Returns +/// +/// This function returns `Ok(PathBuf)` if the path is a valid directory, +/// or `Err(Error)` if the path is not a valid directory. +fn parse_directory(arg: &str) -> Result { + let directory = PathBuf::from(arg); + + if directory.exists() && directory.is_dir() { + Ok(PathBuf::from(arg)) + } else { + Err(ParseError::new( + "path does not exist or is not a directory.", + )) + } +} + +/// Parses a duration string into a `humantime::Duration`. +/// +/// This function takes a duration string (e.g., "10m", "2h 15min") +/// and parses it to `humantime::Duration`. +/// +/// # Arguments +/// +/// * `arg` - A string representing the duration. +/// +/// # Returns +/// +/// A `Result` which is `Ok(humantime::Duration)` if the string is successfully parsed, +/// or `Err(ParseError)` if unsuccessful. +fn parse_refresh_time(arg: &str) -> Result { + arg.parse::().map_err(ParseError::from) } -- libgit2 1.7.2