git_simple_encrypt/
cli.rs

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