Skip to main content

dxm_init/
vcs.rs

1//! Contains code for initializing VCS repositories and files.
2
3use std::{error::Error, ffi::OsString, fmt::Display, path::Path, str::FromStr};
4
5use dxm_manifest::Manifest;
6use git2::Repository;
7
8pub const GITIGNORE_NAME: &str = ".gitignore";
9pub const TEMPLATE_EXTENSION: &str = "example";
10
11const GIT_README: &str = include_str!("../templates/git/README.md");
12const ROOT_GITIGNORE: &str = include_str!("../templates/git/root.gitignore");
13const DATA_GITIGNORE: &str = include_str!("../templates/git/data.gitignore");
14
15/// The possible version control systems to use in servers.
16#[derive(Default, Debug, PartialEq, Eq, Clone)]
17pub enum VcsOption {
18    #[default]
19    None,
20    Git,
21}
22
23#[derive(Debug)]
24pub struct ParseVcsOptionError {
25    option: String,
26}
27
28impl Display for ParseVcsOptionError {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        write!(f, "unknown vsc option {}", self.option)?;
31
32        Ok(())
33    }
34}
35
36impl Error for ParseVcsOptionError {}
37
38impl FromStr for VcsOption {
39    type Err = ParseVcsOptionError;
40
41    fn from_str(option: &str) -> Result<Self, Self::Err> {
42        match option {
43            "none" => Ok(Self::None),
44            "git" => Ok(Self::Git),
45            _ => Err(ParseVcsOptionError {
46                option: option.to_owned(),
47            }),
48        }
49    }
50}
51
52impl VcsOption {
53    /// Initialize the VCS repository and files.
54    pub fn init<P>(&self, path: P, manifest: &Manifest) -> Result<(), Box<dyn Error>>
55    where
56        P: AsRef<Path>,
57    {
58        let path = path.as_ref();
59        let data_path = manifest.server.data(path);
60
61        match self {
62            VcsOption::None => Ok(()),
63            VcsOption::Git => {
64                Repository::init(path)?;
65
66                fs_err::write(path.join(crate::README_NAME), GIT_README)?;
67                fs_err::write(path.join(GITIGNORE_NAME), ROOT_GITIGNORE)?;
68                fs_err::write(data_path.join(GITIGNORE_NAME), DATA_GITIGNORE)?;
69
70                create_template(data_path.join(crate::ENV_CFG_NAME))?;
71                create_template(data_path.join(crate::SECRETS_CFG_NAME))?;
72
73                Ok(())
74            }
75        }
76    }
77}
78
79fn create_template<P>(path: P) -> std::io::Result<u64>
80where
81    P: AsRef<Path>,
82{
83    let path = path.as_ref();
84    let name = path.file_name().ok_or_else(|| {
85        std::io::Error::new(
86            std::io::ErrorKind::InvalidFilename,
87            "template path has no filename",
88        )
89    })?;
90
91    let mut new_name = OsString::with_capacity(name.len() + TEMPLATE_EXTENSION.len() + 1);
92    new_name.push(name);
93    new_name.push(".");
94    new_name.push(TEMPLATE_EXTENSION);
95
96    let dest = path.with_file_name(new_name);
97    fs_err::copy(path, dest)
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn parses_returns_value_when_valid() {
106        assert_eq!(VcsOption::from_str("none").unwrap(), VcsOption::None);
107    }
108
109    #[test]
110    #[should_panic]
111    fn parse_returns_error_when_invalid() {
112        VcsOption::from_str("").unwrap();
113    }
114}