winbrew_windows\deployment\msi/
path.rs1use std::collections::HashMap;
9use std::path::{Path, PathBuf};
10
11pub(super) fn normalize_path(path: &Path) -> String {
12 let raw = path.to_string_lossy();
18 let stripped = raw
19 .strip_prefix(r"\\?\UNC\")
20 .map(|value| format!(r"\\{}", value))
21 .or_else(|| raw.strip_prefix(r"\\?\").map(ToOwned::to_owned))
22 .unwrap_or_else(|| raw.to_string());
23
24 stripped.replace('\\', "/").to_ascii_lowercase()
25}
26
27pub(super) fn normalize_registry_key_path(path: &str) -> String {
28 path.trim().to_ascii_lowercase()
30}
31
32pub(super) fn select_msi_name(value: &str) -> Option<String> {
33 let value = value.trim();
39 if value.is_empty() || value == "." {
40 return None;
41 }
42
43 let selected = match value.split_once('|') {
44 Some((short_name, long_name)) => {
45 let long_name = long_name.trim();
46 let short_name = short_name.trim();
47
48 if !long_name.is_empty() && long_name != "." {
49 long_name
50 } else if !short_name.is_empty() && short_name != "." {
51 short_name
52 } else {
53 return None;
54 }
55 }
56 None => value,
57 };
58
59 if selected.is_empty() || selected == "." {
60 None
61 } else {
62 Some(selected.to_string())
63 }
64}
65
66pub(super) fn resolve_reference_path(
67 reference: &str,
68 directory_paths: &HashMap<String, PathBuf>,
69 file_paths: &HashMap<String, PathBuf>,
70) -> Option<PathBuf> {
71 let reference = reference.trim();
77 if reference.is_empty() {
78 return None;
79 }
80
81 if let Some(key) = reference
82 .strip_prefix("[#")
83 .and_then(|value| value.strip_suffix(']'))
84 {
85 return file_paths
86 .get(key)
87 .cloned()
88 .or_else(|| directory_paths.get(key).cloned());
89 }
90
91 if let Some(rest) = reference.strip_prefix('[')
92 && let Some((key, suffix)) = rest.split_once(']')
93 {
94 let base = file_paths
95 .get(key)
96 .cloned()
97 .or_else(|| directory_paths.get(key).cloned())?;
98 let suffix = suffix.trim_start_matches(['\\', '/']);
99
100 return Some(if suffix.is_empty() {
101 base
102 } else {
103 base.join(suffix)
104 });
105 }
106
107 if let Some(path) = file_paths.get(reference) {
108 return Some(path.clone());
109 }
110
111 if let Some(path) = directory_paths.get(reference) {
112 return Some(path.clone());
113 }
114
115 if reference.contains('\\') || reference.contains('/') || reference.contains(':') {
116 return Some(PathBuf::from(reference));
117 }
118
119 None
120}
121
122#[cfg(test)]
123mod tests {
124 use super::{normalize_path, normalize_registry_key_path, select_msi_name};
125 use std::path::Path;
126
127 #[test]
128 fn select_msi_name_prefers_long_name() {
129 assert_eq!(
130 select_msi_name("SHORT|Long Name"),
131 Some("Long Name".to_string())
132 );
133 }
134
135 #[test]
136 fn select_msi_name_handles_plain_values() {
137 assert_eq!(
138 select_msi_name("FolderName"),
139 Some("FolderName".to_string())
140 );
141 assert_eq!(select_msi_name("SHORTNAM|."), Some("SHORTNAM".to_string()));
142 assert_eq!(select_msi_name("."), None);
143 assert_eq!(select_msi_name(""), None);
144 }
145
146 #[test]
147 fn normalize_path_lowercases_and_uses_forward_slashes() {
148 assert_eq!(
149 normalize_path(Path::new(r"C:\Tools\Demo\bin\App.EXE")),
150 "c:/tools/demo/bin/app.exe"
151 );
152 }
153
154 #[test]
155 fn normalize_registry_key_path_lowercases() {
156 assert_eq!(
157 normalize_registry_key_path(r"Software\Demo\Config"),
158 "software\\demo\\config"
159 );
160 }
161}