1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
use std::path::PathBuf;
use clap::Parser;

use crate::PathExt;

#[derive(Parser)]
#[command(version, about)]
pub struct Cli {
    #[arg(value_parser = validate_file)]
    pub file: PathBuf,

    #[arg(value_parser = validate_dir, help = "If not specified, the backup is created in the same directory as FILE.")]
    pub dir: Option<PathBuf>,

    #[arg(short, default_value_t = false, help = "Delete all backups of FILE")]
    pub delete: bool,

    #[arg(short, value_parser = clap::value_parser!(u8).range(1..),
        default_value_t = 10, help = "Number of backups to keep before pruning")]
    pub num: u8,
}

impl Cli {
    pub fn dir(&self) -> PathBuf {
        match &self.dir {
            Some(dir) => dir.clone(),
            None => self.file.parent().expect("Expected parent directory").to_path_buf().clone(),
        }
    }
}

fn validate_path(path: &str, filetype: &'static str) -> Result<PathBuf, String> {
    let path = PathBuf::from(path)
        .canonicalize()
        .map_err(|_| format!("{filetype} not found: {:?}", path))?;

    if !path.exists() {
        return Err(format!("{filetype} not found: {:?}", path))
    }
    
    Ok(path)
}

fn validate_file(path: &str) -> Result<PathBuf, String> {
    let path = validate_path(path, "File")?;
    if !path.is_file() {
        Err(format!("Source path is not a file: {:?}", path))
    } else if path.filename_str().is_none() {
        return Err(format!("Invalid source file: {:?}", path))
    } else {
        Ok(path)
    }
}

fn validate_dir(path: &str) -> Result<PathBuf, String> {
    let path = validate_path(path, "Directory")?;
    if !path.is_dir() {
        return Err(format!("Destination path is not a directory: {:?}", path))
    } else {
        Ok(path)
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_verify_cli() {
        use clap::CommandFactory;
        super::Cli::command().debug_assert()
    }
}