Skip to main content

git_simple_encrypt/
config.rs

1use std::path::{Path, PathBuf};
2
3use config_file2::Storable;
4use fuck_backslash::FuckBackslash;
5use log::{debug, info};
6use path_absolutize::Absolutize as _;
7use pathdiff::diff_paths;
8use serde::{Deserialize, Serialize};
9
10use crate::{
11    error::{Error, Result},
12    utils::style::Colorize,
13};
14
15pub const CONFIG_FILE_NAME: &str = concat!(env!("CARGO_CRATE_NAME"), ".toml");
16
17#[allow(clippy::struct_field_names)]
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19pub struct Config {
20    /// **absolute path** of the repo. This config item will not be ser/de from
21    /// file; instead, it will be set by cli param.
22    #[serde(skip)]
23    pub repo_path: PathBuf,
24    /// config file path
25    #[serde(skip)]
26    pub(crate) config_path: PathBuf,
27    /// whether to use zstd
28    pub use_zstd: bool,
29    /// zstd compression level (1-22).
30    pub zstd_level: u8,
31    /// list of files (patterns) to encrypt
32    pub crypt_list: Vec<String>,
33}
34
35impl Default for Config {
36    fn default() -> Self {
37        Self {
38            repo_path: PathBuf::from("."),
39            config_path: PathBuf::from(CONFIG_FILE_NAME),
40            use_zstd: true,
41            zstd_level: 15,
42            crypt_list: vec![],
43        }
44    }
45}
46
47impl Storable for Config {
48    fn path(&self) -> impl AsRef<Path> {
49        &self.config_path
50    }
51}
52
53impl Config {
54    /// The path must be absolute.
55    pub fn new(path: impl AsRef<Path>) -> Self {
56        Self::default().with_repo_path(path)
57    }
58
59    /// The path must be absolute.
60    #[must_use]
61    pub fn with_repo_path(mut self, path: impl AsRef<Path>) -> Self {
62        let path = path.as_ref();
63        self.repo_path = path.to_path_buf();
64        self.config_path = path.join(CONFIG_FILE_NAME);
65        self
66    }
67
68    /// Add one path to crypt list.
69    ///
70    /// `path` may be either relative or absolute (it will be resolved against
71    /// `repo_path`). Returns an error if the path does not exist or cannot be
72    /// expressed as a repo-relative path.
73    pub fn add_one_path_to_crypt_list(&mut self, path: impl AsRef<Path>) -> Result<()> {
74        let path = path.as_ref().absolutize_from(&self.repo_path);
75        debug!("adding path to crypt list: {}", path.display());
76        if !path.exists() {
77            return Err(Error::PathNotExist(path.into_owned()));
78        }
79        let path_relative_to_repo = diff_paths(path.as_ref(), &self.repo_path)
80            .unwrap_or_else(|| path.to_path_buf())
81            .fuck_backslash();
82        debug!(
83            "path diff: {} to {}",
84            path.display(),
85            self.repo_path.display()
86        );
87        if path_relative_to_repo.is_absolute() {
88            return Err(Error::PathNotRelative(path_relative_to_repo));
89        }
90        info!(
91            "Add to encrypt list: {}",
92            path_relative_to_repo.display().to_string().green()
93        );
94        self.crypt_list
95            .push(path_relative_to_repo.to_string_lossy().into_owned());
96        Ok(())
97    }
98
99    /// Add the given paths to the encrypt list. This function will be called
100    /// seldomly, so it's not a performance issue.
101    pub fn add_paths_to_crypt_list(&mut self, paths: &[impl AsRef<Path>]) -> Result<()> {
102        for x in paths {
103            self.add_one_path_to_crypt_list(x.as_ref())?;
104        }
105        debug!("store config to {}", self.config_path.display());
106        self.save().map_err(|e| Error::Config(e.to_string()))
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use std::{assert, fs};
113
114    use config_file2::LoadConfigFile;
115    use tempfile::TempDir;
116
117    use super::*;
118
119    #[test]
120    fn test_add_one_file_to_crypt_list() -> Result<()> {
121        let temp_dir = TempDir::new()?.keep();
122        let file_path = temp_dir.join("test.toml");
123        let mut config = Config::load_or_default(file_path)
124            .map_err(|e| Error::Config(e.to_string()))?
125            .with_repo_path(&*temp_dir);
126
127        let path_to_add = temp_dir.join("testdir");
128        fs::create_dir(&path_to_add)?;
129        config.add_one_path_to_crypt_list(path_to_add.as_os_str().to_string_lossy().as_ref())?;
130        println!("{:?}", config.crypt_list.first().unwrap());
131        assert!(
132            config
133                .repo_path
134                .join(config.crypt_list.first().unwrap())
135                .is_dir(),
136            "needs to be dir: {}",
137            config.crypt_list.first().unwrap()
138        );
139        Ok(())
140    }
141}