Add Component and ComponentBuf

Summary:
`Component` and `ComponentBuf` are specializations of `RelativePath` and `RelativePathBuf` that do not have any separators: `/`. The main operation that is done on paths is iterating over its components. The components are generally directory names and occasionally file names.

For the path: `foo/bar/baz.txt` we have 3 components: `foo`, `bar` and `baz.txt`.

A lot of algorithms used in source control management operate on directories so having an abstraction for individual components is going to increase readability in the long run. A clear example for where we may want to use `ComponentBuf` is in the treemanifest logic where all indexing is done using components. The index in those cases must be able to own component. Writing it in terms of `RelativePathBuf` would probably be less readable that writing it in terms of `String`.

This diff also adds the `RelativePath::components` which acts as an iterator over `RelativePath` producing `Component`.

The name `Component` is inspired from `std::path::Component`.

Reviewed By: DurhamG

Differential Revision: D14001108

fbshipit-source-id: 30916d2b78fa89537fc5b30420b3b7c12d1f82c7
This commit is contained in:
Stefan Filip 2019-02-19 13:20:38 -08:00 committed by Facebook Github Bot
parent bba4a5c197
commit 65604c2a4e
2 changed files with 151 additions and 2 deletions

View File

@ -15,4 +15,4 @@ pub mod path;
pub use crate::key::Key;
pub use crate::node::Node;
pub use crate::nodeinfo::NodeInfo;
pub use crate::path::{RepoPath, RepoPathBuf};
pub use crate::path::{PathComponent, PathComponentBuf, RepoPath, RepoPathBuf};

View File

@ -16,6 +16,14 @@ pub struct RepoPathBuf(String);
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct RepoPath(str);
#[derive(Clone, Debug, Default, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct PathComponentBuf(String);
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct PathComponent(str);
const SEPARATOR: char = '/';
impl RepoPathBuf {
pub fn new() -> RepoPathBuf {
Default::default()
@ -31,7 +39,7 @@ impl RepoPathBuf {
fn append(&mut self, s: &str) {
if !self.0.is_empty() {
self.0.push('/');
self.0.push(SEPARATOR);
}
self.0.push_str(s);
}
@ -71,6 +79,10 @@ impl RepoPath {
pub fn from_str(s: &str) -> &RepoPath {
unsafe { mem::transmute(s) }
}
pub fn components(&self) -> impl Iterator<Item = &PathComponent> {
self.0.split(SEPARATOR).map(|s| PathComponent::from_str(s))
}
}
impl AsRef<RepoPath> for RepoPath {
@ -92,6 +104,73 @@ impl fmt::Display for RepoPath {
}
}
impl PathComponentBuf {
pub fn from_string(s: String) -> Self {
PathComponentBuf(s)
}
}
impl Deref for PathComponentBuf {
type Target = PathComponent;
fn deref(&self) -> &Self::Target {
unsafe { mem::transmute(&*self.0) }
}
}
impl AsRef<PathComponent> for PathComponentBuf {
fn as_ref(&self) -> &PathComponent {
self
}
}
impl Borrow<PathComponent> for PathComponentBuf {
fn borrow(&self) -> &PathComponent {
self
}
}
impl fmt::Display for PathComponentBuf {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&*self.0, formatter)
}
}
impl PathComponent {
pub fn from_utf8(s: &[u8]) -> Fallible<&PathComponent> {
let utf8_str = std::str::from_utf8(s)?;
Ok(PathComponent::from_str(utf8_str))
}
pub fn from_str(s: &str) -> &PathComponent {
unsafe { mem::transmute(s) }
}
}
impl AsRef<PathComponent> for PathComponent {
fn as_ref(&self) -> &PathComponent {
self
}
}
impl AsRef<RepoPath> for PathComponent {
fn as_ref(&self) -> &RepoPath {
RepoPath::from_str(&self.0)
}
}
impl ToOwned for PathComponent {
type Owned = PathComponentBuf;
fn to_owned(&self) -> Self::Owned {
PathComponentBuf(self.0.to_string())
}
}
impl fmt::Display for PathComponent {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0, formatter)
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -157,4 +236,74 @@ mod tests {
repo_path_buf.push(RepoPath::from_str("two"));
assert_eq!(repo_path_buf.as_ref(), RepoPath::from_str("one/two"));
}
#[test]
fn test_component_initialization_with_invalid_utf8() {
assert!(PathComponent::from_utf8(&vec![0x80, 0x80]).is_err());
}
#[test]
fn test_component_display() {
assert_eq!(
format!("{}", PathComponent::from_utf8(b"slice").unwrap()),
"slice"
);
assert_eq!(format!("{}", PathComponent::from_str("slice")), "slice");
}
#[test]
fn test_component_debug() {
assert_eq!(
format!("{:?}", PathComponent::from_utf8(b"slice").unwrap()),
"PathComponent(\"slice\")"
);
assert_eq!(
format!("{:?}", PathComponent::from_str("slice")),
"PathComponent(\"slice\")"
);
}
#[test]
fn test_componentbuf_display() {
assert_eq!(
format!("{}", PathComponentBuf::from_string(String::from("slice"))),
"slice"
);
}
#[test]
fn test_componentbuf_debug() {
assert_eq!(
format!("{:?}", PathComponentBuf::from_string(String::from("slice"))),
"PathComponentBuf(\"slice\")"
);
}
#[test]
fn test_component_conversions() {
let componentbuf = PathComponentBuf::from_string(String::from("componentbuf"));
assert_eq!(componentbuf.as_ref().to_owned(), componentbuf);
let component = PathComponent::from_str("component");
assert_eq!(component.to_owned().as_ref(), component);
}
#[test]
fn test_path_components() {
let mut iter = RepoPath::from_str("foo/bar/baz.txt").components();
assert_eq!(iter.next().unwrap(), PathComponent::from_str("foo"));
assert_eq!(iter.next().unwrap(), PathComponent::from_str("bar"));
assert_eq!(iter.next().unwrap(), PathComponent::from_str("baz.txt"));
assert!(iter.next().is_none());
}
#[test]
fn test_append_component_to_path() {
let expected = RepoPathBuf::from_string(String::from("foo/bar/baz.txt"));
let mut pathbuf = RepoPathBuf::new();
for component in expected.components() {
pathbuf.push(component);
}
assert_eq!(pathbuf, expected);
}
}