1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Dir {
    pub prefix: DirPrefix,
    pub salt: String,
    pub path: String,
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CacheDir {
    pub prefix: DirPrefix,
    pub path: String,
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Include {
    pub prefix: DirPrefix,
    pub ignore_missing: bool,
    pub path: String,
}

/// 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.

/// 'salt' property affects to determine cache filename as same as [`Dir`] element.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RemapDir {
    pub prefix: DirPrefix,
    pub as_path: String,
    pub salt: String,
    pub path: String,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum DirPrefix {
    Default,
    Cwd,
    Xdg,
    Relative,
}

pub enum PrefixBehavior {
    Config,
    Cwd,
    Xdg,
    Relative,
}

parse_enum! {
    DirPrefix,
    (Default, "default"),
    (Cwd, "cwd"),
    (Xdg, "xdg"),
    (Relative, "relative"),
}

impl Default for DirPrefix {
    fn default() -> Self {
        DirPrefix::Default
    }
}

/// Get the location to user home directory.
///
/// This implementation follows `FcConfigHome` function of freedesktop.org's
/// Fontconfig library.
#[allow(unused_mut, clippy::let_and_return)]
fn config_home() -> Result<String, std::env::VarError> {
    let mut home = std::env::var("HOME");

    #[cfg(target_os = "windows")]
    {
        home = home.or_else(|_| std::env::var("USERPROFILE"));
    }

    home
}

/// Given a relative path to a config file, this function returns
/// the complete file name to load.
///
/// This is a simplified version of `FcConfigGetFilename` from the Fontconfig
/// library.
fn config_get_file_name(p: &std::path::PathBuf) -> std::path::PathBuf {
    if cfg!(target_os = "windows") {
        // TODO: get config file path properly for Windows
        return p.clone();
    } else {
        std::path::Path::new("/etc/fonts").join(p)
    }
}

fn expand_tilde(path: &String) -> std::path::PathBuf {
    let parsed_path = std::path::Path::new(path);
    if let Ok(stripped_path) = parsed_path.strip_prefix("~") {
        let home = config_home().unwrap_or("/".to_string());
        std::path::Path::new(&home).join(stripped_path)
    } else {
        parsed_path.into()
    }
}

macro_rules! define_calculate_path {
    ($ty:ident, $xdg_env:expr, $xdg_fallback:expr, $default_prefix_behavior:expr) => {
        impl $ty {
            /// Environment variable name which used `xdg` prefix
            pub const XDG_ENV: &'static str = $xdg_env;
            /// Fallback path when `XDG_ENV` is not exists
            pub const XDG_FALLBACK_PATH: &'static str = $xdg_fallback;
            const DEFAULT_PREFIX_BEHAVIOR: PrefixBehavior = $default_prefix_behavior;

            fn get_prefix_behavior(prefix: DirPrefix) -> PrefixBehavior {
                match prefix {
                    DirPrefix::Default => Self::DEFAULT_PREFIX_BEHAVIOR,
                    DirPrefix::Cwd => PrefixBehavior::Cwd,
                    DirPrefix::Xdg => PrefixBehavior::Xdg,
                    DirPrefix::Relative => PrefixBehavior::Relative,
                }
            }

            /// Calculate actual path
            pub fn calculate_path<P: AsRef<std::path::Path> + ?Sized>(
                &self,
                config_file_path: &P,
            ) -> std::path::PathBuf {
                let expanded_path = expand_tilde(&self.path);

                if expanded_path.is_absolute() {
                    return expanded_path;
                }

                let prefix = Self::get_prefix_behavior(self.prefix);

                match prefix {
                    PrefixBehavior::Config => config_get_file_name(&expanded_path),
                    PrefixBehavior::Cwd => std::path::Path::new(".").join(expanded_path),
                    PrefixBehavior::Relative => match config_file_path.as_ref().parent() {
                        Some(parent) => parent.join(expanded_path),
                        None => std::path::Path::new(".").join(expanded_path),
                    },
                    PrefixBehavior::Xdg => {
                        let xdg_path =
                            std::env::var($xdg_env).unwrap_or_else(|_| $xdg_fallback.into());
                        expand_tilde(&xdg_path).join(expanded_path)
                    }
                }
            }
        }
    };
}

define_calculate_path!(Dir, "XDG_DATA_HOME", "~/.local/share", PrefixBehavior::Cwd);
define_calculate_path!(CacheDir, "XDG_CACHE_HOME", "~/.cache", PrefixBehavior::Cwd);
define_calculate_path!(
    Include,
    "XDG_CONFIG_HOME",
    "~/.config",
    PrefixBehavior::Config
);
define_calculate_path!(
    RemapDir,
    "XDG_CONFIG_HOME",
    "~/.config",
    PrefixBehavior::Cwd
);