Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T05:34:25.036Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

path.rs

264 lines · 8.2 KB · rust
1//! Relative path types for deltadb.
2//!
3//! Provides [`RelPath`] and [`RelPathBuf`] — path types that are guaranteed to be
4//! relative, normalized, and valid unicode. Internally stored in POSIX (`/`-delimited)
5//! format regardless of host platform.
6//!
7//! Adapted from Zed's `util::rel_path` module.
8
9use std::{
10    borrow::Cow,
11    path::{Path, PathBuf},
12};
13
14use crate::rel_path::RelPath;
15
16pub mod abs_path;
17pub mod rel_path;
18
19pub trait PathExt {
20    fn to_rel_path_buf(&self) -> anyhow::Result<rel_path::RelPathBuf>;
21}
22
23impl<T: AsRef<Path> + ?Sized> PathExt for T {
24    fn to_rel_path_buf(&self) -> anyhow::Result<rel_path::RelPathBuf> {
25        Ok(RelPath::new(self.as_ref(), PathStyle::local())?.into_owned())
26    }
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
30pub enum PathStyle {
31    Unix,
32    Windows,
33}
34
35impl PathStyle {
36    #[cfg(target_os = "windows")]
37    pub const fn local() -> Self {
38        PathStyle::Windows
39    }
40
41    #[cfg(not(target_os = "windows"))]
42    pub const fn local() -> Self {
43        PathStyle::Unix
44    }
45
46    #[inline]
47    pub fn primary_separator(&self) -> &'static str {
48        match self {
49            PathStyle::Unix => "/",
50            PathStyle::Windows => "\\",
51        }
52    }
53
54    pub fn separators(&self) -> &'static [&'static str] {
55        match self {
56            PathStyle::Unix => &["/"],
57            PathStyle::Windows => &["\\", "/"],
58        }
59    }
60
61    pub fn separators_ch(&self) -> &'static [char] {
62        match self {
63            PathStyle::Unix => &['/'],
64            PathStyle::Windows => &['\\', '/'],
65        }
66    }
67
68    pub fn is_absolute(&self, path_like: &str) -> bool {
69        path_like.starts_with('/')
70            || *self == PathStyle::Windows
71                && (path_like.starts_with('\\')
72                    || path_like
73                        .chars()
74                        .next()
75                        .is_some_and(|c| c.is_ascii_alphabetic())
76                        && path_like[1..]
77                            .strip_prefix(':')
78                            .is_some_and(|path| path.starts_with('/') || path.starts_with('\\')))
79    }
80
81    pub fn is_windows(&self) -> bool {
82        *self == PathStyle::Windows
83    }
84
85    pub fn is_posix(&self) -> bool {
86        *self == PathStyle::Unix
87    }
88
89    pub fn join(self, left: impl AsRef<Path>, right: impl AsRef<Path>) -> Option<String> {
90        let right = right.as_ref().to_str()?;
91        if is_absolute(right, self) {
92            return None;
93        }
94        let left = left.as_ref().to_str()?;
95        if left.is_empty() {
96            Some(right.into())
97        } else {
98            Some(format!(
99                "{left}{}{right}",
100                if left.ends_with(self.primary_separator()) {
101                    ""
102                } else {
103                    self.primary_separator()
104                }
105            ))
106        }
107    }
108
109    pub fn join_path(
110        self,
111        left: impl AsRef<Path>,
112        right: impl AsRef<Path>,
113    ) -> anyhow::Result<PathBuf> {
114        let left = left
115            .as_ref()
116            .to_str()
117            .ok_or_else(|| anyhow::anyhow!("Path contains invalid UTF-8"))?;
118        let right = right.as_ref();
119        let right_string = right
120            .to_str()
121            .ok_or_else(|| anyhow::anyhow!("Path contains invalid UTF-8"))?;
122        let joined = self
123            .join(left, right_string)
124            .ok_or_else(|| anyhow::anyhow!("Path must be relative: {right:?}"))?;
125        Ok(PathBuf::from(self.normalize(&joined)))
126    }
127
128    pub fn normalize(self, path_like: &str) -> String {
129        match self {
130            PathStyle::Windows => crate::normalize_path(Path::new(path_like))
131                .to_string_lossy()
132                .into_owned(),
133            PathStyle::Unix => {
134                let is_absolute = path_like.starts_with('/');
135                let remainder = if is_absolute {
136                    path_like.trim_start_matches('/')
137                } else {
138                    path_like
139                };
140
141                let mut components = Vec::new();
142                for component in remainder.split(self.separators_ch()) {
143                    match component {
144                        "" | "." => {}
145                        ".." => {
146                            if components
147                                .last()
148                                .is_some_and(|component| *component != "..")
149                            {
150                                components.pop();
151                            } else if !is_absolute {
152                                components.push(component);
153                            }
154                        }
155                        component => components.push(component),
156                    }
157                }
158
159                let normalized = components.join(self.primary_separator());
160                if is_absolute && normalized.is_empty() {
161                    "/".to_string()
162                } else if is_absolute {
163                    format!("/{normalized}")
164                } else {
165                    normalized
166                }
167            }
168        }
169    }
170
171    pub fn split(self, path_like: &str) -> (Option<&str>, &str) {
172        let Some(pos) = path_like.rfind(self.primary_separator()) else {
173            return (None, path_like);
174        };
175        let filename_start = pos + self.primary_separator().len();
176        (
177            Some(&path_like[..filename_start]),
178            &path_like[filename_start..],
179        )
180    }
181
182    pub fn strip_prefix<'a>(
183        &self,
184        child: &'a Path,
185        parent: &'a Path,
186    ) -> Option<std::borrow::Cow<'a, RelPath>> {
187        let parent = parent.to_str()?;
188        if parent.is_empty() {
189            return RelPath::new(child, *self).ok();
190        }
191        let parent = self
192            .separators()
193            .iter()
194            .find_map(|sep| parent.strip_suffix(sep))
195            .unwrap_or(parent);
196        let child = child.to_str()?;
197
198        // Match behavior of std::path::Path, which is case-insensitive for drive letters (e.g., "C:" == "c:")
199        let stripped = if self.is_windows()
200            && child.as_bytes().get(1) == Some(&b':')
201            && parent.as_bytes().get(1) == Some(&b':')
202            && child.as_bytes()[0].eq_ignore_ascii_case(&parent.as_bytes()[0])
203        {
204            child[2..].strip_prefix(&parent[2..])?
205        } else {
206            child.strip_prefix(parent)?
207        };
208        if let Some(relative) = self
209            .separators()
210            .iter()
211            .find_map(|sep| stripped.strip_prefix(sep))
212        {
213            RelPath::new(relative.as_ref(), *self).ok()
214        } else if stripped.is_empty() {
215            Some(Cow::Borrowed(RelPath::empty()))
216        } else {
217            None
218        }
219    }
220}
221
222fn is_absolute(path_like: &str, path_style: PathStyle) -> bool {
223    path_like.starts_with('/')
224        || path_style == PathStyle::Windows
225            && (path_like.starts_with('\\')
226                || path_like
227                    .chars()
228                    .next()
229                    .is_some_and(|c| c.is_ascii_alphabetic())
230                    && path_like[1..]
231                        .strip_prefix(':')
232                        .is_some_and(|path| path.starts_with('/') || path.starts_with('\\')))
233}
234
235/// Normalizes a path by resolving `.` and `..` components without
236/// requiring the path to exist on disk (unlike `canonicalize`).
237pub fn normalize_path(path: &Path) -> PathBuf {
238    use std::path::Component;
239    let mut components = path.components().peekable();
240    let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
241        components.next();
242        PathBuf::from(c.as_os_str())
243    } else {
244        PathBuf::new()
245    };
246
247    for component in components {
248        match component {
249            Component::Prefix(..) => unreachable!(),
250            Component::RootDir => {
251                ret.push(component.as_os_str());
252            }
253            Component::CurDir => {}
254            Component::ParentDir => {
255                ret.pop();
256            }
257            Component::Normal(c) => {
258                ret.push(c);
259            }
260        }
261    }
262    ret
263}
264
Served at tenant.openagents/omega Member data and write actions are omitted.