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 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 return root.parent().map(Path::to_owned);
33 } else {
34 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 let proc = std::env::current_exe()?;
82
83 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 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 components.next();
144 out_path.extend(&PathBuf::from("ext/hyperion.ng/assets/webconfig"));
145 }
146 Some("effects") => {
147 components.next();
149 out_path.extend(&PathBuf::from("ext/hyperion.ng/effects"));
150 }
151 _ => {
152 }
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}