git_simple_encrypt/
cli.rs1use std::path::{Path, PathBuf};
2
3use clap::{Parser, Subcommand};
4use config_file2::Storable;
5use log::{debug, info, warn};
6
7use crate::{
8 error::{Error, Result},
9 repo::Repo,
10};
11
12#[derive(Parser, Debug)]
13#[command(author, version, about, long_about = None, after_help = r#"Examples:
14git-se p # Set/update master password
15git-se add file.txt mydir # Add files/folders to the encryption list
16git-se e # Encrypt all files in the list
17git-se d # Decrypt all files in the list
18git-se e xxx.txt dir1 ... # Encrypt specific files
19git-se d xxx.txt dir1 ... # Decrypt specific files
20git-se i # Install a pre-commit hook to check encryption before committing
21"#)]
22#[clap(args_conflicts_with_subcommands = true)]
23pub struct Cli {
24 #[command(subcommand)]
26 pub command: SubCommand,
27 #[arg(short, long, global = true)]
29 #[clap(value_parser = repo_path_parser, default_value = ".")]
30 pub repo: PathBuf,
31}
32
33fn repo_path_parser(path: &str) -> Result<PathBuf, String> {
34 match path_absolutize::Absolutize::absolutize(Path::new(path)) {
35 Ok(p) => Ok(p.into_owned()),
36 Err(e) => Err(e.to_string()),
37 }
38}
39
40#[derive(Subcommand, Debug)]
41pub enum SubCommand {
42 #[clap(alias("e"))]
44 Encrypt {
45 paths: Vec<PathBuf>,
47 },
48 #[clap(alias("d"))]
50 Decrypt {
51 paths: Vec<PathBuf>,
53 },
54 Add { paths: Vec<PathBuf> },
56 Set {
58 #[clap(subcommand)]
59 field: SetField,
60 },
61 #[clap(alias("p"))]
63 Pwd,
64 #[clap(alias("c"))]
66 Check {
67 paths: Vec<PathBuf>,
70 #[arg(long, default_value_t = false)]
72 staged: bool,
73 },
74 #[clap(alias("i"))]
76 Install,
77}
78
79#[derive(Debug, Subcommand)]
80pub enum SetField {
81 Key { value: String },
83 ZstdLevel {
85 #[clap(value_parser = validate_zstd_level)]
86 value: u8,
87 },
88 EnableZstd {
90 #[clap(value_parser = validate_bool)]
91 value: bool,
92 },
93}
94
95impl SetField {
96 pub fn set(&self, repo: &mut Repo) -> Result<()> {
103 match self {
104 Self::Key { value } => {
105 warn!("`set key` is deprecated, please use `pwd` or `p` instead.");
106 repo.set_config("key", value)?;
107 info!("Master key updated.");
108 }
109 Self::EnableZstd { value } => {
110 repo.conf.use_zstd = *value;
111 info!("zstd compression enabled: {value}");
112 }
113 Self::ZstdLevel { value } => {
114 repo.conf.zstd_level = *value;
115 info!("zstd compression level set to {value}");
116 }
117 }
118 debug!("store config to {}", repo.conf.config_path.display());
119 repo.conf
120 .store()
121 .map_err(|e| Error::Config(e.to_string()))?;
122 Ok(())
123 }
124}
125
126fn validate_zstd_level(value: &str) -> Result<u8, String> {
127 let value = value
128 .parse::<u8>()
129 .map_err(|_| "value should be a number")?;
130 if (1..=22_u8).contains(&value) {
131 Ok(value)
132 } else {
133 Err("value should be 1-22".to_string())
134 }
135}
136
137fn validate_bool(value: &str) -> Result<bool, String> {
138 match value {
139 "true" | "1" => Ok(true),
140 "false" | "0" => Ok(false),
141 _ => Err("value should be `true`, `false`, `1` or `0`".into()),
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use assert2::assert;
148
149 use super::*;
150
151 #[test]
152 fn repo_path_parser_resolves_relative() {
153 let parsed = repo_path_parser(".").unwrap();
155 assert!(parsed.is_absolute());
156 }
157}