tor_config_path/
arti_client_paths.rs1use std::{borrow::Cow, path::PathBuf};
7
8use directories::ProjectDirs;
9use std::sync::LazyLock;
10
11use crate::{CfgPathError, CfgPathResolver};
12
13pub fn arti_client_base_resolver() -> CfgPathResolver {
39 let arti_cache = project_dirs().map(|x| Cow::Owned(x.cache_dir().to_owned()));
40 let arti_config = project_dirs().map(|x| Cow::Owned(x.config_dir().to_owned()));
41 let arti_shared_data = project_dirs().map(|x| Cow::Owned(x.data_dir().to_owned()));
42 let arti_local_data = project_dirs().map(|x| Cow::Owned(x.data_local_dir().to_owned()));
43 let program_dir = get_program_dir().map(Cow::Owned);
44 let user_home = crate::home().map(Cow::Borrowed);
45
46 let mut resolver = CfgPathResolver::default();
47
48 resolver.set_var("ARTI_CACHE", arti_cache);
49 resolver.set_var("ARTI_CONFIG", arti_config);
50 resolver.set_var("ARTI_SHARED_DATA", arti_shared_data);
51 resolver.set_var("ARTI_LOCAL_DATA", arti_local_data);
52 resolver.set_var("PROGRAM_DIR", program_dir);
53 resolver.set_var("USER_HOME", user_home);
54
55 resolver
56}
57
58fn get_program_dir() -> Result<PathBuf, CfgPathError> {
60 let binary = std::env::current_exe().map_err(|_| CfgPathError::NoProgramPath)?;
61 let directory = binary.parent().ok_or(CfgPathError::NoProgramDir)?;
62 Ok(directory.to_owned())
63}
64
65fn project_dirs() -> Result<&'static ProjectDirs, CfgPathError> {
67 static PROJECT_DIRS: LazyLock<Option<ProjectDirs>> =
69 LazyLock::new(|| ProjectDirs::from("org", "torproject", "Arti"));
70
71 PROJECT_DIRS.as_ref().ok_or(CfgPathError::NoProjectDirs)
72}
73
74#[cfg(test)]
75mod test {
76 #![allow(clippy::bool_assert_comparison)]
78 #![allow(clippy::clone_on_copy)]
79 #![allow(clippy::dbg_macro)]
80 #![allow(clippy::mixed_attributes_style)]
81 #![allow(clippy::print_stderr)]
82 #![allow(clippy::print_stdout)]
83 #![allow(clippy::single_char_pattern)]
84 #![allow(clippy::unwrap_used)]
85 #![allow(clippy::unchecked_duration_subtraction)]
86 #![allow(clippy::useless_vec)]
87 #![allow(clippy::needless_pass_by_value)]
88 use super::*;
91 use crate::CfgPath;
92
93 fn cfg_variables() -> impl IntoIterator<Item = (&'static str, PathBuf)> {
94 let list = [
95 ("ARTI_CACHE", project_dirs().unwrap().cache_dir()),
96 ("ARTI_CONFIG", project_dirs().unwrap().config_dir()),
97 ("ARTI_SHARED_DATA", project_dirs().unwrap().data_dir()),
98 ("ARTI_LOCAL_DATA", project_dirs().unwrap().data_local_dir()),
99 ("PROGRAM_DIR", &get_program_dir().unwrap()),
100 ("USER_HOME", crate::home().unwrap()),
101 ];
102
103 list.into_iter()
104 .map(|(a, b)| (a, b.to_owned()))
105 .collect::<Vec<_>>()
106 }
107
108 #[cfg(not(target_family = "windows"))]
109 #[test]
110 fn expand_variables() {
111 let path_resolver = arti_client_base_resolver();
112
113 for (var, val) in cfg_variables() {
114 let p = CfgPath::new(format!("${{{var}}}/example"));
115 assert_eq!(p.to_string(), format!("${{{var}}}/example"));
116
117 let expected = val.join("example");
118 assert_eq!(p.path(&path_resolver).unwrap().to_str(), expected.to_str());
119 }
120
121 let p = CfgPath::new("${NOT_A_REAL_VAR}/example".to_string());
122 assert!(p.path(&path_resolver).is_err());
123 }
124
125 #[cfg(target_family = "windows")]
126 #[test]
127 fn expand_variables() {
128 let path_resolver = arti_client_base_resolver();
129
130 for (var, val) in cfg_variables() {
131 let p = CfgPath::new(format!("${{{var}}}\\example"));
132 assert_eq!(p.to_string(), format!("${{{var}}}\\example"));
133
134 let expected = val.join("example");
135 assert_eq!(p.path(&path_resolver).unwrap().to_str(), expected.to_str());
136 }
137
138 let p = CfgPath::new("${NOT_A_REAL_VAR}\\example".to_string());
139 assert!(p.path(&path_resolver).is_err());
140 }
141}