Skip to main content

git_simple_encrypt/
cli.rs

1use std::path::{Path, PathBuf};
2
3use clap::{Parser, Subcommand};
4use config_file2::Storable;
5use log::{debug, info, warn};
6
7use crate::repo::{GitCommand, Repo};
8
9#[derive(Parser, Clone, Debug)]
10#[command(author, version, about, long_about = None, after_help = r#"Examples:
11git-se p                # set password
12git-se add file.txt     # mark `file.txt` as need-to-be-crypted
13git-se e                # encrypt current repo with all marked files
14git-se d                # decrypt current repo
15git-se d 'src/*'        # decrypt all encrypted files in `src` folder
16"#)]
17#[clap(args_conflicts_with_subcommands = true)]
18pub struct Cli {
19    /// Encrypt, Decrypt and Add
20    #[command(subcommand)]
21    pub command: SubCommand,
22    /// Repository path, allow both relative and absolute path.
23    #[arg(short, long, global = true)]
24    #[clap(value_parser = repo_path_parser, default_value = ".")]
25    pub repo: PathBuf,
26}
27
28fn repo_path_parser(path: &str) -> Result<PathBuf, String> {
29    match path_absolutize::Absolutize::absolutize(Path::new(path)) {
30        Ok(p) => Ok(p.into_owned()),
31        Err(e) => Err(format!("{e}")),
32    }
33}
34
35impl Default for Cli {
36    fn default() -> Self {
37        Self {
38            command: SubCommand::Pwd,
39            repo: PathBuf::from("."),
40        }
41    }
42}
43
44#[derive(Subcommand, Debug, Clone)]
45pub enum SubCommand {
46    /// Encrypt all files with crypt attr.
47    #[clap(alias("e"))]
48    Encrypt {
49        /// The files or folders to be encrypted.
50        paths: Vec<PathBuf>,
51    },
52    /// Decrypt all files with crypt attr and `.enc` extension.
53    #[clap(alias("d"))]
54    Decrypt {
55        /// The files or folders to be decrypted.
56        paths: Vec<PathBuf>,
57    },
58    /// Mark files or folders as need-to-be-crypted.
59    Add { paths: Vec<PathBuf> },
60    /// Set key or other config items.
61    Set {
62        #[clap(subcommand)]
63        field: SetField,
64    },
65    /// Set password interactively.
66    #[clap(alias("p"))]
67    Pwd,
68}
69
70#[derive(Debug, Subcommand, Clone)]
71pub enum SetField {
72    /// Set key
73    Key { value: String },
74    /// Set zstd compression level
75    ZstdLevel {
76        #[clap(value_parser = validate_zstd_level)]
77        value: u8,
78    },
79    /// Set zstd compression enable or not
80    EnableZstd {
81        #[clap(value_parser = validate_bool)]
82        value: bool,
83    },
84}
85
86impl SetField {
87    /// Set a field.
88    ///
89    /// # Errors
90    ///
91    /// Returns an error if fail to exec git command or fail to write to config
92    /// file.
93    pub fn set(&self, repo: &mut Repo) -> anyhow::Result<()> {
94        match self {
95            Self::Key { value } => {
96                warn!("`set key` is deprecated, please use `pwd` or `p` instead.");
97                repo.set_config("key", value)?;
98                info!("key set to `{value}`");
99            }
100            Self::EnableZstd { value } => {
101                repo.conf.use_zstd = *value;
102                info!("zstd compression enabled: {value}");
103            }
104            Self::ZstdLevel { value } => {
105                repo.conf.zstd_level = *value;
106                info!("zstd compression level set to {value}");
107            }
108        }
109        debug!("store config to {}", repo.conf.config_path.display());
110        repo.conf.store()?;
111
112        Ok(())
113    }
114}
115
116fn validate_zstd_level(value: &str) -> Result<u8, String> {
117    let value = value
118        .parse::<u8>()
119        .map_err(|_| "value should be a number")?;
120    if (1..=22_u8).contains(&value) {
121        Ok(value)
122    } else {
123        Err("value should be 1-22".to_string())
124    }
125}
126
127fn validate_bool(value: &str) -> Result<bool, String> {
128    match value {
129        "true" | "1" => Ok(true),
130        "false" | "0" => Ok(false),
131        _ => Err("value should be `true`, `false`, `1` or `0`".into()),
132    }
133}