##// END OF EJS Templates
rust-hg-path: add method to get part of a path relative to a prefix...
Raphaël Gomès -
r44285:4f1543a2 default
parent child Browse files
Show More
@@ -1,415 +1,464 b''
1 // hg_path.rs
1 // hg_path.rs
2 //
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
6 // GNU General Public License version 2 or any later version.
7
7
8 use std::borrow::Borrow;
8 use std::borrow::Borrow;
9 use std::ffi::{OsStr, OsString};
9 use std::ffi::{OsStr, OsString};
10 use std::fmt;
10 use std::fmt;
11 use std::ops::Deref;
11 use std::ops::Deref;
12 use std::path::{Path, PathBuf};
12 use std::path::{Path, PathBuf};
13
13
14 #[derive(Debug, Eq, PartialEq)]
14 #[derive(Debug, Eq, PartialEq)]
15 pub enum HgPathError {
15 pub enum HgPathError {
16 /// Bytes from the invalid `HgPath`
16 /// Bytes from the invalid `HgPath`
17 LeadingSlash(Vec<u8>),
17 LeadingSlash(Vec<u8>),
18 /// Bytes and index of the second slash
18 /// Bytes and index of the second slash
19 ConsecutiveSlashes(Vec<u8>, usize),
19 ConsecutiveSlashes(Vec<u8>, usize),
20 /// Bytes and index of the null byte
20 /// Bytes and index of the null byte
21 ContainsNullByte(Vec<u8>, usize),
21 ContainsNullByte(Vec<u8>, usize),
22 /// Bytes
22 /// Bytes
23 DecodeError(Vec<u8>),
23 DecodeError(Vec<u8>),
24 }
24 }
25
25
26 impl ToString for HgPathError {
26 impl ToString for HgPathError {
27 fn to_string(&self) -> String {
27 fn to_string(&self) -> String {
28 match self {
28 match self {
29 HgPathError::LeadingSlash(bytes) => {
29 HgPathError::LeadingSlash(bytes) => {
30 format!("Invalid HgPath '{:?}': has a leading slash.", bytes)
30 format!("Invalid HgPath '{:?}': has a leading slash.", bytes)
31 }
31 }
32 HgPathError::ConsecutiveSlashes(bytes, pos) => format!(
32 HgPathError::ConsecutiveSlashes(bytes, pos) => format!(
33 "Invalid HgPath '{:?}': consecutive slahes at pos {}.",
33 "Invalid HgPath '{:?}': consecutive slahes at pos {}.",
34 bytes, pos
34 bytes, pos
35 ),
35 ),
36 HgPathError::ContainsNullByte(bytes, pos) => format!(
36 HgPathError::ContainsNullByte(bytes, pos) => format!(
37 "Invalid HgPath '{:?}': contains null byte at pos {}.",
37 "Invalid HgPath '{:?}': contains null byte at pos {}.",
38 bytes, pos
38 bytes, pos
39 ),
39 ),
40 HgPathError::DecodeError(bytes) => {
40 HgPathError::DecodeError(bytes) => {
41 format!("Invalid HgPath '{:?}': could not be decoded.", bytes)
41 format!("Invalid HgPath '{:?}': could not be decoded.", bytes)
42 }
42 }
43 }
43 }
44 }
44 }
45 }
45 }
46
46
47 impl From<HgPathError> for std::io::Error {
47 impl From<HgPathError> for std::io::Error {
48 fn from(e: HgPathError) -> Self {
48 fn from(e: HgPathError) -> Self {
49 std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string())
49 std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string())
50 }
50 }
51 }
51 }
52
52
53 /// This is a repository-relative path (or canonical path):
53 /// This is a repository-relative path (or canonical path):
54 /// - no null characters
54 /// - no null characters
55 /// - `/` separates directories
55 /// - `/` separates directories
56 /// - no consecutive slashes
56 /// - no consecutive slashes
57 /// - no leading slash,
57 /// - no leading slash,
58 /// - no `.` nor `..` of special meaning
58 /// - no `.` nor `..` of special meaning
59 /// - stored in repository and shared across platforms
59 /// - stored in repository and shared across platforms
60 ///
60 ///
61 /// Note: there is no guarantee of any `HgPath` being well-formed at any point
61 /// Note: there is no guarantee of any `HgPath` being well-formed at any point
62 /// in its lifetime for performance reasons and to ease ergonomics. It is
62 /// in its lifetime for performance reasons and to ease ergonomics. It is
63 /// however checked using the `check_state` method before any file-system
63 /// however checked using the `check_state` method before any file-system
64 /// operation.
64 /// operation.
65 ///
65 ///
66 /// This allows us to be encoding-transparent as much as possible, until really
66 /// This allows us to be encoding-transparent as much as possible, until really
67 /// needed; `HgPath` can be transformed into a platform-specific path (`OsStr`
67 /// needed; `HgPath` can be transformed into a platform-specific path (`OsStr`
68 /// or `Path`) whenever more complex operations are needed:
68 /// or `Path`) whenever more complex operations are needed:
69 /// On Unix, it's just byte-to-byte conversion. On Windows, it has to be
69 /// On Unix, it's just byte-to-byte conversion. On Windows, it has to be
70 /// decoded from MBCS to WTF-8. If WindowsUTF8Plan is implemented, the source
70 /// decoded from MBCS to WTF-8. If WindowsUTF8Plan is implemented, the source
71 /// character encoding will be determined on a per-repository basis.
71 /// character encoding will be determined on a per-repository basis.
72 //
72 //
73 // FIXME: (adapted from a comment in the stdlib)
73 // FIXME: (adapted from a comment in the stdlib)
74 // `HgPath::new()` current implementation relies on `Slice` being
74 // `HgPath::new()` current implementation relies on `Slice` being
75 // layout-compatible with `[u8]`.
75 // layout-compatible with `[u8]`.
76 // When attribute privacy is implemented, `Slice` should be annotated as
76 // When attribute privacy is implemented, `Slice` should be annotated as
77 // `#[repr(transparent)]`.
77 // `#[repr(transparent)]`.
78 // Anyway, `Slice` representation and layout are considered implementation
78 // Anyway, `Slice` representation and layout are considered implementation
79 // detail, are not documented and must not be relied upon.
79 // detail, are not documented and must not be relied upon.
80 #[derive(Eq, Ord, PartialEq, PartialOrd, Debug, Hash)]
80 #[derive(Eq, Ord, PartialEq, PartialOrd, Debug, Hash)]
81 pub struct HgPath {
81 pub struct HgPath {
82 inner: [u8],
82 inner: [u8],
83 }
83 }
84
84
85 impl HgPath {
85 impl HgPath {
86 pub fn new<S: AsRef<[u8]> + ?Sized>(s: &S) -> &Self {
86 pub fn new<S: AsRef<[u8]> + ?Sized>(s: &S) -> &Self {
87 unsafe { &*(s.as_ref() as *const [u8] as *const Self) }
87 unsafe { &*(s.as_ref() as *const [u8] as *const Self) }
88 }
88 }
89 pub fn is_empty(&self) -> bool {
89 pub fn is_empty(&self) -> bool {
90 self.inner.is_empty()
90 self.inner.is_empty()
91 }
91 }
92 pub fn len(&self) -> usize {
92 pub fn len(&self) -> usize {
93 self.inner.len()
93 self.inner.len()
94 }
94 }
95 fn to_hg_path_buf(&self) -> HgPathBuf {
95 fn to_hg_path_buf(&self) -> HgPathBuf {
96 HgPathBuf {
96 HgPathBuf {
97 inner: self.inner.to_owned(),
97 inner: self.inner.to_owned(),
98 }
98 }
99 }
99 }
100 pub fn bytes(&self) -> std::slice::Iter<u8> {
100 pub fn bytes(&self) -> std::slice::Iter<u8> {
101 self.inner.iter()
101 self.inner.iter()
102 }
102 }
103 pub fn to_ascii_uppercase(&self) -> HgPathBuf {
103 pub fn to_ascii_uppercase(&self) -> HgPathBuf {
104 HgPathBuf::from(self.inner.to_ascii_uppercase())
104 HgPathBuf::from(self.inner.to_ascii_uppercase())
105 }
105 }
106 pub fn to_ascii_lowercase(&self) -> HgPathBuf {
106 pub fn to_ascii_lowercase(&self) -> HgPathBuf {
107 HgPathBuf::from(self.inner.to_ascii_lowercase())
107 HgPathBuf::from(self.inner.to_ascii_lowercase())
108 }
108 }
109 pub fn as_bytes(&self) -> &[u8] {
109 pub fn as_bytes(&self) -> &[u8] {
110 &self.inner
110 &self.inner
111 }
111 }
112 pub fn contains(&self, other: u8) -> bool {
112 pub fn contains(&self, other: u8) -> bool {
113 self.inner.contains(&other)
113 self.inner.contains(&other)
114 }
114 }
115 pub fn starts_with(&self, needle: impl AsRef<HgPath>) -> bool {
116 self.inner.starts_with(needle.as_ref().as_bytes())
117 }
115 pub fn join<T: ?Sized + AsRef<HgPath>>(&self, other: &T) -> HgPathBuf {
118 pub fn join<T: ?Sized + AsRef<HgPath>>(&self, other: &T) -> HgPathBuf {
116 let mut inner = self.inner.to_owned();
119 let mut inner = self.inner.to_owned();
117 if inner.len() != 0 && inner.last() != Some(&b'/') {
120 if inner.len() != 0 && inner.last() != Some(&b'/') {
118 inner.push(b'/');
121 inner.push(b'/');
119 }
122 }
120 inner.extend(other.as_ref().bytes());
123 inner.extend(other.as_ref().bytes());
121 HgPathBuf::from_bytes(&inner)
124 HgPathBuf::from_bytes(&inner)
122 }
125 }
126 /// Given a base directory, returns the slice of `self` relative to the
127 /// base directory. If `base` is not a directory (does not end with a
128 /// `b'/'`), returns `None`.
129 pub fn relative_to(&self, base: impl AsRef<HgPath>) -> Option<&HgPath> {
130 let base = base.as_ref();
131 if base.is_empty() {
132 return Some(self);
133 }
134 let is_dir = base.as_bytes().ends_with(b"/");
135 if is_dir && self.starts_with(base) {
136 Some(HgPath::new(&self.inner[base.len()..]))
137 } else {
138 None
139 }
140 }
123 /// Checks for errors in the path, short-circuiting at the first one.
141 /// Checks for errors in the path, short-circuiting at the first one.
124 /// This generates fine-grained errors useful for debugging.
142 /// This generates fine-grained errors useful for debugging.
125 /// To simply check if the path is valid during tests, use `is_valid`.
143 /// To simply check if the path is valid during tests, use `is_valid`.
126 pub fn check_state(&self) -> Result<(), HgPathError> {
144 pub fn check_state(&self) -> Result<(), HgPathError> {
127 if self.len() == 0 {
145 if self.len() == 0 {
128 return Ok(());
146 return Ok(());
129 }
147 }
130 let bytes = self.as_bytes();
148 let bytes = self.as_bytes();
131 let mut previous_byte = None;
149 let mut previous_byte = None;
132
150
133 if bytes[0] == b'/' {
151 if bytes[0] == b'/' {
134 return Err(HgPathError::LeadingSlash(bytes.to_vec()));
152 return Err(HgPathError::LeadingSlash(bytes.to_vec()));
135 }
153 }
136 for (index, byte) in bytes.iter().enumerate() {
154 for (index, byte) in bytes.iter().enumerate() {
137 match byte {
155 match byte {
138 0 => {
156 0 => {
139 return Err(HgPathError::ContainsNullByte(
157 return Err(HgPathError::ContainsNullByte(
140 bytes.to_vec(),
158 bytes.to_vec(),
141 index,
159 index,
142 ))
160 ))
143 }
161 }
144 b'/' => {
162 b'/' => {
145 if previous_byte.is_some() && previous_byte == Some(b'/') {
163 if previous_byte.is_some() && previous_byte == Some(b'/') {
146 return Err(HgPathError::ConsecutiveSlashes(
164 return Err(HgPathError::ConsecutiveSlashes(
147 bytes.to_vec(),
165 bytes.to_vec(),
148 index,
166 index,
149 ));
167 ));
150 }
168 }
151 }
169 }
152 _ => (),
170 _ => (),
153 };
171 };
154 previous_byte = Some(*byte);
172 previous_byte = Some(*byte);
155 }
173 }
156 Ok(())
174 Ok(())
157 }
175 }
158
176
159 #[cfg(test)]
177 #[cfg(test)]
160 /// Only usable during tests to force developers to handle invalid states
178 /// Only usable during tests to force developers to handle invalid states
161 fn is_valid(&self) -> bool {
179 fn is_valid(&self) -> bool {
162 self.check_state().is_ok()
180 self.check_state().is_ok()
163 }
181 }
164 }
182 }
165
183
166 impl fmt::Display for HgPath {
184 impl fmt::Display for HgPath {
167 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168 write!(f, "{}", String::from_utf8_lossy(&self.inner))
186 write!(f, "{}", String::from_utf8_lossy(&self.inner))
169 }
187 }
170 }
188 }
171
189
172 #[derive(Eq, Ord, Clone, PartialEq, PartialOrd, Debug, Hash)]
190 #[derive(Eq, Ord, Clone, PartialEq, PartialOrd, Debug, Hash)]
173 pub struct HgPathBuf {
191 pub struct HgPathBuf {
174 inner: Vec<u8>,
192 inner: Vec<u8>,
175 }
193 }
176
194
177 impl HgPathBuf {
195 impl HgPathBuf {
178 pub fn new() -> Self {
196 pub fn new() -> Self {
179 Self { inner: Vec::new() }
197 Self { inner: Vec::new() }
180 }
198 }
181 pub fn push(&mut self, byte: u8) {
199 pub fn push(&mut self, byte: u8) {
182 self.inner.push(byte);
200 self.inner.push(byte);
183 }
201 }
184 pub fn from_bytes(s: &[u8]) -> HgPathBuf {
202 pub fn from_bytes(s: &[u8]) -> HgPathBuf {
185 HgPath::new(s).to_owned()
203 HgPath::new(s).to_owned()
186 }
204 }
187 pub fn into_vec(self) -> Vec<u8> {
205 pub fn into_vec(self) -> Vec<u8> {
188 self.inner
206 self.inner
189 }
207 }
190 pub fn as_ref(&self) -> &[u8] {
208 pub fn as_ref(&self) -> &[u8] {
191 self.inner.as_ref()
209 self.inner.as_ref()
192 }
210 }
193 }
211 }
194
212
195 impl fmt::Display for HgPathBuf {
213 impl fmt::Display for HgPathBuf {
196 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197 write!(f, "{}", String::from_utf8_lossy(&self.inner))
215 write!(f, "{}", String::from_utf8_lossy(&self.inner))
198 }
216 }
199 }
217 }
200
218
201 impl Deref for HgPathBuf {
219 impl Deref for HgPathBuf {
202 type Target = HgPath;
220 type Target = HgPath;
203
221
204 #[inline]
222 #[inline]
205 fn deref(&self) -> &HgPath {
223 fn deref(&self) -> &HgPath {
206 &HgPath::new(&self.inner)
224 &HgPath::new(&self.inner)
207 }
225 }
208 }
226 }
209
227
210 impl From<Vec<u8>> for HgPathBuf {
228 impl From<Vec<u8>> for HgPathBuf {
211 fn from(vec: Vec<u8>) -> Self {
229 fn from(vec: Vec<u8>) -> Self {
212 Self { inner: vec }
230 Self { inner: vec }
213 }
231 }
214 }
232 }
215
233
216 impl<T: ?Sized + AsRef<HgPath>> From<&T> for HgPathBuf {
234 impl<T: ?Sized + AsRef<HgPath>> From<&T> for HgPathBuf {
217 fn from(s: &T) -> HgPathBuf {
235 fn from(s: &T) -> HgPathBuf {
218 s.as_ref().to_owned()
236 s.as_ref().to_owned()
219 }
237 }
220 }
238 }
221
239
222 impl Into<Vec<u8>> for HgPathBuf {
240 impl Into<Vec<u8>> for HgPathBuf {
223 fn into(self) -> Vec<u8> {
241 fn into(self) -> Vec<u8> {
224 self.inner
242 self.inner
225 }
243 }
226 }
244 }
227
245
228 impl Borrow<HgPath> for HgPathBuf {
246 impl Borrow<HgPath> for HgPathBuf {
229 fn borrow(&self) -> &HgPath {
247 fn borrow(&self) -> &HgPath {
230 &HgPath::new(self.as_bytes())
248 &HgPath::new(self.as_bytes())
231 }
249 }
232 }
250 }
233
251
234 impl ToOwned for HgPath {
252 impl ToOwned for HgPath {
235 type Owned = HgPathBuf;
253 type Owned = HgPathBuf;
236
254
237 fn to_owned(&self) -> HgPathBuf {
255 fn to_owned(&self) -> HgPathBuf {
238 self.to_hg_path_buf()
256 self.to_hg_path_buf()
239 }
257 }
240 }
258 }
241
259
242 impl AsRef<HgPath> for HgPath {
260 impl AsRef<HgPath> for HgPath {
243 fn as_ref(&self) -> &HgPath {
261 fn as_ref(&self) -> &HgPath {
244 self
262 self
245 }
263 }
246 }
264 }
247
265
248 impl AsRef<HgPath> for HgPathBuf {
266 impl AsRef<HgPath> for HgPathBuf {
249 fn as_ref(&self) -> &HgPath {
267 fn as_ref(&self) -> &HgPath {
250 self
268 self
251 }
269 }
252 }
270 }
253
271
254 impl Extend<u8> for HgPathBuf {
272 impl Extend<u8> for HgPathBuf {
255 fn extend<T: IntoIterator<Item = u8>>(&mut self, iter: T) {
273 fn extend<T: IntoIterator<Item = u8>>(&mut self, iter: T) {
256 self.inner.extend(iter);
274 self.inner.extend(iter);
257 }
275 }
258 }
276 }
259
277
260 /// TODO: Once https://www.mercurial-scm.org/wiki/WindowsUTF8Plan is
278 /// TODO: Once https://www.mercurial-scm.org/wiki/WindowsUTF8Plan is
261 /// implemented, these conversion utils will have to work differently depending
279 /// implemented, these conversion utils will have to work differently depending
262 /// on the repository encoding: either `UTF-8` or `MBCS`.
280 /// on the repository encoding: either `UTF-8` or `MBCS`.
263
281
264 pub fn hg_path_to_os_string<P: AsRef<HgPath>>(
282 pub fn hg_path_to_os_string<P: AsRef<HgPath>>(
265 hg_path: P,
283 hg_path: P,
266 ) -> Result<OsString, HgPathError> {
284 ) -> Result<OsString, HgPathError> {
267 hg_path.as_ref().check_state()?;
285 hg_path.as_ref().check_state()?;
268 let os_str;
286 let os_str;
269 #[cfg(unix)]
287 #[cfg(unix)]
270 {
288 {
271 use std::os::unix::ffi::OsStrExt;
289 use std::os::unix::ffi::OsStrExt;
272 os_str = std::ffi::OsStr::from_bytes(&hg_path.as_ref().as_bytes());
290 os_str = std::ffi::OsStr::from_bytes(&hg_path.as_ref().as_bytes());
273 }
291 }
274 // TODO Handle other platforms
292 // TODO Handle other platforms
275 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
293 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
276 Ok(os_str.to_os_string())
294 Ok(os_str.to_os_string())
277 }
295 }
278
296
279 pub fn hg_path_to_path_buf<P: AsRef<HgPath>>(
297 pub fn hg_path_to_path_buf<P: AsRef<HgPath>>(
280 hg_path: P,
298 hg_path: P,
281 ) -> Result<PathBuf, HgPathError> {
299 ) -> Result<PathBuf, HgPathError> {
282 Ok(Path::new(&hg_path_to_os_string(hg_path)?).to_path_buf())
300 Ok(Path::new(&hg_path_to_os_string(hg_path)?).to_path_buf())
283 }
301 }
284
302
285 pub fn os_string_to_hg_path_buf<S: AsRef<OsStr>>(
303 pub fn os_string_to_hg_path_buf<S: AsRef<OsStr>>(
286 os_string: S,
304 os_string: S,
287 ) -> Result<HgPathBuf, HgPathError> {
305 ) -> Result<HgPathBuf, HgPathError> {
288 let buf;
306 let buf;
289 #[cfg(unix)]
307 #[cfg(unix)]
290 {
308 {
291 use std::os::unix::ffi::OsStrExt;
309 use std::os::unix::ffi::OsStrExt;
292 buf = HgPathBuf::from_bytes(&os_string.as_ref().as_bytes());
310 buf = HgPathBuf::from_bytes(&os_string.as_ref().as_bytes());
293 }
311 }
294 // TODO Handle other platforms
312 // TODO Handle other platforms
295 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
313 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
296
314
297 buf.check_state()?;
315 buf.check_state()?;
298 Ok(buf)
316 Ok(buf)
299 }
317 }
300
318
301 pub fn path_to_hg_path_buf<P: AsRef<Path>>(
319 pub fn path_to_hg_path_buf<P: AsRef<Path>>(
302 path: P,
320 path: P,
303 ) -> Result<HgPathBuf, HgPathError> {
321 ) -> Result<HgPathBuf, HgPathError> {
304 let buf;
322 let buf;
305 let os_str = path.as_ref().as_os_str();
323 let os_str = path.as_ref().as_os_str();
306 #[cfg(unix)]
324 #[cfg(unix)]
307 {
325 {
308 use std::os::unix::ffi::OsStrExt;
326 use std::os::unix::ffi::OsStrExt;
309 buf = HgPathBuf::from_bytes(&os_str.as_bytes());
327 buf = HgPathBuf::from_bytes(&os_str.as_bytes());
310 }
328 }
311 // TODO Handle other platforms
329 // TODO Handle other platforms
312 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
330 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
313
331
314 buf.check_state()?;
332 buf.check_state()?;
315 Ok(buf)
333 Ok(buf)
316 }
334 }
317
335
318 #[cfg(test)]
336 #[cfg(test)]
319 mod tests {
337 mod tests {
320 use super::*;
338 use super::*;
321
339
322 #[test]
340 #[test]
323 fn test_path_states() {
341 fn test_path_states() {
324 assert_eq!(
342 assert_eq!(
325 Err(HgPathError::LeadingSlash(b"/".to_vec())),
343 Err(HgPathError::LeadingSlash(b"/".to_vec())),
326 HgPath::new(b"/").check_state()
344 HgPath::new(b"/").check_state()
327 );
345 );
328 assert_eq!(
346 assert_eq!(
329 Err(HgPathError::ConsecutiveSlashes(b"a/b//c".to_vec(), 4)),
347 Err(HgPathError::ConsecutiveSlashes(b"a/b//c".to_vec(), 4)),
330 HgPath::new(b"a/b//c").check_state()
348 HgPath::new(b"a/b//c").check_state()
331 );
349 );
332 assert_eq!(
350 assert_eq!(
333 Err(HgPathError::ContainsNullByte(b"a/b/\0c".to_vec(), 4)),
351 Err(HgPathError::ContainsNullByte(b"a/b/\0c".to_vec(), 4)),
334 HgPath::new(b"a/b/\0c").check_state()
352 HgPath::new(b"a/b/\0c").check_state()
335 );
353 );
336 // TODO test HgPathError::DecodeError for the Windows implementation.
354 // TODO test HgPathError::DecodeError for the Windows implementation.
337 assert_eq!(true, HgPath::new(b"").is_valid());
355 assert_eq!(true, HgPath::new(b"").is_valid());
338 assert_eq!(true, HgPath::new(b"a/b/c").is_valid());
356 assert_eq!(true, HgPath::new(b"a/b/c").is_valid());
339 // Backslashes in paths are not significant, but allowed
357 // Backslashes in paths are not significant, but allowed
340 assert_eq!(true, HgPath::new(br"a\b/c").is_valid());
358 assert_eq!(true, HgPath::new(br"a\b/c").is_valid());
341 // Dots in paths are not significant, but allowed
359 // Dots in paths are not significant, but allowed
342 assert_eq!(true, HgPath::new(b"a/b/../c/").is_valid());
360 assert_eq!(true, HgPath::new(b"a/b/../c/").is_valid());
343 assert_eq!(true, HgPath::new(b"./a/b/../c/").is_valid());
361 assert_eq!(true, HgPath::new(b"./a/b/../c/").is_valid());
344 }
362 }
345
363
346 #[test]
364 #[test]
347 fn test_iter() {
365 fn test_iter() {
348 let path = HgPath::new(b"a");
366 let path = HgPath::new(b"a");
349 let mut iter = path.bytes();
367 let mut iter = path.bytes();
350 assert_eq!(Some(&b'a'), iter.next());
368 assert_eq!(Some(&b'a'), iter.next());
351 assert_eq!(None, iter.next_back());
369 assert_eq!(None, iter.next_back());
352 assert_eq!(None, iter.next());
370 assert_eq!(None, iter.next());
353
371
354 let path = HgPath::new(b"a");
372 let path = HgPath::new(b"a");
355 let mut iter = path.bytes();
373 let mut iter = path.bytes();
356 assert_eq!(Some(&b'a'), iter.next_back());
374 assert_eq!(Some(&b'a'), iter.next_back());
357 assert_eq!(None, iter.next_back());
375 assert_eq!(None, iter.next_back());
358 assert_eq!(None, iter.next());
376 assert_eq!(None, iter.next());
359
377
360 let path = HgPath::new(b"abc");
378 let path = HgPath::new(b"abc");
361 let mut iter = path.bytes();
379 let mut iter = path.bytes();
362 assert_eq!(Some(&b'a'), iter.next());
380 assert_eq!(Some(&b'a'), iter.next());
363 assert_eq!(Some(&b'c'), iter.next_back());
381 assert_eq!(Some(&b'c'), iter.next_back());
364 assert_eq!(Some(&b'b'), iter.next_back());
382 assert_eq!(Some(&b'b'), iter.next_back());
365 assert_eq!(None, iter.next_back());
383 assert_eq!(None, iter.next_back());
366 assert_eq!(None, iter.next());
384 assert_eq!(None, iter.next());
367
385
368 let path = HgPath::new(b"abc");
386 let path = HgPath::new(b"abc");
369 let mut iter = path.bytes();
387 let mut iter = path.bytes();
370 assert_eq!(Some(&b'a'), iter.next());
388 assert_eq!(Some(&b'a'), iter.next());
371 assert_eq!(Some(&b'b'), iter.next());
389 assert_eq!(Some(&b'b'), iter.next());
372 assert_eq!(Some(&b'c'), iter.next());
390 assert_eq!(Some(&b'c'), iter.next());
373 assert_eq!(None, iter.next_back());
391 assert_eq!(None, iter.next_back());
374 assert_eq!(None, iter.next());
392 assert_eq!(None, iter.next());
375
393
376 let path = HgPath::new(b"abc");
394 let path = HgPath::new(b"abc");
377 let iter = path.bytes();
395 let iter = path.bytes();
378 let mut vec = Vec::new();
396 let mut vec = Vec::new();
379 vec.extend(iter);
397 vec.extend(iter);
380 assert_eq!(vec![b'a', b'b', b'c'], vec);
398 assert_eq!(vec![b'a', b'b', b'c'], vec);
381
399
382 let path = HgPath::new(b"abc");
400 let path = HgPath::new(b"abc");
383 let mut iter = path.bytes();
401 let mut iter = path.bytes();
384 assert_eq!(Some(2), iter.rposition(|c| *c == b'c'));
402 assert_eq!(Some(2), iter.rposition(|c| *c == b'c'));
385
403
386 let path = HgPath::new(b"abc");
404 let path = HgPath::new(b"abc");
387 let mut iter = path.bytes();
405 let mut iter = path.bytes();
388 assert_eq!(None, iter.rposition(|c| *c == b'd'));
406 assert_eq!(None, iter.rposition(|c| *c == b'd'));
389 }
407 }
390
408
391 #[test]
409 #[test]
392 fn test_join() {
410 fn test_join() {
393 let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"b"));
411 let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"b"));
394 assert_eq!(b"a/b", path.as_bytes());
412 assert_eq!(b"a/b", path.as_bytes());
395
413
396 let path = HgPathBuf::from_bytes(b"a/").join(HgPath::new(b"b/c"));
414 let path = HgPathBuf::from_bytes(b"a/").join(HgPath::new(b"b/c"));
397 assert_eq!(b"a/b/c", path.as_bytes());
415 assert_eq!(b"a/b/c", path.as_bytes());
398
416
399 // No leading slash if empty before join
417 // No leading slash if empty before join
400 let path = HgPathBuf::new().join(HgPath::new(b"b/c"));
418 let path = HgPathBuf::new().join(HgPath::new(b"b/c"));
401 assert_eq!(b"b/c", path.as_bytes());
419 assert_eq!(b"b/c", path.as_bytes());
402
420
403 // The leading slash is an invalid representation of an `HgPath`, but
421 // The leading slash is an invalid representation of an `HgPath`, but
404 // it can happen. This creates another invalid representation of
422 // it can happen. This creates another invalid representation of
405 // consecutive bytes.
423 // consecutive bytes.
406 // TODO What should be done in this case? Should we silently remove
424 // TODO What should be done in this case? Should we silently remove
407 // the extra slash? Should we change the signature to a problematic
425 // the extra slash? Should we change the signature to a problematic
408 // `Result<HgPathBuf, HgPathError>`, or should we just keep it so and
426 // `Result<HgPathBuf, HgPathError>`, or should we just keep it so and
409 // let the error happen upon filesystem interaction?
427 // let the error happen upon filesystem interaction?
410 let path = HgPathBuf::from_bytes(b"a/").join(HgPath::new(b"/b"));
428 let path = HgPathBuf::from_bytes(b"a/").join(HgPath::new(b"/b"));
411 assert_eq!(b"a//b", path.as_bytes());
429 assert_eq!(b"a//b", path.as_bytes());
412 let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"/b"));
430 let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"/b"));
413 assert_eq!(b"a//b", path.as_bytes());
431 assert_eq!(b"a//b", path.as_bytes());
414 }
432 }
433
434 #[test]
435 fn test_relative_to() {
436 let path = HgPath::new(b"");
437 let base = HgPath::new(b"");
438 assert_eq!(Some(path), path.relative_to(base));
439
440 let path = HgPath::new(b"path");
441 let base = HgPath::new(b"");
442 assert_eq!(Some(path), path.relative_to(base));
443
444 let path = HgPath::new(b"a");
445 let base = HgPath::new(b"b");
446 assert_eq!(None, path.relative_to(base));
447
448 let path = HgPath::new(b"a/b");
449 let base = HgPath::new(b"a");
450 assert_eq!(None, path.relative_to(base));
451
452 let path = HgPath::new(b"a/b");
453 let base = HgPath::new(b"a/");
454 assert_eq!(Some(HgPath::new(b"b")), path.relative_to(base));
455
456 let path = HgPath::new(b"nested/path/to/b");
457 let base = HgPath::new(b"nested/path/");
458 assert_eq!(Some(HgPath::new(b"to/b")), path.relative_to(base));
459
460 let path = HgPath::new(b"ends/with/dir/");
461 let base = HgPath::new(b"ends/");
462 assert_eq!(Some(HgPath::new(b"with/dir/")), path.relative_to(base));
463 }
415 }
464 }
General Comments 0
You need to be logged in to leave comments. Login now