hyperion/global/
paths.rs

1use std::{
2    io,
3    path::{Path, PathBuf},
4};
5
6#[derive(Debug, Clone, Copy)]
7enum ResolvedPaths {
8    Production,
9    Development,
10}
11
12const ROOT_MARKER: &str = "$ROOT";
13const SYSTEM_MARKER: &str = "$SYSTEM";
14
15#[derive(Clone)]
16pub struct Paths {
17    mode: ResolvedPaths,
18    system_root: PathBuf,
19    user_root: PathBuf,
20}
21
22impl Paths {
23    fn find_dev_root(first_root: &Path) -> Option<PathBuf> {
24        let bn = first_root.file_name().and_then(std::ffi::OsStr::to_str);
25
26        if bn == Some("release") || bn == Some("debug") || bn == Some("deps") {
27            // A Rust release dir?
28            let mut current_root = first_root.parent();
29            while let Some(root) = current_root {
30                if root.file_name().and_then(std::ffi::OsStr::to_str) == Some("target") {
31                    // We need the parent of this one
32                    return root.parent().map(Path::to_owned);
33                } else {
34                    // Keep going up
35                    current_root = root.parent();
36                }
37            }
38        }
39
40        None
41    }
42
43    fn find_bin_root(first_root: &Path) -> Option<PathBuf> {
44        let bn = first_root.file_name().and_then(std::ffi::OsStr::to_str);
45
46        if bn == Some("bin") {
47            return first_root.parent().map(|path| {
48                let mut p = path.to_owned();
49                p.push("share");
50                p.push("hyperion");
51                p
52            });
53        }
54
55        None
56    }
57
58    fn dev_user_root(user_root: Option<PathBuf>, dev_root: &Path) -> PathBuf {
59        if let Some(user_root) = user_root {
60            user_root
61        } else {
62            dev_root.to_owned()
63        }
64    }
65
66    fn prod_user_root(user_root: Option<PathBuf>) -> PathBuf {
67        if let Some(user_root) = user_root {
68            user_root
69        } else {
70            dirs::config_dir()
71                .map(|mut path| {
72                    path.push("hyperion.rs");
73                    path
74                })
75                .unwrap()
76        }
77    }
78
79    pub fn new(user_root: Option<PathBuf>) -> io::Result<Self> {
80        // Try to find the current exe
81        let proc = std::env::current_exe()?;
82
83        // Find the 2nd parent
84        let first_root = proc.parent().unwrap();
85
86        if let Some(dev_root) = Self::find_dev_root(first_root) {
87            debug!(path = %dev_root.display(), "found development root");
88
89            let user_root = Self::dev_user_root(user_root, &dev_root);
90            debug!(path = %user_root.display(), "found user root");
91
92            Ok(Self {
93                mode: ResolvedPaths::Development,
94                system_root: dev_root,
95                user_root,
96            })
97        } else if let Some(bin_root) = Self::find_bin_root(first_root) {
98            debug!(path = %bin_root.display(), "found production root");
99
100            let user_root = Self::prod_user_root(user_root);
101            debug!(path = %user_root.display(), "found user root");
102
103            Ok(Self {
104                mode: ResolvedPaths::Production,
105                system_root: bin_root,
106                user_root,
107            })
108        } else {
109            debug!(path = %first_root.display(), "no root found, using binary");
110
111            let user_root = Self::prod_user_root(user_root);
112            debug!(path = %user_root.display(), "found user root");
113
114            Ok(Self {
115                mode: ResolvedPaths::Production,
116                system_root: first_root.to_owned(),
117                user_root,
118            })
119        }
120    }
121
122    pub fn resolve_path(&self, p: impl Into<PathBuf>) -> PathBuf {
123        let p: PathBuf = p.into();
124
125        if p.is_absolute() {
126            // Don't transform absolute paths
127            trace!(path = %p.display(), "left unchanged");
128            p
129        } else {
130            let mut out_path = PathBuf::new();
131            let mut components = p.components().peekable();
132
133            if let Some(component) = components.peek() {
134                let component = component.as_os_str().to_str();
135                if component == Some(SYSTEM_MARKER) {
136                    out_path.extend(&self.system_root);
137                    components.next();
138
139                    if let ResolvedPaths::Development = self.mode {
140                        match components.peek().and_then(|cmp| cmp.as_os_str().to_str()) {
141                            Some("webconfig") => {
142                                // Webconfig mapping
143                                components.next();
144                                out_path.extend(&PathBuf::from("ext/hyperion.ng/assets/webconfig"));
145                            }
146                            Some("effects") => {
147                                // Effects mapping
148                                components.next();
149                                out_path.extend(&PathBuf::from("ext/hyperion.ng/effects"));
150                            }
151                            _ => {
152                                // No matching mapping
153                            }
154                        }
155                    }
156                } else if component == Some(ROOT_MARKER) {
157                    out_path.extend(&self.user_root);
158                    components.next();
159                }
160            }
161
162            out_path.extend(components);
163
164            trace!(src = %p.display(), dst = %out_path.display(), "remapped path");
165            out_path
166        }
167    }
168}