autopulse_utils/
runtime_path.rs1use std::fmt;
2use typed_path::Utf8TypedPath;
3
4#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5pub enum RuntimePathFlavor {
6 Unix,
7 Windows,
8}
9
10#[derive(Clone, Copy, Debug)]
11pub struct RuntimePath<'a> {
12 source: &'a str,
13 inner: Utf8TypedPath<'a>,
14}
15
16#[derive(Clone, Copy)]
17enum CaseSensitivity {
18 Sensitive,
19 AsciiInsensitive,
20}
21
22impl<'a> RuntimePath<'a> {
23 pub fn new(path: &'a str) -> Self {
24 Self {
25 source: path,
26 inner: Utf8TypedPath::derive(path),
27 }
28 }
29
30 pub fn flavor(&self) -> RuntimePathFlavor {
31 if self.inner.is_unix() {
32 RuntimePathFlavor::Unix
33 } else {
34 RuntimePathFlavor::Windows
35 }
36 }
37
38 pub fn as_str(&self) -> &'a str {
39 self.source
40 }
41
42 pub fn starts_with(&self, base: RuntimePath<'_>) -> bool {
43 self.starts_with_mode(base, self.flavor().default_sensitivity())
44 }
45
46 pub fn starts_with_case_sensitive(&self, base: RuntimePath<'_>) -> bool {
47 self.starts_with_mode(base, CaseSensitivity::Sensitive)
48 }
49
50 pub fn starts_with_ascii_case_insensitive(&self, base: RuntimePath<'_>) -> bool {
51 self.starts_with_mode(base, CaseSensitivity::AsciiInsensitive)
52 }
53
54 pub fn equals(&self, other: RuntimePath<'_>) -> bool {
57 if self.flavor() != other.flavor() {
58 return false;
59 }
60
61 let sensitivity = self.flavor().default_sensitivity();
62
63 let mut self_components = self.inner.components();
64 let mut other_components = other.inner.components();
65
66 loop {
67 match (self_components.next(), other_components.next()) {
68 (Some(a), Some(b)) => {
69 if !sensitivity.components_match(a.as_str(), b.as_str()) {
70 return false;
71 }
72 }
73 (None, None) => return true,
74 _ => return false,
75 }
76 }
77 }
78
79 pub fn component_count(&self) -> usize {
80 self.inner.components().count()
81 }
82
83 pub fn normal_components(&self) -> impl DoubleEndedIterator<Item = &str> + '_ {
84 self.inner
85 .components()
86 .filter(|component| component.is_normal())
87 .map(|component| component.as_str())
88 }
89
90 pub fn file_name(&self) -> Option<&str> {
91 self.inner.file_name()
92 }
93
94 pub fn is_file(&self) -> bool {
97 self.inner.extension().is_some()
98 }
99
100 pub fn is_directory(&self) -> bool {
101 !self.is_file()
102 }
103
104 pub fn parent_or_self(&self) -> RuntimePath<'a> {
107 let source = if self.is_file() {
108 let length = self
109 .inner
110 .parent()
111 .map_or(self.source.len(), |parent| parent.as_str().len());
112 &self.source[..length]
113 } else {
114 self.source
115 };
116 RuntimePath::new(source)
117 }
118
119 fn starts_with_mode(&self, base: RuntimePath<'_>, sensitivity: CaseSensitivity) -> bool {
120 if self.flavor() != base.flavor() {
121 return false;
122 }
123
124 let mut components = self.inner.components();
125 base.inner.components().all(|base_component| {
126 components.next().is_some_and(|component| {
127 sensitivity.components_match(component.as_str(), base_component.as_str())
128 })
129 })
130 }
131}
132
133impl RuntimePathFlavor {
134 fn default_sensitivity(self) -> CaseSensitivity {
137 match self {
138 RuntimePathFlavor::Unix => CaseSensitivity::Sensitive,
139 RuntimePathFlavor::Windows => CaseSensitivity::AsciiInsensitive,
140 }
141 }
142}
143
144impl CaseSensitivity {
145 fn components_match(self, a: &str, b: &str) -> bool {
146 match self {
147 CaseSensitivity::Sensitive => a == b,
148 CaseSensitivity::AsciiInsensitive => a.eq_ignore_ascii_case(b),
149 }
150 }
151}
152
153impl fmt::Display for RuntimePath<'_> {
154 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
155 formatter.write_str(self.as_str())
156 }
157}