use std::path::{Path, PathBuf};
use clap::{Parser, Subcommand};
use config_file2::Storable;
use log::{debug, info, warn};
use crate::{
error::{Error, Result},
repo::Repo,
};
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None, after_help = r#"Examples:
git-se p # Set/update master password
git-se add file.txt mydir # Add files/folders to the encryption list
git-se e # Encrypt all files in the list
git-se d # Decrypt all files in the list
git-se e xxx.txt dir1 ... # Encrypt specific files
git-se d xxx.txt dir1 ... # Decrypt specific files
git-se i # Install a pre-commit hook to check encryption before committing
"#)]
#[clap(args_conflicts_with_subcommands = true)]
pub struct Cli {
#[command(subcommand)]
pub command: SubCommand,
#[arg(short, long, global = true)]
#[clap(value_parser = repo_path_parser, default_value = ".")]
pub repo: PathBuf,
}
fn repo_path_parser(path: &str) -> Result<PathBuf, String> {
match path_absolutize::Absolutize::absolutize(Path::new(path)) {
Ok(p) => Ok(p.into_owned()),
Err(e) => Err(e.to_string()),
}
}
#[derive(Subcommand, Debug)]
pub enum SubCommand {
#[clap(alias("e"))]
Encrypt {
paths: Vec<PathBuf>,
},
#[clap(alias("d"))]
Decrypt {
paths: Vec<PathBuf>,
},
Add { paths: Vec<PathBuf> },
Set {
#[clap(subcommand)]
field: SetField,
},
#[clap(alias("p"))]
Pwd,
#[clap(alias("c"))]
Check {
paths: Vec<PathBuf>,
#[arg(long, default_value_t = false)]
staged: bool,
},
#[clap(alias("i"))]
Install,
}
#[derive(Debug, Subcommand)]
pub enum SetField {
Key { value: String },
ZstdLevel {
#[clap(value_parser = validate_zstd_level)]
value: u8,
},
EnableZstd {
#[clap(value_parser = validate_bool)]
value: bool,
},
}
impl SetField {
pub fn set(&self, repo: &mut Repo) -> Result<()> {
match self {
Self::Key { value } => {
warn!("`set key` is deprecated, please use `pwd` or `p` instead.");
repo.set_config("key", value)?;
info!("Master key updated.");
}
Self::EnableZstd { value } => {
repo.conf.use_zstd = *value;
info!("zstd compression enabled: {value}");
}
Self::ZstdLevel { value } => {
repo.conf.zstd_level = *value;
info!("zstd compression level set to {value}");
}
}
debug!("store config to {}", repo.conf.config_path.display());
repo.conf
.store()
.map_err(|e| Error::Config(e.to_string()))?;
Ok(())
}
}
fn validate_zstd_level(value: &str) -> Result<u8, String> {
let value = value
.parse::<u8>()
.map_err(|_| "value should be a number")?;
if (1..=22_u8).contains(&value) {
Ok(value)
} else {
Err("value should be 1-22".to_string())
}
}
fn validate_bool(value: &str) -> Result<bool, String> {
match value {
"true" | "1" => Ok(true),
"false" | "0" => Ok(false),
_ => Err("value should be `true`, `false`, `1` or `0`".into()),
}
}
#[cfg(test)]
mod tests {
use assert2::assert;
use super::*;
#[test]
fn repo_path_parser_resolves_relative() {
let parsed = repo_path_parser(".").unwrap();
assert!(parsed.is_absolute());
}
}