arca/
lib.rs

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    /** @deprecated Prefer Path::empty() */
164    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                    // Do nothing
701                } 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        // Test single file at root
1200        let path1 = Path { path: "/file.txt".to_string() };
1201        assert_eq!(path1.relative_to(&root), Path::from("file.txt"));
1202        
1203        // Test directory at root
1204        let path2 = Path { path: "/usr".to_string() };
1205        assert_eq!(path2.relative_to(&root), Path::from("usr"));
1206        
1207        // Test nested path
1208        let path3 = Path { path: "/usr/local/bin".to_string() };
1209        assert_eq!(path3.relative_to(&root), Path::from("usr/local/bin"));
1210        
1211        // Test path with trailing slash
1212        let path4 = Path { path: "/usr/local/".to_string() };
1213        assert_eq!(path4.relative_to(&root), Path::from("usr/local/"));
1214        
1215        // Root relative to root should be empty path
1216        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