• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (C) 2024 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! A path relative to a root. Useful for paths relative to an Android repo, for example.
16 
17 use core::fmt::Display;
18 use std::path::{Path, PathBuf};
19 
20 /// Error types for the 'rooted_path' crate.
21 #[derive(thiserror::Error, Debug)]
22 pub enum Error {
23     /// Root path is not absolute
24     #[error("Root path is not absolute: {0}")]
25     RootNotAbsolute(PathBuf),
26     /// Path is not relative
27     #[error("Path is not relative: {0}")]
28     PathNotRelative(PathBuf),
29 }
30 
31 /// A path relative to a root.
32 #[derive(Debug, PartialEq, Eq, Clone)]
33 pub struct RootedPath {
34     root: PathBuf,
35     path: PathBuf,
36 }
37 
38 impl RootedPath {
39     /// Creates a new RootedPath from an absolute root and a path relative to the root.
new<P: Into<PathBuf>>(root: P, path: impl AsRef<Path>) -> Result<RootedPath, Error>40     pub fn new<P: Into<PathBuf>>(root: P, path: impl AsRef<Path>) -> Result<RootedPath, Error> {
41         let root: PathBuf = root.into();
42         if !root.is_absolute() {
43             return Err(Error::RootNotAbsolute(root));
44         }
45         let path = path.as_ref();
46         if !path.is_relative() {
47             return Err(Error::PathNotRelative(path.to_path_buf()));
48         }
49         let path = root.join(path);
50         Ok(RootedPath { root, path })
51     }
52     /// Returns the root.
root(&self) -> &Path53     pub fn root(&self) -> &Path {
54         self.root.as_path()
55     }
56     /// Returns the path relative to the root.
rel(&self) -> &Path57     pub fn rel(&self) -> &Path {
58         self.path.strip_prefix(&self.root).unwrap()
59     }
60     /// Returns the absolute path.
abs(&self) -> &Path61     pub fn abs(&self) -> &Path {
62         self.path.as_path()
63     }
64     /// Creates a new RootedPath with path adjoined to self.
join(&self, path: impl AsRef<Path>) -> Result<RootedPath, Error>65     pub fn join(&self, path: impl AsRef<Path>) -> Result<RootedPath, Error> {
66         let path = path.as_ref();
67         if !path.is_relative() {
68             return Err(Error::PathNotRelative(path.to_path_buf()));
69         }
70         Ok(RootedPath { root: self.root.clone(), path: self.path.join(path) })
71     }
72     /// Creates a new RootedPath with the same root but a new relative directory.
with_same_root(&self, path: impl AsRef<Path>) -> Result<RootedPath, Error>73     pub fn with_same_root(&self, path: impl AsRef<Path>) -> Result<RootedPath, Error> {
74         RootedPath::new(self.root.clone(), path)
75     }
76 }
77 
78 impl Display for RootedPath {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result79     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80         write!(f, "{}", self.rel().display())
81     }
82 }
83 
84 impl AsRef<Path> for RootedPath {
as_ref(&self) -> &Path85     fn as_ref(&self) -> &Path {
86         self.abs()
87     }
88 }
89 
90 impl From<RootedPath> for PathBuf {
from(val: RootedPath) -> Self91     fn from(val: RootedPath) -> Self {
92         val.path
93     }
94 }
95 
96 #[cfg(test)]
97 mod tests {
98     use super::*;
99 
100     #[test]
test_basic() -> Result<(), Error>101     fn test_basic() -> Result<(), Error> {
102         let p = RootedPath::new("/foo", "bar")?;
103         assert_eq!(p.root(), Path::new("/foo"));
104         assert_eq!(p.rel(), Path::new("bar"));
105         assert_eq!(p.abs(), PathBuf::from("/foo/bar"));
106         assert_eq!(p.join("baz")?, RootedPath::new("/foo", "bar/baz")?);
107         assert_eq!(p.with_same_root("baz")?, RootedPath::new("/foo", "baz")?);
108         Ok(())
109     }
110 
111     #[test]
test_errors() -> Result<(), Error>112     fn test_errors() -> Result<(), Error> {
113         assert!(RootedPath::new("foo", "bar").is_err());
114         assert!(RootedPath::new("/foo", "/bar").is_err());
115         let p = RootedPath::new("/foo", "bar")?;
116         assert!(p.join("/baz").is_err());
117         assert!(p.with_same_root("/baz").is_err());
118         Ok(())
119     }
120 
121     #[test]
test_conversion() -> Result<(), Error>122     fn test_conversion() -> Result<(), Error> {
123         let p = RootedPath::new("/foo", "bar")?;
124 
125         let path = p.as_ref();
126         assert_eq!(path, Path::new("/foo/bar"));
127 
128         let pathbuf: PathBuf = p.into();
129         assert_eq!(pathbuf, Path::new("/foo/bar"));
130 
131         Ok(())
132     }
133 }
134