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.save().map_err(|e| Error::Config(e.to_string()))?;
120 Ok(())
121 }
122}
123
124fn validate_zstd_level(value: &str) -> Result<u8, String> {
125 let value = value
126 .parse::<u8>()
127 .map_err(|_| "value should be a number")?;
128 if (1..=22_u8).contains(&value) {
129 Ok(value)
130 } else {
131 Err("value should be 1-22".to_string())
132 }
133}
134
135fn validate_bool(value: &str) -> Result<bool, String> {
136 match value {
137 "true" | "1" => Ok(true),
138 "false" | "0" => Ok(false),
139 _ => Err("value should be `true`, `false`, `1` or `0`".into()),
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use assert2::assert;
146
147 use super::*;
148
149 #[test]
150 fn repo_path_parser_resolves_relative() {
151 let parsed = repo_path_parser(".").unwrap();
153 assert!(parsed.is_absolute());
154 }
155}