1#[derive(Clone, Debug, Default, PartialEq, Eq)]
2#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3pub struct Dir {
4 pub prefix: DirPrefix,
5 pub salt: String,
6 pub path: String,
7}
8
9#[derive(Clone, Debug, Default, PartialEq, Eq)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11pub struct CacheDir {
12 pub prefix: DirPrefix,
13 pub path: String,
14}
15
16#[derive(Clone, Debug, Default, PartialEq, Eq)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
18pub struct Include {
19 pub prefix: DirPrefix,
20 pub ignore_missing: bool,
21 pub path: String,
22}
23
24/// This element contains a directory name where will be mapped as the path 'as-path' in cached information. This is useful if the directory name is an alias (via a bind mount or symlink) to another directory in the system for which cached font information is likely to exist.
25
26/// 'salt' property affects to determine cache filename as same as [`Dir`] element.
27#[derive(Clone, Debug, Default, PartialEq, Eq)]
28#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
29pub struct RemapDir {
30 pub prefix: DirPrefix,
31 pub as_path: String,
32 pub salt: String,
33 pub path: String,
34}
35
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
38pub enum DirPrefix {
39 Default,
40 Cwd,
41 Xdg,
42 Relative,
43}
44
45pub enum PrefixBehavior {
46 Config,
47 Cwd,
48 Xdg,
49 Relative,
50}
51
52parse_enum! {
53 DirPrefix,
54 (Default, "default"),
55 (Cwd, "cwd"),
56 (Xdg, "xdg"),
57 (Relative, "relative"),
58}
59
60impl Default for DirPrefix {
61 fn default() -> Self {
62 DirPrefix::Default
63 }
64}
65
66/// Get the location to user home directory.
67///
68/// This implementation follows `FcConfigHome` function of freedesktop.org's
69/// Fontconfig library.
70#[allow(unused_mut, clippy::let_and_return)]
71fn config_home() -> Result<String, std::env::VarError> {
72 let mut home: Result = std::env::var(key:"HOME");
73
74 #[cfg(target_os = "windows")]
75 {
76 home = home.or_else(|_| std::env::var("USERPROFILE"));
77 }
78
79 home
80}
81
82/// Given a relative path to a config file, this function returns
83/// the complete file name to load.
84///
85/// This is a simplified version of `FcConfigGetFilename` from the Fontconfig
86/// library.
87fn config_get_file_name(p: &std::path::PathBuf) -> std::path::PathBuf {
88 if cfg!(target_os = "windows") {
89 // TODO: get config file path properly for Windows
90 return p.clone();
91 } else {
92 std::path::Path::new("/etc/fonts").join(path:p)
93 }
94}
95
96fn expand_tilde(path: &String) -> std::path::PathBuf {
97 let parsed_path: &Path = std::path::Path::new(path);
98 if let Ok(stripped_path: &Path) = parsed_path.strip_prefix(base:"~") {
99 let home: String = config_home().unwrap_or(default:"/".to_string());
100 std::path::Path::new(&home).join(stripped_path)
101 } else {
102 parsed_path.into()
103 }
104}
105
106macro_rules! define_calculate_path {
107 ($ty:ident, $xdg_env:expr, $xdg_fallback:expr, $default_prefix_behavior:expr) => {
108 impl $ty {
109 /// Environment variable name which used `xdg` prefix
110 pub const XDG_ENV: &'static str = $xdg_env;
111 /// Fallback path when `XDG_ENV` is not exists
112 pub const XDG_FALLBACK_PATH: &'static str = $xdg_fallback;
113 const DEFAULT_PREFIX_BEHAVIOR: PrefixBehavior = $default_prefix_behavior;
114
115 fn get_prefix_behavior(prefix: DirPrefix) -> PrefixBehavior {
116 match prefix {
117 DirPrefix::Default => Self::DEFAULT_PREFIX_BEHAVIOR,
118 DirPrefix::Cwd => PrefixBehavior::Cwd,
119 DirPrefix::Xdg => PrefixBehavior::Xdg,
120 DirPrefix::Relative => PrefixBehavior::Relative,
121 }
122 }
123
124 /// Calculate actual path
125 pub fn calculate_path<P: AsRef<std::path::Path> + ?Sized>(
126 &self,
127 config_file_path: &P,
128 ) -> std::path::PathBuf {
129 let expanded_path = expand_tilde(&self.path);
130
131 if expanded_path.is_absolute() {
132 return expanded_path;
133 }
134
135 let prefix = Self::get_prefix_behavior(self.prefix);
136
137 match prefix {
138 PrefixBehavior::Config => config_get_file_name(&expanded_path),
139 PrefixBehavior::Cwd => std::path::Path::new(".").join(expanded_path),
140 PrefixBehavior::Relative => match config_file_path.as_ref().parent() {
141 Some(parent) => parent.join(expanded_path),
142 None => std::path::Path::new(".").join(expanded_path),
143 },
144 PrefixBehavior::Xdg => {
145 let xdg_path =
146 std::env::var($xdg_env).unwrap_or_else(|_| $xdg_fallback.into());
147 expand_tilde(&xdg_path).join(expanded_path)
148 }
149 }
150 }
151 }
152 };
153}
154
155define_calculate_path!(Dir, "XDG_DATA_HOME", "~/.local/share", PrefixBehavior::Cwd);
156define_calculate_path!(CacheDir, "XDG_CACHE_HOME", "~/.cache", PrefixBehavior::Cwd);
157define_calculate_path!(
158 Include,
159 "XDG_CONFIG_HOME",
160 "~/.config",
161 PrefixBehavior::Config
162);
163define_calculate_path!(
164 RemapDir,
165 "XDG_CONFIG_HOME",
166 "~/.config",
167 PrefixBehavior::Cwd
168);
169