##// END OF EJS Templates
rust-dirs-multiset: add `DirsChildrenMultiset`...
Raphaël Gomès -
r44739:0e9ac396 default
parent child Browse files
Show More
@@ -8,12 +8,15 b''
8 //! A multiset of directory names.
8 //! A multiset of directory names.
9 //!
9 //!
10 //! Used to counts the references to directories in a manifest or dirstate.
10 //! Used to counts the references to directories in a manifest or dirstate.
11 use crate::utils::hg_path::{HgPath, HgPathBuf};
12 use crate::{
11 use crate::{
13 dirstate::EntryState, utils::files, DirstateEntry, DirstateMapError,
12 dirstate::EntryState,
14 FastHashMap,
13 utils::{
14 files,
15 hg_path::{HgPath, HgPathBuf},
16 },
17 DirstateEntry, DirstateMapError, FastHashMap,
15 };
18 };
16 use std::collections::hash_map::{self, Entry};
19 use std::collections::{hash_map, hash_map::Entry, HashMap, HashSet};
17
20
18 // could be encapsulated if we care API stability more seriously
21 // could be encapsulated if we care API stability more seriously
19 pub type DirsMultisetIter<'a> = hash_map::Keys<'a, HgPathBuf, u32>;
22 pub type DirsMultisetIter<'a> = hash_map::Keys<'a, HgPathBuf, u32>;
@@ -129,6 +132,68 b' impl DirsMultiset {'
129 }
132 }
130 }
133 }
131
134
135 /// This is basically a reimplementation of `DirsMultiset` that stores the
136 /// children instead of just a count of them, plus a small optional
137 /// optimization to avoid some directories we don't need.
138 #[derive(PartialEq, Debug)]
139 pub struct DirsChildrenMultiset<'a> {
140 inner: FastHashMap<&'a HgPath, HashSet<&'a HgPath>>,
141 only_include: Option<HashSet<&'a HgPath>>,
142 }
143
144 impl<'a> DirsChildrenMultiset<'a> {
145 pub fn new(
146 paths: impl Iterator<Item = &'a HgPathBuf>,
147 only_include: Option<&'a HashSet<impl AsRef<HgPath> + 'a>>,
148 ) -> Self {
149 let mut new = Self {
150 inner: HashMap::default(),
151 only_include: only_include
152 .map(|s| s.iter().map(|p| p.as_ref()).collect()),
153 };
154
155 for path in paths {
156 new.add_path(path)
157 }
158
159 new
160 }
161 fn add_path(&mut self, path: &'a (impl AsRef<HgPath> + 'a)) {
162 if path.as_ref().is_empty() {
163 return;
164 }
165 for (directory, basename) in files::find_dirs_with_base(path.as_ref())
166 {
167 if !self.is_dir_included(directory) {
168 continue;
169 }
170 self.inner
171 .entry(directory)
172 .and_modify(|e| {
173 e.insert(basename);
174 })
175 .or_insert_with(|| {
176 let mut set = HashSet::new();
177 set.insert(basename);
178 set
179 });
180 }
181 }
182 fn is_dir_included(&self, dir: impl AsRef<HgPath>) -> bool {
183 match &self.only_include {
184 None => false,
185 Some(i) => i.contains(dir.as_ref()),
186 }
187 }
188
189 pub fn get(
190 &self,
191 path: impl AsRef<HgPath>,
192 ) -> Option<&HashSet<&'a HgPath>> {
193 self.inner.get(path.as_ref())
194 }
195 }
196
132 #[cfg(test)]
197 #[cfg(test)]
133 mod tests {
198 mod tests {
134 use super::*;
199 use super::*;
@@ -10,11 +10,11 b''
10 //! Functions for fiddling with files.
10 //! Functions for fiddling with files.
11
11
12 use crate::utils::hg_path::{HgPath, HgPathBuf};
12 use crate::utils::hg_path::{HgPath, HgPathBuf};
13 use std::iter::FusedIterator;
14
13
15 use crate::utils::replace_slice;
14 use crate::utils::replace_slice;
16 use lazy_static::lazy_static;
15 use lazy_static::lazy_static;
17 use std::fs::Metadata;
16 use std::fs::Metadata;
17 use std::iter::FusedIterator;
18 use std::path::Path;
18 use std::path::Path;
19
19
20 pub fn get_path_from_bytes(bytes: &[u8]) -> &Path {
20 pub fn get_path_from_bytes(bytes: &[u8]) -> &Path {
@@ -64,6 +64,28 b" impl<'a> Iterator for Ancestors<'a> {"
64
64
65 impl<'a> FusedIterator for Ancestors<'a> {}
65 impl<'a> FusedIterator for Ancestors<'a> {}
66
66
67 /// An iterator over repository path yielding itself and its ancestors.
68 #[derive(Copy, Clone, Debug)]
69 pub(crate) struct AncestorsWithBase<'a> {
70 next: Option<(&'a HgPath, &'a HgPath)>,
71 }
72
73 impl<'a> Iterator for AncestorsWithBase<'a> {
74 type Item = (&'a HgPath, &'a HgPath);
75
76 fn next(&mut self) -> Option<Self::Item> {
77 let next = self.next;
78 self.next = match self.next {
79 Some((s, _)) if s.is_empty() => None,
80 Some((s, _)) => Some(s.split_filename()),
81 None => None,
82 };
83 next
84 }
85 }
86
87 impl<'a> FusedIterator for AncestorsWithBase<'a> {}
88
67 /// Returns an iterator yielding ancestor directories of the given repository
89 /// Returns an iterator yielding ancestor directories of the given repository
68 /// path.
90 /// path.
69 ///
91 ///
@@ -79,6 +101,25 b" pub fn find_dirs<'a>(path: &'a HgPath) -"
79 dirs
101 dirs
80 }
102 }
81
103
104 /// Returns an iterator yielding ancestor directories of the given repository
105 /// path.
106 ///
107 /// The path is separated by '/', and must not start with '/'.
108 ///
109 /// The path itself isn't included unless it is b"" (meaning the root
110 /// directory.)
111 pub(crate) fn find_dirs_with_base<'a>(
112 path: &'a HgPath,
113 ) -> AncestorsWithBase<'a> {
114 let mut dirs = AncestorsWithBase {
115 next: Some((path, HgPath::new(b""))),
116 };
117 if !path.is_empty() {
118 dirs.next(); // skip itself
119 }
120 dirs
121 }
122
82 /// TODO more than ASCII?
123 /// TODO more than ASCII?
83 pub fn normalize_case(path: &HgPath) -> HgPathBuf {
124 pub fn normalize_case(path: &HgPath) -> HgPathBuf {
84 #[cfg(windows)] // NTFS compares via upper()
125 #[cfg(windows)] // NTFS compares via upper()
@@ -170,4 +211,28 b' mod tests {'
170 assert_eq!(dirs.next(), None);
211 assert_eq!(dirs.next(), None);
171 assert_eq!(dirs.next(), None);
212 assert_eq!(dirs.next(), None);
172 }
213 }
214
215 #[test]
216 fn test_find_dirs_with_base_some() {
217 let mut dirs = super::find_dirs_with_base(HgPath::new(b"foo/bar/baz"));
218 assert_eq!(
219 dirs.next(),
220 Some((HgPath::new(b"foo/bar"), HgPath::new(b"baz")))
221 );
222 assert_eq!(
223 dirs.next(),
224 Some((HgPath::new(b"foo"), HgPath::new(b"bar")))
225 );
226 assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b"foo"))));
227 assert_eq!(dirs.next(), None);
228 assert_eq!(dirs.next(), None);
229 }
230
231 #[test]
232 fn test_find_dirs_with_base_empty() {
233 let mut dirs = super::find_dirs_with_base(HgPath::new(b""));
234 assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b""))));
235 assert_eq!(dirs.next(), None);
236 assert_eq!(dirs.next(), None);
237 }
173 }
238 }
@@ -183,6 +183,29 b' impl HgPath {'
183 &self.inner[..]
183 &self.inner[..]
184 })
184 })
185 }
185 }
186 /// Returns a tuple of slices `(base, filename)` resulting from the split
187 /// at the rightmost `/`, if any.
188 ///
189 /// # Examples:
190 ///
191 /// ```
192 /// use hg::utils::hg_path::HgPath;
193 ///
194 /// let path = HgPath::new(b"cool/hg/path").split_filename();
195 /// assert_eq!(path, (HgPath::new(b"cool/hg"), HgPath::new(b"path")));
196 ///
197 /// let path = HgPath::new(b"pathwithoutsep").split_filename();
198 /// assert_eq!(path, (HgPath::new(b""), HgPath::new(b"pathwithoutsep")));
199 /// ```
200 pub fn split_filename(&self) -> (&Self, &Self) {
201 match &self.inner.iter().rposition(|c| *c == b'/') {
202 None => (HgPath::new(""), &self),
203 Some(size) => (
204 HgPath::new(&self.inner[..*size]),
205 HgPath::new(&self.inner[*size + 1..]),
206 ),
207 }
208 }
186 pub fn join<T: ?Sized + AsRef<Self>>(&self, other: &T) -> HgPathBuf {
209 pub fn join<T: ?Sized + AsRef<Self>>(&self, other: &T) -> HgPathBuf {
187 let mut inner = self.inner.to_owned();
210 let mut inner = self.inner.to_owned();
188 if inner.len() != 0 && inner.last() != Some(&b'/') {
211 if inner.len() != 0 && inner.last() != Some(&b'/') {
General Comments 0
You need to be logged in to leave comments. Login now