dt_core/
utils.rs

1use std::path::{Path, PathBuf};
2
3use crate::error::{Error as AppError, Result};
4
5/// Gets config path from environment variables, or infer one.
6///
7/// 1. If the environment variable indexed by `env_for_file`'s value is
8/// present, that environment variable's value is returned as the config file
9/// path.
10///
11/// # Example
12///
13/// ```
14/// # use dt_core::utils::default_config_path;
15/// # use std::path::PathBuf;
16/// # use std::str::FromStr;
17/// std::env::set_var("DT_CLI_CONFIG_PATH", "/tmp/dt/configuration.toml");
18/// assert_eq!(
19///     default_config_path::<&str>("DT_CLI_CONFIG_PATH", "", &[]),
20///     Ok(PathBuf::from_str("/tmp/dt/configuration.toml").unwrap()),
21/// );
22/// ```
23///
24/// 2. Otherwise, if the environment variable indexed by `env_for_dir`'s value
25/// is present, that environment variable's value is considered the parent
26/// directory of the returned config path, filenames within `search_list` will
27/// be checked in order and the first existing file's path will be returned.
28/// If none of the `search_list` exists, a fallback filename `config.toml`
29/// will be used.
30///
31/// # Example
32///
33/// ```
34/// # use dt_core::utils::default_config_path;
35/// # use std::path::PathBuf;
36/// # use std::str::FromStr;
37/// std::env::set_var("DT_CONFIG_DIR", "/tmp/d/t");
38/// assert_eq!(
39///     default_config_path::<&str>(
40///         "some_non_existing_var",
41///         "DT_CONFIG_DIR",
42///         &[],
43///     ),
44///     Ok(PathBuf::from_str("/tmp/d/t/config.toml").unwrap()),
45/// );
46/// ```
47///
48/// 3. When neither of `env_for_file`'s and `env_for_dir`'s corresponding
49/// environment variable exists, the parent directory of returned path is
50/// inferred as `$XDG_CONFIG_HOME/dt`, or `$HOME/.config/dt` if
51/// XDG_CONFIG_HOME is not set in the runtime environment.
52///
53/// # Example
54///
55/// ```
56/// # use dt_core::utils::default_config_path;
57/// # use std::path::PathBuf;
58/// # use std::str::FromStr;
59/// std::env::set_var("XDG_CONFIG_HOME", "/tmp/confighome");
60/// assert_eq!(
61///     default_config_path::<&str>(
62///         "some_non_existing_var",
63///         "some_other_non_existing_var",
64///         &[],
65///     ),
66///     Ok(PathBuf::from_str("/tmp/confighome/dt/config.toml").unwrap()),
67/// );
68///
69/// std::env::remove_var("XDG_CONFIG_HOME");
70/// std::env::set_var("HOME", "/tmp/home");
71/// assert_eq!(
72///     default_config_path::<&str>(
73///         "some_non_existing_var",
74///         "some_other_non_existing_var",
75///         &[],
76///     ),
77///     Ok(PathBuf::from_str("/tmp/home/.config/dt/config.toml").unwrap()),
78/// );
79/// ```
80pub fn default_config_path<P: AsRef<Path>>(
81    env_for_file: &str,
82    env_for_dir: &str,
83    search_list: &[P],
84) -> Result<PathBuf> {
85    if let Ok(file_path) = std::env::var(env_for_file) {
86        log::debug!(
87            "Using config file '{}' (from environment variable `{}`)",
88            file_path,
89            env_for_file,
90        );
91        Ok(file_path.into())
92    } else {
93        let dir_path = match std::env::var(env_for_dir) {
94            Ok(dir_path) => {
95                log::debug!(
96                    "Using config directory '{}' (from environment variable `{}`)",
97                    dir_path,
98                    env_for_dir,
99                );
100                dir_path.into()
101            }
102            _ => {
103                if let Some(dir_path) = dirs::config_dir() {
104                    log::debug!("Using config directory '{}' (inferred)", dir_path.display(),);
105                    dir_path.join("dt")
106                } else {
107                    return Err(AppError::ConfigError(
108                        "Could not infer directory to config file".to_owned(),
109                    ));
110                }
111            }
112        };
113        let mut file_path = dir_path.join("config.toml");
114        for p in search_list {
115            let candidate = dir_path.join(p);
116            if candidate.exists() {
117                file_path = candidate;
118                break;
119            }
120        }
121        log::debug!("Using config file '{}' (inferred)", file_path.display());
122        Ok(file_path)
123    }
124}
125
126/// Gets the host-specific suffix, according to given [`hostname_sep`] and
127/// current machine's hostname.
128///
129/// [`hostname_sep`]: crate::config::GlobalConfig::hostname_sep
130pub fn host_specific_suffix(hostname_sep: &str) -> String {
131    hostname_sep.to_owned()
132        + gethostname::gethostname()
133            .to_str()
134            .expect("Failed getting hostname")
135}
136
137#[cfg(test)]
138pub(crate) mod testing {
139    use std::{
140        ffi::OsString, fs::Permissions, os::unix::prelude::PermissionsExt, path::PathBuf,
141        str::FromStr,
142    };
143
144    use color_eyre::Report;
145
146    const TESTROOT: &str = "/tmp/dt-testing";
147
148    pub fn get_testroot(top_level: &str) -> PathBuf {
149        PathBuf::from_str(TESTROOT).unwrap().join(top_level)
150    }
151
152    pub fn prepare_directory(abspath: PathBuf, mode: u32) -> std::result::Result<PathBuf, Report> {
153        std::fs::create_dir_all(&abspath)?;
154        std::fs::set_permissions(&abspath, Permissions::from_mode(mode))?;
155        Ok(abspath)
156    }
157
158    pub fn prepare_file(abspath: PathBuf, mode: u32) -> std::result::Result<PathBuf, Report> {
159        if let Some(parent) = abspath.parent() {
160            std::fs::create_dir_all(parent)?;
161        }
162        std::fs::write(
163            &abspath,
164            "Created by: `dt_core::syncing::tests::prepare_file`\n",
165        )?;
166        std::fs::set_permissions(&abspath, Permissions::from_mode(mode))?;
167        Ok(abspath)
168    }
169
170    pub fn gethostname() -> OsString {
171        "r2d2".into()
172    }
173
174    pub fn get_current_uid() -> users::uid_t {
175        418
176    }
177
178    pub fn get_current_username() -> Option<OsString> {
179        Some("luke".into())
180    }
181
182    pub fn linux_os_release() -> crate::error::Result<sys_info::LinuxOSReleaseInfo> {
183        let info = sys_info::LinuxOSReleaseInfo {
184            id: Some("dt".into()),
185            id_like: Some("DotfileTemplater".into()),
186            name: Some("dt".into()),
187            pretty_name: Some("DT".into()),
188            version: Some("latest".into()),
189            version_id: Some("0.99.99".into()),
190            version_codename: Some("dummy-version_codename".into()),
191            ansi_color: Some("dummy-ansi_color".into()),
192            logo: Some("Buzz Lightyear".into()),
193            cpe_name: Some("dummy-cpe_name".into()),
194            build_id: Some("#somethingsomething".into()),
195            variant: Some("dummy-variant".into()),
196            variant_id: Some("dummy-variant_id".into()),
197            home_url: Some("https://github.com/blurgyy/dt/".into()),
198            documentation_url: Some("https://dt.cli.rs/".into()),
199            support_url: Some("https://github.com/blurgyy/dt/issues".into()),
200            bug_report_url: Some("https://github.com/blurgyy/dt/issues".into()),
201            privacy_policy_url: Some(
202                "https://github.com/blurgyy/dt/blob/main/CODE_OF_CONDUCT.md".into(),
203            ),
204        };
205        Ok(info)
206    }
207}
208
209// Author: Blurgy <gy@blurgy.xyz>
210// Date:   Oct 03 2021, 02:54 [CST]