1use std::fmt::{Debug, Display, Formatter};
2use std::fs::ReadDir;
3use std::io::Read;
4use std::str::FromStr;
5use std::{fs, io};
6
7use radix_trie::TrieCommon;
8
9pub mod path;
10
11#[derive(Debug)]
12pub enum ImmutableErr {
13 Immutable,
14 Io(std::io::Error),
15}
16
17pub trait OkMissing<T, E> {
18 fn ok_missing(self) -> Result<Option<T>, E>;
19}
20
21impl<T> OkMissing<T, std::io::Error> for Result<T, std::io::Error> {
22 fn ok_missing(self) -> Result<Option<T>, std::io::Error> {
23 match self {
24 Ok(value) => Ok(Some(value)),
25 Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
26 Err(err) => Err(err),
27 }
28 }
29}
30
31pub struct PathIterator<'a> {
32 components: Vec<&'a str>,
33 front_idx: usize,
34 back_idx: usize,
35 has_trailing_slash: bool,
36}
37
38impl<'a> PathIterator<'a> {
39 pub fn new(path: &'a Path) -> Self {
40 let path_str = path.as_str();
41
42 let has_leading_slash = path_str.starts_with('/');
43 let has_trailing_slash = path_str.ends_with('/') && path_str.len() > 1;
44
45 let components_path = path_str;
46 let components_path = components_path.strip_prefix('/').unwrap_or(components_path);
47 let components_path = components_path.strip_suffix('/').unwrap_or(components_path);
48
49 let mut components = Vec::new();
50
51 if has_leading_slash {
52 components.push("");
53 }
54
55 if components_path.len() > 0 {
56 components.extend(components_path.split('/'));
57 }
58
59 let front_idx = 1;
60 let back_idx = components.len();
61
62 Self {
63 components,
64 front_idx,
65 back_idx,
66 has_trailing_slash,
67 }
68 }
69}
70
71impl<'a> Iterator for PathIterator<'a> {
72 type Item = Path;
73
74 fn next(&mut self) -> Option<Self::Item> {
75 if self.front_idx > self.back_idx {
76 return None;
77 }
78
79 let front_idx = self.front_idx;
80 self.front_idx += 1;
81
82 if front_idx == 1 {
83 return Some(Path::root());
84 }
85
86 let mut path
87 = self.components[0..front_idx].join("/");
88
89 if self.has_trailing_slash {
90 path.push('/');
91 }
92
93 Some(Path::from(path))
94 }
95}
96
97impl<'a> DoubleEndedIterator for PathIterator<'a> {
98 fn next_back(&mut self) -> Option<Self::Item> {
99 if self.front_idx > self.back_idx {
100 return None;
101 }
102
103 let back_idx = self.back_idx;
104 self.back_idx -= 1;
105
106 if back_idx == 1 {
107 return Some(Path::from("/"));
108 }
109
110 let components
111 = self.components[0..back_idx].join("/");
112
113 Some(Path::from(components))
114 }
115}
116
117#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
118#[cfg_attr(feature = "bincode", derive(bincode_derive::Decode, bincode_derive::Encode))]
119#[cfg_attr(feature = "serde", derive(serde_derive::Serialize, serde_derive::Deserialize))]
120#[cfg_attr(feature = "serde", serde(transparent))]
121pub struct Path {
122 path: String,
123}
124
125impl Path {
126 pub fn temp_dir_pattern(str: &str) -> std::io::Result<Path> {
127 let index = str.find("<>")
128 .unwrap();
129
130 let before = &str[..index];
131 let after = &str[index + 2..];
132
133 let nonce = std::time::SystemTime::now()
134 .duration_since(std::time::UNIX_EPOCH)
135 .unwrap()
136 .as_nanos();
137
138 let mut dir = std::env::temp_dir().to_arca();
139 let name = format!("{}{:032x}{}", before, nonce, after);
140
141 dir.join_str(name);
142 dir.fs_create_dir_all()?;
143
144 Ok(dir)
145 }
146
147 pub fn temp_dir() -> std::io::Result<Path> {
148 Self::temp_dir_pattern("temp-<>")
149 }
150
151 pub fn current_exe() -> std::io::Result<Path> {
152 Ok(std::env::current_exe()?.to_arca())
153 }
154
155 pub fn current_dir() -> std::io::Result<Path> {
156 Ok(std::env::current_dir()?.to_arca())
157 }
158
159 pub fn home_dir() -> Result<Path, std::env::VarError> {
160 Ok(Path::from(std::env::var("HOME")?))
161 }
162
163 pub fn new() -> Self {
165 Path {path: "".to_string()}
166 }
167
168 pub fn empty() -> Self {
169 Path {path: "".to_string()}
170 }
171
172 pub fn root() -> Self {
173 Path {path: "/".to_string()}
174 }
175
176 pub fn iter_path(&self) -> PathIterator {
177 PathIterator::new(self)
178 }
179
180 pub fn dirname<'a>(&'a self) -> Option<Path> {
181 let mut slice_len = self.path.len();
182 if self.path.ends_with('/') {
183 if self.path.len() > 1 {
184 slice_len -= 1;
185 } else {
186 return None;
187 }
188 }
189
190 let slice = &self.path[..slice_len];
191 if let Some(last_slash) = slice.rfind('/') {
192 if last_slash > 0 {
193 return Some(Path::from(&slice[..last_slash]));
194 } else {
195 return Some(Path::from("/"));
196 }
197 }
198
199 None
200 }
201
202 pub fn basename<'a>(&'a self) -> Option<&'a str> {
203 let has_trailing_slash = self.path.ends_with('/');
204
205 let initial_slice = if has_trailing_slash {
206 &self.path[..self.path.len() - 1]
207 } else {
208 &self.path
209 };
210
211 let first_basename_char = initial_slice
212 .rfind('/')
213 .map(|i| i + 1)
214 .unwrap_or(0);
215
216 if first_basename_char < initial_slice.len() {
217 Some(&initial_slice[first_basename_char..])
218 } else {
219 None
220 }
221 }
222
223 pub fn extname<'a>(&'a self) -> Option<&'a str> {
224 self.basename().and_then(|basename| {
225 if let Some(mut last_dot) = basename.rfind('.') {
226 if last_dot > 2 && &basename[last_dot - 2..] == ".d.ts" {
227 last_dot -= 2;
228 }
229
230 if last_dot != 0 {
231 Some(&basename[last_dot..])
232 } else {
233 None
234 }
235 } else {
236 None
237 }
238 })
239 }
240
241 pub fn as_str<'a>(&'a self) -> &'a str {
242 self.path.as_str()
243 }
244
245 pub fn to_path_buf(&self) -> std::path::PathBuf {
246 std::path::PathBuf::from(&self.path)
247 }
248
249 pub fn is_root(&self) -> bool {
250 self.path == "/"
251 }
252
253 pub fn is_absolute(&self) -> bool {
254 self.path.starts_with('/')
255 }
256
257 pub fn is_relative(&self) -> bool {
258 !self.is_absolute()
259 }
260
261 pub fn is_forward(&self) -> bool {
262 self.is_relative() && !self.is_extern()
263 }
264
265 pub fn is_extern(&self) -> bool {
266 self.path.starts_with("../") || self.path == ".."
267 }
268
269 pub fn fs_create_parent(&self) -> io::Result<&Self> {
270 if let Some(parent) = self.dirname() {
271 parent.fs_create_dir_all()?;
272 }
273
274 Ok(self)
275 }
276
277 pub fn fs_create_dir_all(&self) -> io::Result<&Self> {
278 fs::create_dir_all(&self.path)?;
279 Ok(self)
280 }
281
282 pub fn fs_create_dir(&self) -> io::Result<&Self> {
283 fs::create_dir(&self.path)?;
284 Ok(self)
285 }
286
287 pub fn fs_set_permissions(&self, permissions: fs::Permissions) -> io::Result<&Self> {
288 fs::set_permissions(&self.path, permissions)?;
289 Ok(self)
290 }
291
292 pub fn fs_metadata(&self) -> io::Result<fs::Metadata> {
293 fs::metadata(&self.path)
294 }
295
296 pub fn fs_exists(&self) -> bool {
297 self.fs_metadata().is_ok()
298 }
299
300 pub fn fs_is_file(&self) -> bool {
301 self.fs_metadata().map(|m| m.is_file()).unwrap_or(false)
302 }
303
304 pub fn fs_is_dir(&self) -> bool {
305 self.fs_metadata().map(|m| m.is_dir()).unwrap_or(false)
306 }
307
308 pub fn if_exists(&self) -> Option<Path> {
309 if self.fs_exists() {
310 Some(self.clone())
311 } else {
312 None
313 }
314 }
315
316 pub fn if_file(&self) -> Option<Path> {
317 if self.fs_is_file() {
318 Some(self.clone())
319 } else {
320 None
321 }
322 }
323
324 pub fn if_dir(&self) -> Option<Path> {
325 if self.fs_is_dir() {
326 Some(self.clone())
327 } else {
328 None
329 }
330 }
331
332 pub fn fs_read(&self) -> io::Result<Vec<u8>> {
333 fs::read(&self.to_path_buf())
334 }
335
336 pub fn fs_read_prealloc(&self) -> io::Result<Vec<u8>> {
337 let metadata = self.fs_metadata()?;
338
339 self.fs_read_with_size(metadata.len())
340 }
341
342 pub fn fs_read_with_size(&self, size: u64) -> io::Result<Vec<u8>> {
343 let mut data = Vec::with_capacity(size as usize);
344
345 let mut file = std::fs::File::open(&self.to_path_buf())?;
346 file.read_to_end(&mut data)?;
347
348 Ok(data)
349 }
350
351 pub fn fs_read_text(&self) -> io::Result<String> {
352 fs::read_to_string(self.to_path_buf())
353 }
354
355 pub fn fs_read_text_prealloc(&self) -> io::Result<String> {
356 let metadata = self.fs_metadata()?;
357
358 self.fs_read_text_with_size(metadata.len())
359 }
360
361 pub fn fs_read_text_with_size(&self, size: u64) -> io::Result<String> {
362 let mut data = String::with_capacity(size as usize);
363
364 let mut file = std::fs::File::open(&self.to_path_buf())?;
365 file.read_to_string(&mut data)?;
366
367 Ok(data)
368 }
369
370 #[cfg(feature = "tokio")]
371 pub async fn fs_read_text_async(&self) -> io::Result<String> {
372 tokio::fs::read_to_string(self.to_path_buf()).await
373 }
374
375 pub fn fs_read_dir(&self) -> io::Result<ReadDir> {
376 fs::read_dir(&self.to_path_buf())
377 }
378
379 pub fn fs_write<T: AsRef<[u8]>>(&self, data: T) -> io::Result<&Self> {
380 fs::write(self.to_path_buf(), data)?;
381 Ok(self)
382 }
383
384 pub fn fs_write_text<T: AsRef<str>>(&self, text: T) -> io::Result<&Self> {
385 fs::write(self.to_path_buf(), text.as_ref())?;
386 Ok(self)
387 }
388
389 pub fn fs_expect<T: AsRef<[u8]>>(&self, data: T, permissions: fs::Permissions) -> Result<&Self, ImmutableErr> {
390 let path_buf = self.to_path_buf();
391
392 let update_content = std::fs::read(&path_buf)
393 .ok_missing()
394 .map(|current| current.map(|current| current.ne(data.as_ref())).unwrap_or(true))
395 .map_err(ImmutableErr::Io)?;
396
397 if update_content {
398 return Err(ImmutableErr::Immutable);
399 }
400
401 let update_permissions = update_content ||
402 std::fs::metadata(&path_buf).map_err(ImmutableErr::Io)?.permissions() != permissions;
403
404 if update_permissions {
405 return Err(ImmutableErr::Immutable);
406 }
407
408 Ok(self)
409 }
410
411 pub fn fs_change<T: AsRef<[u8]>>(&self, data: T, permissions: fs::Permissions) -> io::Result<&Self> {
412 let path_buf = self.to_path_buf();
413
414 let update_content = std::fs::read(&path_buf)
415 .ok_missing()
416 .map(|current| current.map(|current| current.ne(data.as_ref())).unwrap_or(true))?;
417
418 if update_content {
419 std::fs::write(&path_buf, data)?;
420 }
421
422 let update_permissions = update_content ||
423 std::fs::metadata(&path_buf)?.permissions() != permissions;
424
425 if update_permissions {
426 std::fs::set_permissions(&path_buf, permissions)?;
427 }
428
429 Ok(self)
430 }
431
432 pub fn fs_rename(&self, new_path: &Path) -> io::Result<&Self> {
433 fs::rename(self.to_path_buf(), new_path.to_path_buf())?;
434 Ok(self)
435 }
436
437 pub fn fs_rm_file(&self) -> io::Result<&Self> {
438 fs::remove_file(self.to_path_buf())?;
439 Ok(self)
440 }
441
442 pub fn fs_rm(&self) -> io::Result<&Self> {
443 match self.fs_is_dir() {
444 true => fs::remove_dir_all(self.to_path_buf()),
445 false => fs::remove_file(self.to_path_buf()),
446 }?;
447
448 Ok(self)
449 }
450
451 pub fn without_ext(&self) -> Path {
452 self.with_ext("")
453 }
454
455 pub fn with_ext(&self, ext: &str) -> Path {
456 let mut copy = self.clone();
457 copy.set_ext(ext);
458 copy
459 }
460
461 pub fn set_ext(&mut self, ext: &str) -> &mut Self {
462 let has_trailing_slash = self.path.ends_with('/');
463
464 let initial_slice = if has_trailing_slash {
465 &self.path[..self.path.len() - 1]
466 } else {
467 &self.path
468 };
469
470 let first_basename_char = initial_slice
471 .rfind('/')
472 .map(|i| i + 1)
473 .unwrap_or(0);
474
475 let mut ext_char = self.path[first_basename_char..]
476 .rfind('.')
477 .map(|i| i + first_basename_char)
478 .unwrap_or(initial_slice.len());
479
480 if ext_char == first_basename_char {
481 ext_char = self.path.len();
482 }
483
484 if ext_char > 2 && &self.path[ext_char - 2..] == ".d.ts" {
485 ext_char -= 2;
486 }
487
488 let mut copy = self.path[..ext_char].to_string();
489 copy.push_str(ext);
490
491 if has_trailing_slash {
492 copy.push('/');
493 }
494
495 self.path = copy;
496 self
497 }
498
499 pub fn with_join(&self, other: &Path) -> Path {
500 let mut copy = self.clone();
501 copy.join(other);
502 copy
503 }
504
505 pub fn with_join_str<T>(&self, other: T) -> Path
506 where
507 T: AsRef<str>,
508 {
509 let mut copy = self.clone();
510 copy.join_str(other);
511 copy
512 }
513
514 pub fn join(&mut self, other: &Path) -> &mut Self {
515 if !other.path.is_empty() {
516 if self.path.is_empty() || other.is_absolute() {
517 self.path = other.path.clone();
518 } else {
519 if !self.path.ends_with('/') {
520 self.path.push('/');
521 }
522 self.path.push_str(&other.path);
523 self.normalize();
524 }
525 }
526
527 self
528 }
529
530 pub fn join_str<T>(&mut self, other: T) -> &mut Self
531 where
532 T: AsRef<str>,
533 {
534 self.join(&Path::from(other.as_ref()))
535 }
536
537 pub fn contains(&self, other: &Path) -> bool {
538 other.as_str().starts_with(self.as_str()) || other == self
539 }
540
541 pub fn relative_to(&self, other: &Path) -> Path {
542 assert!(self.is_absolute());
543 assert!(other.is_absolute());
544
545 let ends_with_slash = self.path.ends_with('/');
546
547 let self_components: Vec<&str> = self.path.trim_end_matches('/').split('/').collect();
548 let other_components: Vec<&str> = other.path.trim_end_matches('/').split('/').collect();
549
550 let common_prefix_length = self_components.iter()
551 .zip(other_components.iter())
552 .take_while(|(a, b)| a == b)
553 .count();
554
555 let mut relative_path = vec![];
556
557 for _ in common_prefix_length..other_components.len() {
558 if other_components[common_prefix_length..].len() > 0 {
559 relative_path.push("..");
560 }
561 }
562
563 for component in self_components[common_prefix_length..].iter() {
564 relative_path.push(*component);
565 }
566
567 if ends_with_slash {
568 relative_path.push("");
569 }
570
571 if relative_path.is_empty() {
572 Path::from(".")
573 } else {
574 Path::from(relative_path.join("/"))
575 }
576 }
577
578 fn normalize(&mut self) {
579 self.path = resolve_path(&self.path);
580 }
581}
582
583impl Debug for Path {
584 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
585 write!(f, "Path({})", self.path)
586 }
587}
588
589impl Default for Path {
590 fn default() -> Self {
591 Path::new()
592 }
593}
594
595impl FromStr for Path {
596 type Err = std::io::Error;
597
598 fn from_str(s: &str) -> Result<Self, Self::Err> {
599 Ok(Path::from(s))
600 }
601}
602
603impl<T: AsRef<str>> From<T> for Path {
604 fn from(path: T) -> Self {
605 Path {
606 path: resolve_path(path.as_ref()),
607 }
608 }
609}
610
611impl Display for Path {
612 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
613 write!(f, "{}", self.path)
614 }
615}
616
617pub trait ToArcaPath {
618 fn to_arca(&self) -> Path;
619}
620
621impl ToArcaPath for std::path::Path {
622 fn to_arca(&self) -> Path {
623 Path::from(self.to_string_lossy().to_owned())
624 }
625}
626
627impl ToArcaPath for std::path::PathBuf {
628 fn to_arca(&self) -> Path {
629 Path::from(self.to_string_lossy().to_owned())
630 }
631}
632
633#[cfg(feature = "napi")]
634impl napi::bindgen_prelude::TypeName for Path {
635 fn type_name() -> &'static str {
636 String::type_name()
637 }
638
639 fn value_type() -> napi::ValueType {
640 String::value_type()
641 }
642}
643
644#[cfg(feature = "napi")]
645impl napi::bindgen_prelude::ValidateNapiValue for Path {
646 unsafe fn validate(env: napi::sys::napi_env, napi_val: napi::sys::napi_value) -> napi::Result<napi::sys::napi_value> {
647 let mut result = -1;
648 napi::check_status!(
649 unsafe { napi::sys::napi_typeof(env, napi_val, &mut result) },
650 "Failed to detect napi value type",
651 )?;
652
653 let received_type = napi::ValueType::from(result);
654 if let Ok(validate_ret) = unsafe { String::validate(env, napi_val) } {
655 Ok(validate_ret)
656 } else {
657 Err(napi::Error::new(
658 napi::Status::InvalidArg,
659 format!(
660 "Expect value to be String, but received {}",
661 received_type
662 ),
663 ))
664 }
665 }
666}
667
668#[cfg(feature = "napi")]
669impl napi::bindgen_prelude::FromNapiValue for Path {
670 unsafe fn from_napi_value(env: napi::sys::napi_env, napi_val: napi::sys::napi_value) -> napi::Result<Self> {
671 let mut val_type = 0;
672
673 napi::check_status!(
674 unsafe { napi::sys::napi_typeof(env, napi_val, &mut val_type) },
675 "Failed to convert napi value into rust type `Path`",
676 )?;
677
678 Ok(Path::from(unsafe { String::from_napi_value(env, napi_val)? }))
679 }
680}
681
682#[cfg(feature = "napi")]
683impl napi::bindgen_prelude::ToNapiValue for Path {
684 unsafe fn to_napi_value(env: napi::sys::napi_env, val: Self) -> napi::Result<napi::sys::napi_value> {
685 unsafe { String::to_napi_value(env, val.path) }
686 }
687}
688
689fn resolve_path(input: &str) -> String {
690 if input.is_empty() {
691 return "".to_string();
692 }
693
694 let mut path = Vec::new();
695 for component in input.split('/') {
696 match component {
697 ".." => {
698 let last = path.last();
699 if last == Some(&"") {
700 } else if last != None && last != Some(&"..") {
702 path.pop();
703 } else {
704 path.push("..");
705 }
706 },
707 "." => {},
708 "" => {
709 if path.is_empty() {
710 path.push("");
711 }
712 },
713 _ => {
714 path.push(component);
715 },
716 }
717 }
718
719 if input.ends_with("/") {
720 path.push("");
721 }
722
723 if path == vec![""] {
724 return "/".to_string();
725 } else {
726 format!("{}", path.join("/"))
727 }
728}
729
730#[derive(Debug, Default, Clone)]
731pub struct Trie<T> {
732 inner: radix_trie::Trie<String, (Path, T)>,
733}
734
735impl<T> Trie<T> {
736 fn key(&self, key: &Path) -> String {
737 let mut p = key.to_string();
738
739 if !p.ends_with('/') {
740 p.push('/');
741 }
742
743 p
744 }
745
746 pub fn get(&self, key: &Path) -> Option<&T> {
747 self.inner.get(&self.key(&key)).map(|t| &t.1)
748 }
749
750 pub fn get_mut(&mut self, key: &Path) -> Option<&mut T> {
751 self.inner.get_mut(&self.key(&key)).map(|t| &mut t.1)
752 }
753
754 pub fn get_ancestor_record(&self, key: &Path) -> Option<(&String, &Path, &T)> {
755 self.inner.get_ancestor(&self.key(&key)).map(|e| {
756 let k = e.key().unwrap();
757 let v = e.value().unwrap();
758
759 (k, &v.0, &v.1)
760 })
761 }
762
763 pub fn get_ancestor_key(&self, key: &Path) -> Option<&String> {
764 self.inner.get_ancestor(&self.key(&key)).and_then(|e| e.key())
765 }
766
767 pub fn get_ancestor_path(&self, key: &Path) -> Option<&Path> {
768 self.inner.get_ancestor_value(&self.key(&key)).map(|t| &t.0)
769 }
770
771 pub fn get_ancestor_value(&self, key: &Path) -> Option<&T> {
772 self.inner.get_ancestor_value(&self.key(&key)).map(|t| &t.1)
773 }
774
775 pub fn insert(&mut self, key: Path, value: T) -> () {
776 let k = self.key(&key);
777 let p = Path::from(k.clone());
778
779 self.inner.insert(k, (p, value)).map(|t| t.1);
780 }
781
782 pub fn remove(&mut self, key: &Path) -> () {
783 self.inner.remove(&self.key(&key));
784 }
785}
786
787#[cfg(test)]
788mod tests {
789 use super::*;
790
791 #[test]
792 fn test_join() {
793 assert_eq!(Path::from("/usr/local").with_join(&Path::from("bin")), Path::from("/usr/local/bin"));
794 assert_eq!(Path::from("/usr/local").with_join(&Path::from("bin/")), Path::from("/usr/local/bin/"));
795 assert_eq!(Path::from("/usr/local/").with_join(&Path::from("bin")), Path::from("/usr/local/bin"));
796 assert_eq!(Path::from("/usr/local/").with_join(&Path::from("bin/")), Path::from("/usr/local/bin/"));
797 assert_eq!(Path::from("/usr/local").with_join(&Path::from("/bin")), Path::from("/bin"));
798 assert_eq!(Path::from("usr/local").with_join(&Path::from("bin")), Path::from("usr/local/bin"));
799 assert_eq!(Path::from("usr/local").with_join(&Path::from("bin/")), Path::from("usr/local/bin/"));
800 assert_eq!(Path::new().with_join(&Path::from("bin")), Path::from("bin"));
801 }
802
803 #[test]
804 fn test_resolve_path() {
805 assert_eq!(resolve_path("/a/b/c/./../d/"), "/a/b/d/");
806 assert_eq!(resolve_path("../foo"), "../foo");
807 assert_eq!(resolve_path("./../foo"), "../foo");
808 assert_eq!(resolve_path("/a/./b/../../c"), "/c");
809 assert_eq!(resolve_path("/a/.."), "/");
810 assert_eq!(resolve_path("/../../a"), "/a");
811 assert_eq!(resolve_path("./a/"), "a/");
812 assert_eq!(resolve_path(""), "");
813 assert_eq!(resolve_path("a/b/../../c"), "c");
814 assert_eq!(resolve_path("../a/./b/c/../../"), "../a/");
815 assert_eq!(resolve_path("/.."), "/");
816 assert_eq!(resolve_path("/."), "/");
817 assert_eq!(resolve_path("./."), "");
818 assert_eq!(resolve_path("../../../foo"), "../../../foo");
819 assert_eq!(resolve_path("./././a"), "a");
820 assert_eq!(resolve_path("b/./c/././d"), "b/c/d");
821 assert_eq!(resolve_path("foo/../../bar"), "../bar");
822 assert_eq!(resolve_path("/foo/bar/../../../baz"), "/baz");
823 }
824
825 #[test]
826 fn test_same_path() {
827 let path1 = Path { path: "/home/user/docs".to_string() };
828 let path2 = Path { path: "/home/user/docs".to_string() };
829 assert_eq!(path2.relative_to(&path1), Path::from(""));
830 }
831
832 #[test]
833 fn test_subdirectory() {
834 let path1 = Path { path: "/home/user/docs".to_string() };
835 let path2 = Path { path: "/home/user/docs/reports".to_string() };
836 assert_eq!(path2.relative_to(&path1), Path::from("reports"));
837 }
838
839 #[test]
840 fn test_subdirectory_trailing_slash() {
841 let path1 = Path { path: "/home/user/docs/".to_string() };
842 let path2 = Path { path: "/home/user/docs/reports".to_string() };
843 assert_eq!(path2.relative_to(&path1), Path::from("reports"));
844 }
845
846 #[test]
847 fn test_subdirectory_trailing_slash_subject() {
848 let path1 = Path { path: "/home/user/docs".to_string() };
849 let path2 = Path { path: "/home/user/docs/reports/".to_string() };
850 assert_eq!(path2.relative_to(&path1), Path::from("reports/"));
851 }
852
853 #[test]
854 fn test_parent_directory() {
855 let path1 = Path { path: "/home/user/docs/reports".to_string() };
856 let path2 = Path { path: "/home/user/docs".to_string() };
857 assert_eq!(path2.relative_to(&path1), Path::from(".."));
858 }
859
860 #[test]
861 fn test_different_directory() {
862 let path1 = Path { path: "/home/user/docs".to_string() };
863 let path2 = Path { path: "/home/user/music".to_string() };
864 assert_eq!(path2.relative_to(&path1), Path::from("../music"));
865 }
866
867 #[test]
868 fn test_different_root() {
869 let path1 = Path { path: "/home/user/docs".to_string() };
870 let path2 = Path { path: "/var/log".to_string() };
871 assert_eq!(path2.relative_to(&path1), Path::from("../../../var/log"));
872 }
873
874 #[test]
875 #[should_panic]
876 fn test_relative_path() {
877 let path1 = Path { path: "/home/user/docs".to_string() };
878 let path2 = Path { path: "var/log".to_string() };
879 path2.relative_to(&path1);
880 }
881
882 #[test]
883 fn test_dirname_with_extension() {
884 let path = Path { path: "/usr/local/bin/test.txt".to_string() };
885 assert_eq!(path.dirname(), Some(Path::from("/usr/local/bin")));
886 }
887
888 #[test]
889 fn test_dirname_without_extension() {
890 let path = Path { path: "/usr/local/bin/test".to_string() };
891 assert_eq!(path.dirname(), Some(Path::from("/usr/local/bin")));
892 }
893
894 #[test]
895 fn test_dirname_with_trailing_slash() {
896 let path = Path { path: "/usr/local/bin/".to_string() };
897 assert_eq!(path.dirname(), Some(Path::from("/usr/local")));
898 }
899
900 #[test]
901 fn test_dirname_with_single_slash() {
902 let path = Path { path: "/".to_string() };
903 assert_eq!(path.dirname(), None);
904 }
905
906 #[test]
907 fn test_dirname_with_root_folder() {
908 let path = Path { path: "/usr".to_string() };
909 assert_eq!(path.dirname(), Some(Path::from("/")));
910 }
911
912 #[test]
913 fn test_dirname_with_empty_string() {
914 let path = Path { path: "".to_string() };
915 assert_eq!(path.dirname(), None);
916 }
917
918 #[test]
919 fn test_basename_with_extension() {
920 let path = Path { path: "/usr/local/bin/test.txt".to_string() };
921 assert_eq!(path.basename(), Some("test.txt"));
922 }
923
924 #[test]
925 fn test_basename_without_extension() {
926 let path = Path { path: "/usr/local/bin/test".to_string() };
927 assert_eq!(path.basename(), Some("test"));
928 }
929
930 #[test]
931 fn test_basename_with_trailing_slash() {
932 let path = Path { path: "/usr/local/bin/".to_string() };
933 assert_eq!(path.basename(), Some("bin"));
934 }
935
936 #[test]
937 fn test_basename_with_single_slash() {
938 let path = Path { path: "/".to_string() };
939 assert_eq!(path.basename(), None);
940 }
941
942 #[test]
943 fn test_basename_with_empty_string() {
944 let path = Path { path: "".to_string() };
945 assert_eq!(path.basename(), None);
946 }
947
948 #[test]
949 fn test_basename_with_relative() {
950 let path = Path { path: "foo".to_string() };
951 assert_eq!(path.basename(), Some("foo"));
952 }
953
954 #[test]
955 fn test_extname_with_extension() {
956 let path = Path { path: "/usr/local/bin/test.txt".to_string() };
957 assert_eq!(path.extname(), Some(".txt"));
958 }
959
960 #[test]
961 fn test_extname_with_double_extension() {
962 let path = Path { path: "/usr/local/bin/test.foo.txt".to_string() };
963 assert_eq!(path.extname(), Some(".txt"));
964 }
965
966 #[test]
967 fn test_extname_with_d_ts() {
968 let path = Path { path: "/usr/local/bin/foo.d.ts".to_string() };
969 assert_eq!(path.extname(), Some(".d.ts"));
970 }
971
972 #[test]
973 fn test_extname_with_d_ts_out_of_range() {
974 let path = Path { path: "x.ts".to_string() };
975 assert_eq!(path.extname(), Some(".ts"));
976 }
977
978 #[test]
979 fn test_extname_without_extension() {
980 let path = Path { path: "/usr/local/bin/test".to_string() };
981 assert_eq!(path.extname(), None);
982 }
983
984 #[test]
985 fn test_extname_with_trailing_slash() {
986 let path = Path { path: "/usr/local/bin/.htaccess".to_string() };
987 assert_eq!(path.extname(), None);
988 }
989
990 #[test]
991 fn test_extname_with_single_slash() {
992 let path = Path { path: "/".to_string() };
993 assert_eq!(path.extname(), None);
994 }
995
996 #[test]
997 fn test_extname_with_empty_string() {
998 let path = Path { path: "".to_string() };
999 assert_eq!(path.extname(), None);
1000 }
1001
1002 #[test]
1003 fn test_trie_insert() {
1004 let mut trie = Trie::default();
1005 let path = Path::from("/path/to/item/");
1006 let item = "item";
1007
1008 trie.insert(path.clone(), item.to_string());
1009
1010 assert_eq!(trie.get(&path).unwrap(), item);
1011 }
1012
1013 #[test]
1014 fn test_trie_remove() {
1015 let mut trie = Trie::default();
1016 let path = Path::from("/path/to/item/");
1017 let item = "item";
1018
1019 trie.insert(path.clone(), item.to_string());
1020 assert_eq!(trie.get(&path).unwrap(), item);
1021
1022 trie.remove(&path);
1023 assert_eq!(trie.get(&path), None);
1024 }
1025
1026 #[test]
1027 fn test_get_ancestor_record() {
1028 let mut trie = Trie::default();
1029 let path = Path::from("/path/to/item/");
1030 let item = "item";
1031
1032 trie.insert(path.clone(), item.to_string());
1033
1034 let ancestor_path = Path::from("/path/to/item/child");
1035 assert_eq!(trie.get_ancestor_record(&ancestor_path).unwrap().2, item);
1036 }
1037
1038 #[test]
1039 fn test_get_ancestor_key() {
1040 let mut trie = Trie::default();
1041 let path = Path::from("/path/to/item/");
1042 let item = "item";
1043
1044 trie.insert(path.clone(), item.to_string());
1045
1046 let ancestor_path = Path::from("/path/to/item/child");
1047 assert_eq!(trie.get_ancestor_key(&ancestor_path).unwrap(), "/path/to/item/");
1048 }
1049
1050 #[test]
1051 fn test_get_ancestor_path() {
1052 let mut trie = Trie::default();
1053 let path = Path::from("/path/to/item/");
1054 let item = "item";
1055
1056 trie.insert(path.clone(), item.to_string());
1057
1058 let ancestor_path = Path::from("/path/to/item/child");
1059 assert_eq!(trie.get_ancestor_path(&ancestor_path).unwrap(), &path);
1060 }
1061
1062 #[test]
1063 fn test_get_ancestor_value() {
1064 let mut trie = Trie::default();
1065 let path = Path::from("/path/to/item/");
1066 let item = "item";
1067
1068 trie.insert(path.clone(), item.to_string());
1069
1070 let ancestor_path = Path::from("/path/to/item/child");
1071 assert_eq!(trie.get_ancestor_value(&ancestor_path).unwrap(), item);
1072 }
1073
1074 #[cfg(feature = "serde")]
1075 #[test]
1076 fn test_serde_serialization() {
1077 let path = Path::from("/usr/local/bin/test.txt");
1078 let serialized = serde_json::to_string(&path).unwrap();
1079 assert_eq!(serialized, "\"/usr/local/bin/test.txt\"");
1080
1081 let deserialized: Path = serde_json::from_str(&serialized).unwrap();
1082 assert_eq!(path, deserialized);
1083 }
1084
1085 #[test]
1086 fn test_set_ext_with_extension() {
1087 let mut path = Path { path: "/usr/local/bin/test.txt".to_string() };
1088 path.set_ext(".log");
1089 assert_eq!(path.as_str(), "/usr/local/bin/test.log");
1090 }
1091
1092 #[test]
1093 fn test_set_ext_without_extension() {
1094 let mut path = Path { path: "/usr/local/bin/test".to_string() };
1095 path.set_ext(".log");
1096 assert_eq!(path.as_str(), "/usr/local/bin/test.log");
1097 }
1098
1099 #[test]
1100 fn test_set_ext_with_empty_extension() {
1101 let mut path = Path { path: "/usr/local/bin/test.txt".to_string() };
1102 path.set_ext("");
1103 assert_eq!(path.as_str(), "/usr/local/bin/test");
1104 }
1105
1106 #[test]
1107 fn test_set_ext_with_dot_extension() {
1108 let mut path = Path { path: "/usr/local/bin/test.txt".to_string() };
1109 path.set_ext(".");
1110 assert_eq!(path.as_str(), "/usr/local/bin/test.");
1111 }
1112
1113 #[test]
1114 fn test_set_ext_with_dot_basename() {
1115 let mut path = Path { path: "/usr/local/bin/.htaccess".to_string() };
1116 path.set_ext(".log");
1117 assert_eq!(path.as_str(), "/usr/local/bin/.htaccess.log");
1118 }
1119
1120 #[test]
1121 fn test_set_ext_with_no_extension() {
1122 let mut path = Path { path: "/usr/local/bin/".to_string() };
1123 path.set_ext(".log");
1124 assert_eq!(path.as_str(), "/usr/local/bin.log/");
1125 }
1126
1127 #[test]
1128 fn test_set_ext_with_d_ts() {
1129 let mut path = Path { path: "/usr/local/bin/foo.d.ts".to_string() };
1130 path.set_ext(".log");
1131 assert_eq!(path.as_str(), "/usr/local/bin/foo.log");
1132 }
1133
1134 #[test]
1135 fn test_set_ext_with_d_ts_out_of_range() {
1136 let mut path = Path { path: "x.ts".to_string() };
1137 path.set_ext(".log");
1138 assert_eq!(path.as_str(), "x.log");
1139 }
1140
1141 #[test]
1142 fn test_set_ext_relative() {
1143 let mut path = Path { path: "test.txt".to_string() };
1144 path.set_ext(".log");
1145 assert_eq!(path.as_str(), "test.log");
1146 }
1147
1148 #[test]
1149 fn test_iter_root() {
1150 let path = Path::root();
1151 let mut iter = path.iter_path();
1152
1153 assert_eq!(iter.next(), Some(Path::from("/")));
1154 assert_eq!(iter.next(), None);
1155 }
1156
1157 #[test]
1158 fn test_iter_path() {
1159 let path = Path::from("/usr/local/bin/test.txt");
1160 let mut iter = path.iter_path();
1161
1162 assert_eq!(iter.next(), Some(Path::from("/")));
1163 assert_eq!(iter.next(), Some(Path::from("/usr")));
1164 assert_eq!(iter.next(), Some(Path::from("/usr/local")));
1165 assert_eq!(iter.next(), Some(Path::from("/usr/local/bin")));
1166 assert_eq!(iter.next(), Some(Path::from("/usr/local/bin/test.txt")));
1167 assert_eq!(iter.next(), None);
1168 }
1169
1170 #[test]
1171 fn test_iter_trailing() {
1172 let path = Path::from("/usr/local/bin/");
1173 let mut iter = path.iter_path();
1174
1175 assert_eq!(iter.next(), Some(Path::from("/")));
1176 assert_eq!(iter.next(), Some(Path::from("/usr/")));
1177 assert_eq!(iter.next(), Some(Path::from("/usr/local/")));
1178 assert_eq!(iter.next(), Some(Path::from("/usr/local/bin/")));
1179 assert_eq!(iter.next(), None);
1180 }
1181
1182 #[test]
1183 fn test_iter_path_rev() {
1184 let path = Path::from("/usr/local/bin/test.txt");
1185 let mut iter = path.iter_path().rev();
1186
1187 assert_eq!(iter.next(), Some(Path::from("/usr/local/bin/test.txt")));
1188 assert_eq!(iter.next(), Some(Path::from("/usr/local/bin")));
1189 assert_eq!(iter.next(), Some(Path::from("/usr/local")));
1190 assert_eq!(iter.next(), Some(Path::from("/usr")));
1191 assert_eq!(iter.next(), Some(Path::from("/")));
1192 assert_eq!(iter.next(), None);
1193 }
1194
1195 #[test]
1196 fn test_relative_to_root() {
1197 let root = Path { path: "/".to_string() };
1198
1199 let path1 = Path { path: "/file.txt".to_string() };
1201 assert_eq!(path1.relative_to(&root), Path::from("file.txt"));
1202
1203 let path2 = Path { path: "/usr".to_string() };
1205 assert_eq!(path2.relative_to(&root), Path::from("usr"));
1206
1207 let path3 = Path { path: "/usr/local/bin".to_string() };
1209 assert_eq!(path3.relative_to(&root), Path::from("usr/local/bin"));
1210
1211 let path4 = Path { path: "/usr/local/".to_string() };
1213 assert_eq!(path4.relative_to(&root), Path::from("usr/local/"));
1214
1215 assert_eq!(root.relative_to(&root), Path::from("."));
1217 }
1218
1219 #[test]
1220 fn test_relative_to_root_subject() {
1221 let path1 = Path { path: "/usr/local/bin".to_string() };
1222 assert_eq!(Path::root().relative_to(&path1), Path::from("../../../"));
1223 }
1224
1225}
1226