##// END OF EJS Templates
dirstate-tree: Add `WithBasename` wrapper for `HgPath`...
Simon Sapin -
r47866:3c11c24b default
parent child Browse files
Show More
@@ -0,0 +1,159 b''
1 use crate::utils::hg_path::HgPath;
2 use std::borrow::Borrow;
3
4 /// Wraps `HgPath` or `HgPathBuf` to make it behave "as" its last path
5 /// component, a.k.a. its base name (as in Python’s `os.path.basename`), but
6 /// also allow recovering the full path.
7 ///
8 /// "Behaving as" means that equality and comparison consider only the base
9 /// name, and `std::borrow::Borrow` is implemented to return only the base
10 /// name. This allows using the base name as a map key while still being able
11 /// to recover the full path, in a single memory allocation.
12 #[derive(Debug)]
13 pub struct WithBasename<T> {
14 full_path: T,
15
16 /// The position after the last slash separator in `full_path`, or `0`
17 /// if there is no slash.
18 base_name_start: usize,
19 }
20
21 impl<T> WithBasename<T> {
22 pub fn full_path(&self) -> &T {
23 &self.full_path
24 }
25 }
26
27 impl<T: AsRef<HgPath>> WithBasename<T> {
28 pub fn new(full_path: T) -> Self {
29 let base_name_start = if let Some(last_slash_position) = full_path
30 .as_ref()
31 .as_bytes()
32 .iter()
33 .rposition(|&byte| byte == b'/')
34 {
35 last_slash_position + 1
36 } else {
37 0
38 };
39 Self {
40 base_name_start,
41 full_path,
42 }
43 }
44
45 pub fn base_name(&self) -> &HgPath {
46 HgPath::new(
47 &self.full_path.as_ref().as_bytes()[self.base_name_start..],
48 )
49 }
50 }
51
52 impl<T: AsRef<HgPath>> Borrow<HgPath> for WithBasename<T> {
53 fn borrow(&self) -> &HgPath {
54 self.base_name()
55 }
56 }
57
58 impl<T: AsRef<HgPath> + PartialEq> PartialEq for WithBasename<T> {
59 fn eq(&self, other: &Self) -> bool {
60 self.base_name() == other.base_name()
61 }
62 }
63
64 impl<T: AsRef<HgPath> + Eq> Eq for WithBasename<T> {}
65
66 impl<T: AsRef<HgPath> + PartialOrd> PartialOrd for WithBasename<T> {
67 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
68 self.base_name().partial_cmp(other.base_name())
69 }
70 }
71
72 impl<T: AsRef<HgPath> + Ord> Ord for WithBasename<T> {
73 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
74 self.base_name().cmp(other.base_name())
75 }
76 }
77
78 impl<T: ?Sized + ToOwned> WithBasename<&'_ T> {
79 pub fn to_owned(&self) -> WithBasename<T::Owned> {
80 WithBasename {
81 full_path: self.full_path.to_owned(),
82 base_name_start: self.base_name_start,
83 }
84 }
85 }
86
87 impl<'a> WithBasename<&'a HgPath> {
88 /// Returns an iterator of `WithBasename<&HgPath>` for the ancestor
89 /// directory paths of the given `path`, as well as `path` itself.
90 ///
91 /// For example, the full paths of inclusive ancestors of "a/b/c" are "a",
92 /// "a/b", and "a/b/c" in that order.
93 pub fn inclusive_ancestors_of(
94 path: &'a HgPath,
95 ) -> impl Iterator<Item = WithBasename<&'a HgPath>> {
96 let mut slash_positions =
97 path.as_bytes().iter().enumerate().filter_map(|(i, &byte)| {
98 if byte == b'/' {
99 Some(i)
100 } else {
101 None
102 }
103 });
104 let mut opt_next_component_start = Some(0);
105 std::iter::from_fn(move || {
106 opt_next_component_start.take().map(|next_component_start| {
107 if let Some(slash_pos) = slash_positions.next() {
108 opt_next_component_start = Some(slash_pos + 1);
109 Self {
110 full_path: HgPath::new(&path.as_bytes()[..slash_pos]),
111 base_name_start: next_component_start,
112 }
113 } else {
114 // Not setting `opt_next_component_start` here: there will
115 // be no iteration after this one because `.take()` set it
116 // to `None`.
117 Self {
118 full_path: path,
119 base_name_start: next_component_start,
120 }
121 }
122 })
123 })
124 }
125 }
126
127 #[test]
128 fn test() {
129 let a = WithBasename::new(HgPath::new("a").to_owned());
130 assert_eq!(&**a.full_path(), HgPath::new(b"a"));
131 assert_eq!(a.base_name(), HgPath::new(b"a"));
132
133 let cba = WithBasename::new(HgPath::new("c/b/a").to_owned());
134 assert_eq!(&**cba.full_path(), HgPath::new(b"c/b/a"));
135 assert_eq!(cba.base_name(), HgPath::new(b"a"));
136
137 assert_eq!(a, cba);
138 let borrowed: &HgPath = cba.borrow();
139 assert_eq!(borrowed, HgPath::new("a"));
140 }
141
142 #[test]
143 fn test_inclusive_ancestors() {
144 let mut iter = WithBasename::inclusive_ancestors_of(HgPath::new("a/bb/c"));
145
146 let next = iter.next().unwrap();
147 assert_eq!(*next.full_path(), HgPath::new("a"));
148 assert_eq!(next.base_name(), HgPath::new("a"));
149
150 let next = iter.next().unwrap();
151 assert_eq!(*next.full_path(), HgPath::new("a/bb"));
152 assert_eq!(next.base_name(), HgPath::new("bb"));
153
154 let next = iter.next().unwrap();
155 assert_eq!(*next.full_path(), HgPath::new("a/bb/c"));
156 assert_eq!(next.base_name(), HgPath::new("c"));
157
158 assert!(iter.next().is_none());
159 }
@@ -1,2 +1,3 b''
1 1 pub mod dirstate_map;
2 2 pub mod dispatch;
3 pub mod path_with_basename;
General Comments 0
You need to be logged in to leave comments. Login now