index : git-mirror-sync.git

ascending towards madness

author holly sparkles <sparkles@holly.sh> 2024-01-20 10:13:54.0 +00:00:00
committer holly sparkles <sparkles@holly.sh> 2024-01-20 10:13:54.0 +00:00:00
commit
b36a30becd0fe7397dd850f1e7f07bc6b180ba3c [patch]
tree
d53551e0b27830921d00db96a712b4a4a0a5d48b
parent
697f9429bd974ee6a6470f1d3795422dc058398f
download
b36a30becd0fe7397dd850f1e7f07bc6b180ba3c.tar.gz

feat: add proper parsing of cli args with corresponding error type



Diff

 src/error.rs | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 src/main.rs  | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++----------
 2 files changed, 140 insertions(+), 12 deletions(-)

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<DurationError> 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::<humantime::Duration>()
            .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<PathBuf, ParseError> {
    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<humantime::Duration, ParseError> {
    arg.parse::<humantime::Duration>().map_err(ParseError::from)
}