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