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    /// The path must be absolute.
59    #[must_use]
60    pub fn with_repo_path(mut self, path: impl AsRef<Path>) -> Self {
61        let path = path.as_ref();
62        self.repo_path = path.to_path_buf();
63        self.config_path = path.join(CONFIG_FILE_NAME);
64        self
65    }
66
67    /// Add one path to crypt list.
68    ///
69    /// `path` may be either relative or absolute (it will be resolved against
70    /// `repo_path`). Returns an error if the path does not exist or cannot be
71    /// expressed as a repo-relative path.
72    pub fn add_one_path_to_crypt_list(&mut self, path: impl AsRef<Path>) -> Result<()> {
73        let path = path
74            .as_ref()
75            .absolutize_from(&self.repo_path)
76            .map_err(|e| Error::Other(format!("path absolutize failed: {e}")))?;
77        debug!("adding path to crypt list: {}", path.display());
78        if !path.exists() {
79            return Err(Error::PathNotExist(path.into_owned()));
80        }
81        let path_relative_to_repo = diff_paths(path.as_ref(), &self.repo_path)
82            .unwrap_or_else(|| path.to_path_buf())
83            .fuck_backslash();
84        debug!(
85            "path diff: {} to {}",
86            path.display(),
87            self.repo_path.display()
88        );
89        if path_relative_to_repo.is_absolute() {
90            return Err(Error::PathNotRelative(path_relative_to_repo));
91        }
92        info!(
93            "Add to encrypt list: {}",
94            path_relative_to_repo.display().to_string().green()
95        );
96        self.crypt_list
97            .push(path_relative_to_repo.to_string_lossy().into_owned());
98        Ok(())
99    }
100
101    /// Add the given paths to the encrypt list. This function will be called
102    /// seldomly, so it's not a performance issue.
103    pub fn add_paths_to_crypt_list(&mut self, paths: &[impl AsRef<Path>]) -> Result<()> {
104        for x in paths {
105            self.add_one_path_to_crypt_list(x.as_ref())?;
106        }
107        debug!("store config to {}", self.config_path.display());
108        self.store().map_err(|e| Error::Config(e.to_string()))
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use std::{assert, fs};
115
116    use config_file2::LoadConfigFile;
117    use tempfile::TempDir;
118
119    use super::*;
120
121    #[test]
122    fn test_add_one_file_to_crypt_list() -> crate::Result<()> {
123        let temp_dir = TempDir::new()?.keep();
124        let file_path = temp_dir.join("test.toml");
125        let mut config = Config::load_or_default(file_path)
126            .map_err(|e| Error::Config(e.to_string()))?
127            .with_repo_path(&*temp_dir);
128
129        let path_to_add = temp_dir.join("testdir");
130        fs::create_dir(&path_to_add)?;
131        config.add_one_path_to_crypt_list(path_to_add.as_os_str().to_string_lossy().as_ref())?;
132        println!("{:?}", config.crypt_list.first().unwrap());
133        assert!(
134            config
135                .repo_path
136                .join(config.crypt_list.first().unwrap())
137                .is_dir(),
138            "needs to be dir: {}",
139            config.crypt_list.first().unwrap()
140        );
141        Ok(())
142    }
143}