##// END OF EJS Templates
rust: re-format with nightly rustfmt...
Martin von Zweigbergk -
r44652:73209802 default
parent child Browse files
Show More
@@ -1,646 +1,648 b''
1 1 // hg_path.rs
2 2 //
3 3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 use std::borrow::Borrow;
9 9 use std::ffi::{OsStr, OsString};
10 10 use std::fmt;
11 11 use std::ops::Deref;
12 12 use std::path::{Path, PathBuf};
13 13
14 14 #[derive(Debug, Eq, PartialEq)]
15 15 pub enum HgPathError {
16 16 /// Bytes from the invalid `HgPath`
17 17 LeadingSlash(Vec<u8>),
18 18 /// Bytes and index of the second slash
19 19 ConsecutiveSlashes(Vec<u8>, usize),
20 20 /// Bytes and index of the null byte
21 21 ContainsNullByte(Vec<u8>, usize),
22 22 /// Bytes
23 23 DecodeError(Vec<u8>),
24 24 }
25 25
26 26 impl ToString for HgPathError {
27 27 fn to_string(&self) -> String {
28 28 match self {
29 29 HgPathError::LeadingSlash(bytes) => {
30 30 format!("Invalid HgPath '{:?}': has a leading slash.", bytes)
31 31 }
32 32 HgPathError::ConsecutiveSlashes(bytes, pos) => format!(
33 33 "Invalid HgPath '{:?}': consecutive slahes at pos {}.",
34 34 bytes, pos
35 35 ),
36 36 HgPathError::ContainsNullByte(bytes, pos) => format!(
37 37 "Invalid HgPath '{:?}': contains null byte at pos {}.",
38 38 bytes, pos
39 39 ),
40 40 HgPathError::DecodeError(bytes) => {
41 41 format!("Invalid HgPath '{:?}': could not be decoded.", bytes)
42 42 }
43 43 }
44 44 }
45 45 }
46 46
47 47 impl From<HgPathError> for std::io::Error {
48 48 fn from(e: HgPathError) -> Self {
49 49 std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string())
50 50 }
51 51 }
52 52
53 53 /// This is a repository-relative path (or canonical path):
54 54 /// - no null characters
55 55 /// - `/` separates directories
56 56 /// - no consecutive slashes
57 57 /// - no leading slash,
58 58 /// - no `.` nor `..` of special meaning
59 59 /// - stored in repository and shared across platforms
60 60 ///
61 61 /// Note: there is no guarantee of any `HgPath` being well-formed at any point
62 62 /// in its lifetime for performance reasons and to ease ergonomics. It is
63 63 /// however checked using the `check_state` method before any file-system
64 64 /// operation.
65 65 ///
66 66 /// This allows us to be encoding-transparent as much as possible, until really
67 67 /// needed; `HgPath` can be transformed into a platform-specific path (`OsStr`
68 68 /// or `Path`) whenever more complex operations are needed:
69 69 /// On Unix, it's just byte-to-byte conversion. On Windows, it has to be
70 70 /// decoded from MBCS to WTF-8. If WindowsUTF8Plan is implemented, the source
71 71 /// character encoding will be determined on a per-repository basis.
72 72 //
73 73 // FIXME: (adapted from a comment in the stdlib)
74 74 // `HgPath::new()` current implementation relies on `Slice` being
75 75 // layout-compatible with `[u8]`.
76 76 // When attribute privacy is implemented, `Slice` should be annotated as
77 77 // `#[repr(transparent)]`.
78 78 // Anyway, `Slice` representation and layout are considered implementation
79 79 // detail, are not documented and must not be relied upon.
80 80 #[derive(Eq, Ord, PartialEq, PartialOrd, Hash)]
81 81 pub struct HgPath {
82 82 inner: [u8],
83 83 }
84 84
85 85 impl HgPath {
86 86 pub fn new<S: AsRef<[u8]> + ?Sized>(s: &S) -> &Self {
87 87 unsafe { &*(s.as_ref() as *const [u8] as *const Self) }
88 88 }
89 89 pub fn is_empty(&self) -> bool {
90 90 self.inner.is_empty()
91 91 }
92 92 pub fn len(&self) -> usize {
93 93 self.inner.len()
94 94 }
95 95 fn to_hg_path_buf(&self) -> HgPathBuf {
96 96 HgPathBuf {
97 97 inner: self.inner.to_owned(),
98 98 }
99 99 }
100 100 pub fn bytes(&self) -> std::slice::Iter<u8> {
101 101 self.inner.iter()
102 102 }
103 103 pub fn to_ascii_uppercase(&self) -> HgPathBuf {
104 104 HgPathBuf::from(self.inner.to_ascii_uppercase())
105 105 }
106 106 pub fn to_ascii_lowercase(&self) -> HgPathBuf {
107 107 HgPathBuf::from(self.inner.to_ascii_lowercase())
108 108 }
109 109 pub fn as_bytes(&self) -> &[u8] {
110 110 &self.inner
111 111 }
112 112 pub fn contains(&self, other: u8) -> bool {
113 113 self.inner.contains(&other)
114 114 }
115 115 pub fn starts_with(&self, needle: impl AsRef<HgPath>) -> bool {
116 116 self.inner.starts_with(needle.as_ref().as_bytes())
117 117 }
118 118 pub fn join<T: ?Sized + AsRef<HgPath>>(&self, other: &T) -> HgPathBuf {
119 119 let mut inner = self.inner.to_owned();
120 120 if inner.len() != 0 && inner.last() != Some(&b'/') {
121 121 inner.push(b'/');
122 122 }
123 123 inner.extend(other.as_ref().bytes());
124 124 HgPathBuf::from_bytes(&inner)
125 125 }
126 126 /// Given a base directory, returns the slice of `self` relative to the
127 127 /// base directory. If `base` is not a directory (does not end with a
128 128 /// `b'/'`), returns `None`.
129 129 pub fn relative_to(&self, base: impl AsRef<HgPath>) -> Option<&HgPath> {
130 130 let base = base.as_ref();
131 131 if base.is_empty() {
132 132 return Some(self);
133 133 }
134 134 let is_dir = base.as_bytes().ends_with(b"/");
135 135 if is_dir && self.starts_with(base) {
136 136 Some(HgPath::new(&self.inner[base.len()..]))
137 137 } else {
138 138 None
139 139 }
140 140 }
141 141
142 142 #[cfg(windows)]
143 143 /// Copied from the Python stdlib's `os.path.splitdrive` implementation.
144 144 ///
145 /// Split a pathname into drive/UNC sharepoint and relative path specifiers.
146 /// Returns a 2-tuple (drive_or_unc, path); either part may be empty.
145 /// Split a pathname into drive/UNC sharepoint and relative path
146 /// specifiers. Returns a 2-tuple (drive_or_unc, path); either part may
147 /// be empty.
147 148 ///
148 149 /// If you assign
149 150 /// result = split_drive(p)
150 151 /// It is always true that:
151 152 /// result[0] + result[1] == p
152 153 ///
153 /// If the path contained a drive letter, drive_or_unc will contain everything
154 /// up to and including the colon.
154 /// If the path contained a drive letter, drive_or_unc will contain
155 /// everything up to and including the colon.
155 156 /// e.g. split_drive("c:/dir") returns ("c:", "/dir")
156 157 ///
157 /// If the path contained a UNC path, the drive_or_unc will contain the host
158 /// name and share up to but not including the fourth directory separator
159 /// character.
160 /// e.g. split_drive("//host/computer/dir") returns ("//host/computer", "/dir")
158 /// If the path contained a UNC path, the drive_or_unc will contain the
159 /// host name and share up to but not including the fourth directory
160 /// separator character.
161 /// e.g. split_drive("//host/computer/dir") returns ("//host/computer",
162 /// "/dir")
161 163 ///
162 164 /// Paths cannot contain both a drive letter and a UNC path.
163 165 pub fn split_drive<'a>(&self) -> (&HgPath, &HgPath) {
164 166 let bytes = self.as_bytes();
165 167 let is_sep = |b| std::path::is_separator(b as char);
166 168
167 169 if self.len() < 2 {
168 170 (HgPath::new(b""), &self)
169 171 } else if is_sep(bytes[0])
170 172 && is_sep(bytes[1])
171 173 && (self.len() == 2 || !is_sep(bytes[2]))
172 174 {
173 175 // Is a UNC path:
174 176 // vvvvvvvvvvvvvvvvvvvv drive letter or UNC path
175 177 // \\machine\mountpoint\directory\etc\...
176 178 // directory ^^^^^^^^^^^^^^^
177 179
178 180 let machine_end_index = bytes[2..].iter().position(|b| is_sep(*b));
179 181 let mountpoint_start_index = if let Some(i) = machine_end_index {
180 182 i + 2
181 183 } else {
182 184 return (HgPath::new(b""), &self);
183 185 };
184 186
185 187 match bytes[mountpoint_start_index + 1..]
186 188 .iter()
187 189 .position(|b| is_sep(*b))
188 190 {
189 191 // A UNC path can't have two slashes in a row
190 192 // (after the initial two)
191 193 Some(0) => (HgPath::new(b""), &self),
192 194 Some(i) => {
193 195 let (a, b) =
194 196 bytes.split_at(mountpoint_start_index + 1 + i);
195 197 (HgPath::new(a), HgPath::new(b))
196 198 }
197 199 None => (&self, HgPath::new(b"")),
198 200 }
199 201 } else if bytes[1] == b':' {
200 202 // Drive path c:\directory
201 203 let (a, b) = bytes.split_at(2);
202 204 (HgPath::new(a), HgPath::new(b))
203 205 } else {
204 206 (HgPath::new(b""), &self)
205 207 }
206 208 }
207 209
208 210 #[cfg(unix)]
209 211 /// Split a pathname into drive and path. On Posix, drive is always empty.
210 212 pub fn split_drive(&self) -> (&HgPath, &HgPath) {
211 213 (HgPath::new(b""), &self)
212 214 }
213 215
214 216 /// Checks for errors in the path, short-circuiting at the first one.
215 217 /// This generates fine-grained errors useful for debugging.
216 218 /// To simply check if the path is valid during tests, use `is_valid`.
217 219 pub fn check_state(&self) -> Result<(), HgPathError> {
218 220 if self.len() == 0 {
219 221 return Ok(());
220 222 }
221 223 let bytes = self.as_bytes();
222 224 let mut previous_byte = None;
223 225
224 226 if bytes[0] == b'/' {
225 227 return Err(HgPathError::LeadingSlash(bytes.to_vec()));
226 228 }
227 229 for (index, byte) in bytes.iter().enumerate() {
228 230 match byte {
229 231 0 => {
230 232 return Err(HgPathError::ContainsNullByte(
231 233 bytes.to_vec(),
232 234 index,
233 235 ))
234 236 }
235 237 b'/' => {
236 238 if previous_byte.is_some() && previous_byte == Some(b'/') {
237 239 return Err(HgPathError::ConsecutiveSlashes(
238 240 bytes.to_vec(),
239 241 index,
240 242 ));
241 243 }
242 244 }
243 245 _ => (),
244 246 };
245 247 previous_byte = Some(*byte);
246 248 }
247 249 Ok(())
248 250 }
249 251
250 252 #[cfg(test)]
251 253 /// Only usable during tests to force developers to handle invalid states
252 254 fn is_valid(&self) -> bool {
253 255 self.check_state().is_ok()
254 256 }
255 257 }
256 258
257 259 impl fmt::Debug for HgPath {
258 260 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259 261 write!(f, "HgPath({:?})", String::from_utf8_lossy(&self.inner))
260 262 }
261 263 }
262 264
263 265 impl fmt::Display for HgPath {
264 266 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
265 267 write!(f, "{}", String::from_utf8_lossy(&self.inner))
266 268 }
267 269 }
268 270
269 271 #[derive(Eq, Ord, Clone, PartialEq, PartialOrd, Hash)]
270 272 pub struct HgPathBuf {
271 273 inner: Vec<u8>,
272 274 }
273 275
274 276 impl HgPathBuf {
275 277 pub fn new() -> Self {
276 278 Self { inner: Vec::new() }
277 279 }
278 280 pub fn push(&mut self, byte: u8) {
279 281 self.inner.push(byte);
280 282 }
281 283 pub fn from_bytes(s: &[u8]) -> HgPathBuf {
282 284 HgPath::new(s).to_owned()
283 285 }
284 286 pub fn into_vec(self) -> Vec<u8> {
285 287 self.inner
286 288 }
287 289 pub fn as_ref(&self) -> &[u8] {
288 290 self.inner.as_ref()
289 291 }
290 292 }
291 293
292 294 impl fmt::Debug for HgPathBuf {
293 295 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
294 296 write!(f, "HgPathBuf({:?})", String::from_utf8_lossy(&self.inner))
295 297 }
296 298 }
297 299
298 300 impl fmt::Display for HgPathBuf {
299 301 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
300 302 write!(f, "{}", String::from_utf8_lossy(&self.inner))
301 303 }
302 304 }
303 305
304 306 impl Deref for HgPathBuf {
305 307 type Target = HgPath;
306 308
307 309 #[inline]
308 310 fn deref(&self) -> &HgPath {
309 311 &HgPath::new(&self.inner)
310 312 }
311 313 }
312 314
313 315 impl From<Vec<u8>> for HgPathBuf {
314 316 fn from(vec: Vec<u8>) -> Self {
315 317 Self { inner: vec }
316 318 }
317 319 }
318 320
319 321 impl<T: ?Sized + AsRef<HgPath>> From<&T> for HgPathBuf {
320 322 fn from(s: &T) -> HgPathBuf {
321 323 s.as_ref().to_owned()
322 324 }
323 325 }
324 326
325 327 impl Into<Vec<u8>> for HgPathBuf {
326 328 fn into(self) -> Vec<u8> {
327 329 self.inner
328 330 }
329 331 }
330 332
331 333 impl Borrow<HgPath> for HgPathBuf {
332 334 fn borrow(&self) -> &HgPath {
333 335 &HgPath::new(self.as_bytes())
334 336 }
335 337 }
336 338
337 339 impl ToOwned for HgPath {
338 340 type Owned = HgPathBuf;
339 341
340 342 fn to_owned(&self) -> HgPathBuf {
341 343 self.to_hg_path_buf()
342 344 }
343 345 }
344 346
345 347 impl AsRef<HgPath> for HgPath {
346 348 fn as_ref(&self) -> &HgPath {
347 349 self
348 350 }
349 351 }
350 352
351 353 impl AsRef<HgPath> for HgPathBuf {
352 354 fn as_ref(&self) -> &HgPath {
353 355 self
354 356 }
355 357 }
356 358
357 359 impl Extend<u8> for HgPathBuf {
358 360 fn extend<T: IntoIterator<Item = u8>>(&mut self, iter: T) {
359 361 self.inner.extend(iter);
360 362 }
361 363 }
362 364
363 365 /// TODO: Once https://www.mercurial-scm.org/wiki/WindowsUTF8Plan is
364 366 /// implemented, these conversion utils will have to work differently depending
365 367 /// on the repository encoding: either `UTF-8` or `MBCS`.
366 368
367 369 pub fn hg_path_to_os_string<P: AsRef<HgPath>>(
368 370 hg_path: P,
369 371 ) -> Result<OsString, HgPathError> {
370 372 hg_path.as_ref().check_state()?;
371 373 let os_str;
372 374 #[cfg(unix)]
373 375 {
374 376 use std::os::unix::ffi::OsStrExt;
375 377 os_str = std::ffi::OsStr::from_bytes(&hg_path.as_ref().as_bytes());
376 378 }
377 379 // TODO Handle other platforms
378 380 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
379 381 Ok(os_str.to_os_string())
380 382 }
381 383
382 384 pub fn hg_path_to_path_buf<P: AsRef<HgPath>>(
383 385 hg_path: P,
384 386 ) -> Result<PathBuf, HgPathError> {
385 387 Ok(Path::new(&hg_path_to_os_string(hg_path)?).to_path_buf())
386 388 }
387 389
388 390 pub fn os_string_to_hg_path_buf<S: AsRef<OsStr>>(
389 391 os_string: S,
390 392 ) -> Result<HgPathBuf, HgPathError> {
391 393 let buf;
392 394 #[cfg(unix)]
393 395 {
394 396 use std::os::unix::ffi::OsStrExt;
395 397 buf = HgPathBuf::from_bytes(&os_string.as_ref().as_bytes());
396 398 }
397 399 // TODO Handle other platforms
398 400 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
399 401
400 402 buf.check_state()?;
401 403 Ok(buf)
402 404 }
403 405
404 406 pub fn path_to_hg_path_buf<P: AsRef<Path>>(
405 407 path: P,
406 408 ) -> Result<HgPathBuf, HgPathError> {
407 409 let buf;
408 410 let os_str = path.as_ref().as_os_str();
409 411 #[cfg(unix)]
410 412 {
411 413 use std::os::unix::ffi::OsStrExt;
412 414 buf = HgPathBuf::from_bytes(&os_str.as_bytes());
413 415 }
414 416 // TODO Handle other platforms
415 417 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
416 418
417 419 buf.check_state()?;
418 420 Ok(buf)
419 421 }
420 422
421 423 #[cfg(test)]
422 424 mod tests {
423 425 use super::*;
424 426
425 427 #[test]
426 428 fn test_path_states() {
427 429 assert_eq!(
428 430 Err(HgPathError::LeadingSlash(b"/".to_vec())),
429 431 HgPath::new(b"/").check_state()
430 432 );
431 433 assert_eq!(
432 434 Err(HgPathError::ConsecutiveSlashes(b"a/b//c".to_vec(), 4)),
433 435 HgPath::new(b"a/b//c").check_state()
434 436 );
435 437 assert_eq!(
436 438 Err(HgPathError::ContainsNullByte(b"a/b/\0c".to_vec(), 4)),
437 439 HgPath::new(b"a/b/\0c").check_state()
438 440 );
439 441 // TODO test HgPathError::DecodeError for the Windows implementation.
440 442 assert_eq!(true, HgPath::new(b"").is_valid());
441 443 assert_eq!(true, HgPath::new(b"a/b/c").is_valid());
442 444 // Backslashes in paths are not significant, but allowed
443 445 assert_eq!(true, HgPath::new(br"a\b/c").is_valid());
444 446 // Dots in paths are not significant, but allowed
445 447 assert_eq!(true, HgPath::new(b"a/b/../c/").is_valid());
446 448 assert_eq!(true, HgPath::new(b"./a/b/../c/").is_valid());
447 449 }
448 450
449 451 #[test]
450 452 fn test_iter() {
451 453 let path = HgPath::new(b"a");
452 454 let mut iter = path.bytes();
453 455 assert_eq!(Some(&b'a'), iter.next());
454 456 assert_eq!(None, iter.next_back());
455 457 assert_eq!(None, iter.next());
456 458
457 459 let path = HgPath::new(b"a");
458 460 let mut iter = path.bytes();
459 461 assert_eq!(Some(&b'a'), iter.next_back());
460 462 assert_eq!(None, iter.next_back());
461 463 assert_eq!(None, iter.next());
462 464
463 465 let path = HgPath::new(b"abc");
464 466 let mut iter = path.bytes();
465 467 assert_eq!(Some(&b'a'), iter.next());
466 468 assert_eq!(Some(&b'c'), iter.next_back());
467 469 assert_eq!(Some(&b'b'), iter.next_back());
468 470 assert_eq!(None, iter.next_back());
469 471 assert_eq!(None, iter.next());
470 472
471 473 let path = HgPath::new(b"abc");
472 474 let mut iter = path.bytes();
473 475 assert_eq!(Some(&b'a'), iter.next());
474 476 assert_eq!(Some(&b'b'), iter.next());
475 477 assert_eq!(Some(&b'c'), iter.next());
476 478 assert_eq!(None, iter.next_back());
477 479 assert_eq!(None, iter.next());
478 480
479 481 let path = HgPath::new(b"abc");
480 482 let iter = path.bytes();
481 483 let mut vec = Vec::new();
482 484 vec.extend(iter);
483 485 assert_eq!(vec![b'a', b'b', b'c'], vec);
484 486
485 487 let path = HgPath::new(b"abc");
486 488 let mut iter = path.bytes();
487 489 assert_eq!(Some(2), iter.rposition(|c| *c == b'c'));
488 490
489 491 let path = HgPath::new(b"abc");
490 492 let mut iter = path.bytes();
491 493 assert_eq!(None, iter.rposition(|c| *c == b'd'));
492 494 }
493 495
494 496 #[test]
495 497 fn test_join() {
496 498 let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"b"));
497 499 assert_eq!(b"a/b", path.as_bytes());
498 500
499 501 let path = HgPathBuf::from_bytes(b"a/").join(HgPath::new(b"b/c"));
500 502 assert_eq!(b"a/b/c", path.as_bytes());
501 503
502 504 // No leading slash if empty before join
503 505 let path = HgPathBuf::new().join(HgPath::new(b"b/c"));
504 506 assert_eq!(b"b/c", path.as_bytes());
505 507
506 508 // The leading slash is an invalid representation of an `HgPath`, but
507 509 // it can happen. This creates another invalid representation of
508 510 // consecutive bytes.
509 511 // TODO What should be done in this case? Should we silently remove
510 512 // the extra slash? Should we change the signature to a problematic
511 513 // `Result<HgPathBuf, HgPathError>`, or should we just keep it so and
512 514 // let the error happen upon filesystem interaction?
513 515 let path = HgPathBuf::from_bytes(b"a/").join(HgPath::new(b"/b"));
514 516 assert_eq!(b"a//b", path.as_bytes());
515 517 let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"/b"));
516 518 assert_eq!(b"a//b", path.as_bytes());
517 519 }
518 520
519 521 #[test]
520 522 fn test_relative_to() {
521 523 let path = HgPath::new(b"");
522 524 let base = HgPath::new(b"");
523 525 assert_eq!(Some(path), path.relative_to(base));
524 526
525 527 let path = HgPath::new(b"path");
526 528 let base = HgPath::new(b"");
527 529 assert_eq!(Some(path), path.relative_to(base));
528 530
529 531 let path = HgPath::new(b"a");
530 532 let base = HgPath::new(b"b");
531 533 assert_eq!(None, path.relative_to(base));
532 534
533 535 let path = HgPath::new(b"a/b");
534 536 let base = HgPath::new(b"a");
535 537 assert_eq!(None, path.relative_to(base));
536 538
537 539 let path = HgPath::new(b"a/b");
538 540 let base = HgPath::new(b"a/");
539 541 assert_eq!(Some(HgPath::new(b"b")), path.relative_to(base));
540 542
541 543 let path = HgPath::new(b"nested/path/to/b");
542 544 let base = HgPath::new(b"nested/path/");
543 545 assert_eq!(Some(HgPath::new(b"to/b")), path.relative_to(base));
544 546
545 547 let path = HgPath::new(b"ends/with/dir/");
546 548 let base = HgPath::new(b"ends/");
547 549 assert_eq!(Some(HgPath::new(b"with/dir/")), path.relative_to(base));
548 550 }
549 551
550 552 #[test]
551 553 #[cfg(unix)]
552 554 fn test_split_drive() {
553 555 // Taken from the Python stdlib's tests
554 556 assert_eq!(
555 557 HgPath::new(br"/foo/bar").split_drive(),
556 558 (HgPath::new(b""), HgPath::new(br"/foo/bar"))
557 559 );
558 560 assert_eq!(
559 561 HgPath::new(br"foo:bar").split_drive(),
560 562 (HgPath::new(b""), HgPath::new(br"foo:bar"))
561 563 );
562 564 assert_eq!(
563 565 HgPath::new(br":foo:bar").split_drive(),
564 566 (HgPath::new(b""), HgPath::new(br":foo:bar"))
565 567 );
566 568 // Also try NT paths; should not split them
567 569 assert_eq!(
568 570 HgPath::new(br"c:\foo\bar").split_drive(),
569 571 (HgPath::new(b""), HgPath::new(br"c:\foo\bar"))
570 572 );
571 573 assert_eq!(
572 574 HgPath::new(b"c:/foo/bar").split_drive(),
573 575 (HgPath::new(b""), HgPath::new(br"c:/foo/bar"))
574 576 );
575 577 assert_eq!(
576 578 HgPath::new(br"\\conky\mountpoint\foo\bar").split_drive(),
577 579 (
578 580 HgPath::new(b""),
579 581 HgPath::new(br"\\conky\mountpoint\foo\bar")
580 582 )
581 583 );
582 584 }
583 585
584 586 #[test]
585 587 #[cfg(windows)]
586 588 fn test_split_drive() {
587 589 assert_eq!(
588 590 HgPath::new(br"c:\foo\bar").split_drive(),
589 591 (HgPath::new(br"c:"), HgPath::new(br"\foo\bar"))
590 592 );
591 593 assert_eq!(
592 594 HgPath::new(b"c:/foo/bar").split_drive(),
593 595 (HgPath::new(br"c:"), HgPath::new(br"/foo/bar"))
594 596 );
595 597 assert_eq!(
596 598 HgPath::new(br"\\conky\mountpoint\foo\bar").split_drive(),
597 599 (
598 600 HgPath::new(br"\\conky\mountpoint"),
599 601 HgPath::new(br"\foo\bar")
600 602 )
601 603 );
602 604 assert_eq!(
603 605 HgPath::new(br"//conky/mountpoint/foo/bar").split_drive(),
604 606 (
605 607 HgPath::new(br"//conky/mountpoint"),
606 608 HgPath::new(br"/foo/bar")
607 609 )
608 610 );
609 611 assert_eq!(
610 612 HgPath::new(br"\\\conky\mountpoint\foo\bar").split_drive(),
611 613 (
612 614 HgPath::new(br""),
613 615 HgPath::new(br"\\\conky\mountpoint\foo\bar")
614 616 )
615 617 );
616 618 assert_eq!(
617 619 HgPath::new(br"///conky/mountpoint/foo/bar").split_drive(),
618 620 (
619 621 HgPath::new(br""),
620 622 HgPath::new(br"///conky/mountpoint/foo/bar")
621 623 )
622 624 );
623 625 assert_eq!(
624 626 HgPath::new(br"\\conky\\mountpoint\foo\bar").split_drive(),
625 627 (
626 628 HgPath::new(br""),
627 629 HgPath::new(br"\\conky\\mountpoint\foo\bar")
628 630 )
629 631 );
630 632 assert_eq!(
631 633 HgPath::new(br"//conky//mountpoint/foo/bar").split_drive(),
632 634 (
633 635 HgPath::new(br""),
634 636 HgPath::new(br"//conky//mountpoint/foo/bar")
635 637 )
636 638 );
637 639 // UNC part containing U+0130
638 640 assert_eq!(
639 641 HgPath::new(b"//conky/MOUNTPO\xc4\xb0NT/foo/bar").split_drive(),
640 642 (
641 643 HgPath::new(b"//conky/MOUNTPO\xc4\xb0NT"),
642 644 HgPath::new(br"/foo/bar")
643 645 )
644 646 );
645 647 }
646 648 }
General Comments 0
You need to be logged in to leave comments. Login now