1use camino::{Utf8Component, Utf8Path, Utf8PathBuf};
5
6pub fn expand_tilde(s: &str) -> Utf8PathBuf {
17 match home_dir() {
18 Some(home) => expand_tilde_with(s, &home),
19 None => Utf8PathBuf::from(s),
20 }
21}
22
23pub fn expand_tilde_with(s: &str, home: &Utf8Path) -> Utf8PathBuf {
26 if let Some(rest) = s.strip_prefix("~/").or_else(|| s.strip_prefix("~\\")) {
27 home.join(rest)
28 } else if s == "~" {
29 home.to_path_buf()
30 } else {
31 Utf8PathBuf::from(s)
32 }
33}
34
35pub fn home_dir() -> Option<Utf8PathBuf> {
37 std::env::var("HOME")
38 .ok()
39 .or_else(|| std::env::var("USERPROFILE").ok())
40 .map(Utf8PathBuf::from)
41}
42
43pub fn source_walker(source: &Utf8Path) -> ignore::WalkBuilder {
52 let mut b = ignore::WalkBuilder::new(source);
53 b.hidden(false).git_ignore(false).ignore(false);
54 b.filter_entry(|entry| entry.file_name() != ".yui");
55 b
56}
57
58pub fn mirror_into_backup(backup_root: &Utf8Path, abs_target: &Utf8Path) -> Utf8PathBuf {
66 let mut out = backup_root.to_path_buf();
67 for component in abs_target.components() {
68 match component {
69 Utf8Component::Prefix(p) => {
70 let s = p.as_str().trim_end_matches(':');
71 if !s.is_empty() {
72 out.push(s);
73 }
74 }
75 Utf8Component::RootDir | Utf8Component::CurDir => {}
76 Utf8Component::ParentDir => {}
77 Utf8Component::Normal(s) => {
78 out.push(s);
79 }
80 }
81 }
82 out
83}
84
85pub fn append_timestamp(path: &Utf8Path, ts: &str) -> Utf8PathBuf {
93 let parent = path.parent().map(Utf8PathBuf::from).unwrap_or_default();
94 let file_name = path.file_name().unwrap_or("");
95
96 let (stem, ext) = match (path.file_stem(), path.extension()) {
97 (Some(stem), Some(ext)) if !file_name.starts_with('.') => (stem, Some(ext)),
98 _ => (file_name, None),
99 };
100
101 let new_name = match ext {
102 Some(ext) => format!("{stem}_{ts}.{ext}"),
103 None => format!("{stem}_{ts}"),
104 };
105 parent.join(new_name)
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn mirror_unix_absolute() {
114 let r = mirror_into_backup(
115 Utf8Path::new("/dotfiles/.yui/backup"),
116 Utf8Path::new("/home/u/.config/foo.toml"),
117 );
118 assert_eq!(
119 r,
120 Utf8PathBuf::from("/dotfiles/.yui/backup/home/u/.config/foo.toml")
121 );
122 }
123
124 #[test]
125 fn append_with_extension() {
126 let r = append_timestamp(Utf8Path::new("a/b.yml"), "20260429_143022123");
127 assert_eq!(r, Utf8PathBuf::from("a/b_20260429_143022123.yml"));
128 }
129
130 #[test]
131 fn append_no_extension() {
132 let r = append_timestamp(Utf8Path::new("a/b"), "20260429_143022123");
133 assert_eq!(r, Utf8PathBuf::from("a/b_20260429_143022123"));
134 }
135
136 #[test]
137 fn append_dotfile() {
138 let r = append_timestamp(Utf8Path::new(".gitconfig"), "20260429_143022123");
139 assert_eq!(r, Utf8PathBuf::from(".gitconfig_20260429_143022123"));
140 }
141
142 #[test]
143 fn tilde_slash_expands() {
144 let home = Utf8Path::new("/h/u");
145 assert_eq!(
146 expand_tilde_with("~/foo", home),
147 Utf8PathBuf::from("/h/u/foo")
148 );
149 assert_eq!(
150 expand_tilde_with("~/.config/nvim", home),
151 Utf8PathBuf::from("/h/u/.config/nvim")
152 );
153 }
154
155 #[test]
156 fn tilde_backslash_expands_for_windows_input() {
157 let home = Utf8Path::new("C:/Users/u");
159 assert_eq!(
160 expand_tilde_with("~\\foo", home),
161 Utf8PathBuf::from("C:/Users/u/foo")
162 );
163 }
164
165 #[test]
166 fn lone_tilde_is_home() {
167 let home = Utf8Path::new("/h/u");
168 assert_eq!(expand_tilde_with("~", home), Utf8PathBuf::from("/h/u"));
169 }
170
171 #[test]
172 fn tilde_user_form_is_untouched() {
173 let home = Utf8Path::new("/h/u");
174 assert_eq!(
177 expand_tilde_with("~root/foo", home),
178 Utf8PathBuf::from("~root/foo")
179 );
180 }
181
182 #[test]
183 fn no_tilde_unchanged() {
184 let home = Utf8Path::new("/h/u");
185 assert_eq!(
186 expand_tilde_with("/abs/path", home),
187 Utf8PathBuf::from("/abs/path")
188 );
189 assert_eq!(
190 expand_tilde_with("rel/path", home),
191 Utf8PathBuf::from("rel/path")
192 );
193 assert_eq!(
195 expand_tilde_with("/foo/~/bar", home),
196 Utf8PathBuf::from("/foo/~/bar")
197 );
198 }
199}