use std::path::{Path, PathBuf};
use clap::{Parser, Subcommand};
use config_file2::Storable;
use log::{debug, info, warn};
use crate::repo::{GitCommand, Repo};
#[derive(Parser, Clone, 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(format!("{e}")),
}
}
impl Default for Cli {
fn default() -> Self {
Self {
command: SubCommand::Pwd,
repo: PathBuf::from("."),
}
}
}
#[derive(Subcommand, Debug, Clone)]
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>,
},
#[clap(alias("i"))]
Install,
}
#[derive(Debug, Subcommand, Clone)]
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) -> anyhow::Result<()> {
match self {
Self::Key { value } => {
warn!("`set key` is deprecated, please use `pwd` or `p` instead.");
repo.set_config("key", value)?;
info!("key set to `{value}`");
}
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()?;
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()),
}
}