##// END OF EJS Templates
rust-utils: add Rust implementation of Python's "os.path.splitdrive"...
Raphaël Gomès -
r44588:baa4e7fd default
parent child Browse files
Show More
@@ -1,476 +1,646 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
142 #[cfg(windows)]
143 /// Copied from the Python stdlib's `os.path.splitdrive` implementation.
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.
147 ///
148 /// If you assign
149 /// result = split_drive(p)
150 /// It is always true that:
151 /// result[0] + result[1] == p
152 ///
153 /// If the path contained a drive letter, drive_or_unc will contain everything
154 /// up to and including the colon.
155 /// e.g. split_drive("c:/dir") returns ("c:", "/dir")
156 ///
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")
161 ///
162 /// Paths cannot contain both a drive letter and a UNC path.
163 pub fn split_drive<'a>(&self) -> (&HgPath, &HgPath) {
164 let bytes = self.as_bytes();
165 let is_sep = |b| std::path::is_separator(b as char);
166
167 if self.len() < 2 {
168 (HgPath::new(b""), &self)
169 } else if is_sep(bytes[0])
170 && is_sep(bytes[1])
171 && (self.len() == 2 || !is_sep(bytes[2]))
172 {
173 // Is a UNC path:
174 // vvvvvvvvvvvvvvvvvvvv drive letter or UNC path
175 // \\machine\mountpoint\directory\etc\...
176 // directory ^^^^^^^^^^^^^^^
177
178 let machine_end_index = bytes[2..].iter().position(|b| is_sep(*b));
179 let mountpoint_start_index = if let Some(i) = machine_end_index {
180 i + 2
181 } else {
182 return (HgPath::new(b""), &self);
183 };
184
185 match bytes[mountpoint_start_index + 1..]
186 .iter()
187 .position(|b| is_sep(*b))
188 {
189 // A UNC path can't have two slashes in a row
190 // (after the initial two)
191 Some(0) => (HgPath::new(b""), &self),
192 Some(i) => {
193 let (a, b) =
194 bytes.split_at(mountpoint_start_index + 1 + i);
195 (HgPath::new(a), HgPath::new(b))
196 }
197 None => (&self, HgPath::new(b"")),
198 }
199 } else if bytes[1] == b':' {
200 // Drive path c:\directory
201 let (a, b) = bytes.split_at(2);
202 (HgPath::new(a), HgPath::new(b))
203 } else {
204 (HgPath::new(b""), &self)
205 }
206 }
207
208 #[cfg(unix)]
209 /// Split a pathname into drive and path. On Posix, drive is always empty.
210 pub fn split_drive(&self) -> (&HgPath, &HgPath) {
211 (HgPath::new(b""), &self)
212 }
213
141 214 /// Checks for errors in the path, short-circuiting at the first one.
142 215 /// This generates fine-grained errors useful for debugging.
143 216 /// To simply check if the path is valid during tests, use `is_valid`.
144 217 pub fn check_state(&self) -> Result<(), HgPathError> {
145 218 if self.len() == 0 {
146 219 return Ok(());
147 220 }
148 221 let bytes = self.as_bytes();
149 222 let mut previous_byte = None;
150 223
151 224 if bytes[0] == b'/' {
152 225 return Err(HgPathError::LeadingSlash(bytes.to_vec()));
153 226 }
154 227 for (index, byte) in bytes.iter().enumerate() {
155 228 match byte {
156 229 0 => {
157 230 return Err(HgPathError::ContainsNullByte(
158 231 bytes.to_vec(),
159 232 index,
160 233 ))
161 234 }
162 235 b'/' => {
163 236 if previous_byte.is_some() && previous_byte == Some(b'/') {
164 237 return Err(HgPathError::ConsecutiveSlashes(
165 238 bytes.to_vec(),
166 239 index,
167 240 ));
168 241 }
169 242 }
170 243 _ => (),
171 244 };
172 245 previous_byte = Some(*byte);
173 246 }
174 247 Ok(())
175 248 }
176 249
177 250 #[cfg(test)]
178 251 /// Only usable during tests to force developers to handle invalid states
179 252 fn is_valid(&self) -> bool {
180 253 self.check_state().is_ok()
181 254 }
182 255 }
183 256
184 257 impl fmt::Debug for HgPath {
185 258 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186 259 write!(f, "HgPath({:?})", String::from_utf8_lossy(&self.inner))
187 260 }
188 261 }
189 262
190 263 impl fmt::Display for HgPath {
191 264 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192 265 write!(f, "{}", String::from_utf8_lossy(&self.inner))
193 266 }
194 267 }
195 268
196 269 #[derive(Eq, Ord, Clone, PartialEq, PartialOrd, Hash)]
197 270 pub struct HgPathBuf {
198 271 inner: Vec<u8>,
199 272 }
200 273
201 274 impl HgPathBuf {
202 275 pub fn new() -> Self {
203 276 Self { inner: Vec::new() }
204 277 }
205 278 pub fn push(&mut self, byte: u8) {
206 279 self.inner.push(byte);
207 280 }
208 281 pub fn from_bytes(s: &[u8]) -> HgPathBuf {
209 282 HgPath::new(s).to_owned()
210 283 }
211 284 pub fn into_vec(self) -> Vec<u8> {
212 285 self.inner
213 286 }
214 287 pub fn as_ref(&self) -> &[u8] {
215 288 self.inner.as_ref()
216 289 }
217 290 }
218 291
219 292 impl fmt::Debug for HgPathBuf {
220 293 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221 294 write!(f, "HgPathBuf({:?})", String::from_utf8_lossy(&self.inner))
222 295 }
223 296 }
224 297
225 298 impl fmt::Display for HgPathBuf {
226 299 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227 300 write!(f, "{}", String::from_utf8_lossy(&self.inner))
228 301 }
229 302 }
230 303
231 304 impl Deref for HgPathBuf {
232 305 type Target = HgPath;
233 306
234 307 #[inline]
235 308 fn deref(&self) -> &HgPath {
236 309 &HgPath::new(&self.inner)
237 310 }
238 311 }
239 312
240 313 impl From<Vec<u8>> for HgPathBuf {
241 314 fn from(vec: Vec<u8>) -> Self {
242 315 Self { inner: vec }
243 316 }
244 317 }
245 318
246 319 impl<T: ?Sized + AsRef<HgPath>> From<&T> for HgPathBuf {
247 320 fn from(s: &T) -> HgPathBuf {
248 321 s.as_ref().to_owned()
249 322 }
250 323 }
251 324
252 325 impl Into<Vec<u8>> for HgPathBuf {
253 326 fn into(self) -> Vec<u8> {
254 327 self.inner
255 328 }
256 329 }
257 330
258 331 impl Borrow<HgPath> for HgPathBuf {
259 332 fn borrow(&self) -> &HgPath {
260 333 &HgPath::new(self.as_bytes())
261 334 }
262 335 }
263 336
264 337 impl ToOwned for HgPath {
265 338 type Owned = HgPathBuf;
266 339
267 340 fn to_owned(&self) -> HgPathBuf {
268 341 self.to_hg_path_buf()
269 342 }
270 343 }
271 344
272 345 impl AsRef<HgPath> for HgPath {
273 346 fn as_ref(&self) -> &HgPath {
274 347 self
275 348 }
276 349 }
277 350
278 351 impl AsRef<HgPath> for HgPathBuf {
279 352 fn as_ref(&self) -> &HgPath {
280 353 self
281 354 }
282 355 }
283 356
284 357 impl Extend<u8> for HgPathBuf {
285 358 fn extend<T: IntoIterator<Item = u8>>(&mut self, iter: T) {
286 359 self.inner.extend(iter);
287 360 }
288 361 }
289 362
290 363 /// TODO: Once https://www.mercurial-scm.org/wiki/WindowsUTF8Plan is
291 364 /// implemented, these conversion utils will have to work differently depending
292 365 /// on the repository encoding: either `UTF-8` or `MBCS`.
293 366
294 367 pub fn hg_path_to_os_string<P: AsRef<HgPath>>(
295 368 hg_path: P,
296 369 ) -> Result<OsString, HgPathError> {
297 370 hg_path.as_ref().check_state()?;
298 371 let os_str;
299 372 #[cfg(unix)]
300 373 {
301 374 use std::os::unix::ffi::OsStrExt;
302 375 os_str = std::ffi::OsStr::from_bytes(&hg_path.as_ref().as_bytes());
303 376 }
304 377 // TODO Handle other platforms
305 378 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
306 379 Ok(os_str.to_os_string())
307 380 }
308 381
309 382 pub fn hg_path_to_path_buf<P: AsRef<HgPath>>(
310 383 hg_path: P,
311 384 ) -> Result<PathBuf, HgPathError> {
312 385 Ok(Path::new(&hg_path_to_os_string(hg_path)?).to_path_buf())
313 386 }
314 387
315 388 pub fn os_string_to_hg_path_buf<S: AsRef<OsStr>>(
316 389 os_string: S,
317 390 ) -> Result<HgPathBuf, HgPathError> {
318 391 let buf;
319 392 #[cfg(unix)]
320 393 {
321 394 use std::os::unix::ffi::OsStrExt;
322 395 buf = HgPathBuf::from_bytes(&os_string.as_ref().as_bytes());
323 396 }
324 397 // TODO Handle other platforms
325 398 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
326 399
327 400 buf.check_state()?;
328 401 Ok(buf)
329 402 }
330 403
331 404 pub fn path_to_hg_path_buf<P: AsRef<Path>>(
332 405 path: P,
333 406 ) -> Result<HgPathBuf, HgPathError> {
334 407 let buf;
335 408 let os_str = path.as_ref().as_os_str();
336 409 #[cfg(unix)]
337 410 {
338 411 use std::os::unix::ffi::OsStrExt;
339 412 buf = HgPathBuf::from_bytes(&os_str.as_bytes());
340 413 }
341 414 // TODO Handle other platforms
342 415 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
343 416
344 417 buf.check_state()?;
345 418 Ok(buf)
346 419 }
347 420
348 421 #[cfg(test)]
349 422 mod tests {
350 423 use super::*;
351 424
352 425 #[test]
353 426 fn test_path_states() {
354 427 assert_eq!(
355 428 Err(HgPathError::LeadingSlash(b"/".to_vec())),
356 429 HgPath::new(b"/").check_state()
357 430 );
358 431 assert_eq!(
359 432 Err(HgPathError::ConsecutiveSlashes(b"a/b//c".to_vec(), 4)),
360 433 HgPath::new(b"a/b//c").check_state()
361 434 );
362 435 assert_eq!(
363 436 Err(HgPathError::ContainsNullByte(b"a/b/\0c".to_vec(), 4)),
364 437 HgPath::new(b"a/b/\0c").check_state()
365 438 );
366 439 // TODO test HgPathError::DecodeError for the Windows implementation.
367 440 assert_eq!(true, HgPath::new(b"").is_valid());
368 441 assert_eq!(true, HgPath::new(b"a/b/c").is_valid());
369 442 // Backslashes in paths are not significant, but allowed
370 443 assert_eq!(true, HgPath::new(br"a\b/c").is_valid());
371 444 // Dots in paths are not significant, but allowed
372 445 assert_eq!(true, HgPath::new(b"a/b/../c/").is_valid());
373 446 assert_eq!(true, HgPath::new(b"./a/b/../c/").is_valid());
374 447 }
375 448
376 449 #[test]
377 450 fn test_iter() {
378 451 let path = HgPath::new(b"a");
379 452 let mut iter = path.bytes();
380 453 assert_eq!(Some(&b'a'), iter.next());
381 454 assert_eq!(None, iter.next_back());
382 455 assert_eq!(None, iter.next());
383 456
384 457 let path = HgPath::new(b"a");
385 458 let mut iter = path.bytes();
386 459 assert_eq!(Some(&b'a'), iter.next_back());
387 460 assert_eq!(None, iter.next_back());
388 461 assert_eq!(None, iter.next());
389 462
390 463 let path = HgPath::new(b"abc");
391 464 let mut iter = path.bytes();
392 465 assert_eq!(Some(&b'a'), iter.next());
393 466 assert_eq!(Some(&b'c'), iter.next_back());
394 467 assert_eq!(Some(&b'b'), iter.next_back());
395 468 assert_eq!(None, iter.next_back());
396 469 assert_eq!(None, iter.next());
397 470
398 471 let path = HgPath::new(b"abc");
399 472 let mut iter = path.bytes();
400 473 assert_eq!(Some(&b'a'), iter.next());
401 474 assert_eq!(Some(&b'b'), iter.next());
402 475 assert_eq!(Some(&b'c'), iter.next());
403 476 assert_eq!(None, iter.next_back());
404 477 assert_eq!(None, iter.next());
405 478
406 479 let path = HgPath::new(b"abc");
407 480 let iter = path.bytes();
408 481 let mut vec = Vec::new();
409 482 vec.extend(iter);
410 483 assert_eq!(vec![b'a', b'b', b'c'], vec);
411 484
412 485 let path = HgPath::new(b"abc");
413 486 let mut iter = path.bytes();
414 487 assert_eq!(Some(2), iter.rposition(|c| *c == b'c'));
415 488
416 489 let path = HgPath::new(b"abc");
417 490 let mut iter = path.bytes();
418 491 assert_eq!(None, iter.rposition(|c| *c == b'd'));
419 492 }
420 493
421 494 #[test]
422 495 fn test_join() {
423 496 let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"b"));
424 497 assert_eq!(b"a/b", path.as_bytes());
425 498
426 499 let path = HgPathBuf::from_bytes(b"a/").join(HgPath::new(b"b/c"));
427 500 assert_eq!(b"a/b/c", path.as_bytes());
428 501
429 502 // No leading slash if empty before join
430 503 let path = HgPathBuf::new().join(HgPath::new(b"b/c"));
431 504 assert_eq!(b"b/c", path.as_bytes());
432 505
433 506 // The leading slash is an invalid representation of an `HgPath`, but
434 507 // it can happen. This creates another invalid representation of
435 508 // consecutive bytes.
436 509 // TODO What should be done in this case? Should we silently remove
437 510 // the extra slash? Should we change the signature to a problematic
438 511 // `Result<HgPathBuf, HgPathError>`, or should we just keep it so and
439 512 // let the error happen upon filesystem interaction?
440 513 let path = HgPathBuf::from_bytes(b"a/").join(HgPath::new(b"/b"));
441 514 assert_eq!(b"a//b", path.as_bytes());
442 515 let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"/b"));
443 516 assert_eq!(b"a//b", path.as_bytes());
444 517 }
445 518
446 519 #[test]
447 520 fn test_relative_to() {
448 521 let path = HgPath::new(b"");
449 522 let base = HgPath::new(b"");
450 523 assert_eq!(Some(path), path.relative_to(base));
451 524
452 525 let path = HgPath::new(b"path");
453 526 let base = HgPath::new(b"");
454 527 assert_eq!(Some(path), path.relative_to(base));
455 528
456 529 let path = HgPath::new(b"a");
457 530 let base = HgPath::new(b"b");
458 531 assert_eq!(None, path.relative_to(base));
459 532
460 533 let path = HgPath::new(b"a/b");
461 534 let base = HgPath::new(b"a");
462 535 assert_eq!(None, path.relative_to(base));
463 536
464 537 let path = HgPath::new(b"a/b");
465 538 let base = HgPath::new(b"a/");
466 539 assert_eq!(Some(HgPath::new(b"b")), path.relative_to(base));
467 540
468 541 let path = HgPath::new(b"nested/path/to/b");
469 542 let base = HgPath::new(b"nested/path/");
470 543 assert_eq!(Some(HgPath::new(b"to/b")), path.relative_to(base));
471 544
472 545 let path = HgPath::new(b"ends/with/dir/");
473 546 let base = HgPath::new(b"ends/");
474 547 assert_eq!(Some(HgPath::new(b"with/dir/")), path.relative_to(base));
475 548 }
549
550 #[test]
551 #[cfg(unix)]
552 fn test_split_drive() {
553 // Taken from the Python stdlib's tests
554 assert_eq!(
555 HgPath::new(br"/foo/bar").split_drive(),
556 (HgPath::new(b""), HgPath::new(br"/foo/bar"))
557 );
558 assert_eq!(
559 HgPath::new(br"foo:bar").split_drive(),
560 (HgPath::new(b""), HgPath::new(br"foo:bar"))
561 );
562 assert_eq!(
563 HgPath::new(br":foo:bar").split_drive(),
564 (HgPath::new(b""), HgPath::new(br":foo:bar"))
565 );
566 // Also try NT paths; should not split them
567 assert_eq!(
568 HgPath::new(br"c:\foo\bar").split_drive(),
569 (HgPath::new(b""), HgPath::new(br"c:\foo\bar"))
570 );
571 assert_eq!(
572 HgPath::new(b"c:/foo/bar").split_drive(),
573 (HgPath::new(b""), HgPath::new(br"c:/foo/bar"))
574 );
575 assert_eq!(
576 HgPath::new(br"\\conky\mountpoint\foo\bar").split_drive(),
577 (
578 HgPath::new(b""),
579 HgPath::new(br"\\conky\mountpoint\foo\bar")
580 )
581 );
476 582 }
583
584 #[test]
585 #[cfg(windows)]
586 fn test_split_drive() {
587 assert_eq!(
588 HgPath::new(br"c:\foo\bar").split_drive(),
589 (HgPath::new(br"c:"), HgPath::new(br"\foo\bar"))
590 );
591 assert_eq!(
592 HgPath::new(b"c:/foo/bar").split_drive(),
593 (HgPath::new(br"c:"), HgPath::new(br"/foo/bar"))
594 );
595 assert_eq!(
596 HgPath::new(br"\\conky\mountpoint\foo\bar").split_drive(),
597 (
598 HgPath::new(br"\\conky\mountpoint"),
599 HgPath::new(br"\foo\bar")
600 )
601 );
602 assert_eq!(
603 HgPath::new(br"//conky/mountpoint/foo/bar").split_drive(),
604 (
605 HgPath::new(br"//conky/mountpoint"),
606 HgPath::new(br"/foo/bar")
607 )
608 );
609 assert_eq!(
610 HgPath::new(br"\\\conky\mountpoint\foo\bar").split_drive(),
611 (
612 HgPath::new(br""),
613 HgPath::new(br"\\\conky\mountpoint\foo\bar")
614 )
615 );
616 assert_eq!(
617 HgPath::new(br"///conky/mountpoint/foo/bar").split_drive(),
618 (
619 HgPath::new(br""),
620 HgPath::new(br"///conky/mountpoint/foo/bar")
621 )
622 );
623 assert_eq!(
624 HgPath::new(br"\\conky\\mountpoint\foo\bar").split_drive(),
625 (
626 HgPath::new(br""),
627 HgPath::new(br"\\conky\\mountpoint\foo\bar")
628 )
629 );
630 assert_eq!(
631 HgPath::new(br"//conky//mountpoint/foo/bar").split_drive(),
632 (
633 HgPath::new(br""),
634 HgPath::new(br"//conky//mountpoint/foo/bar")
635 )
636 );
637 // UNC part containing U+0130
638 assert_eq!(
639 HgPath::new(b"//conky/MOUNTPO\xc4\xb0NT/foo/bar").split_drive(),
640 (
641 HgPath::new(b"//conky/MOUNTPO\xc4\xb0NT"),
642 HgPath::new(br"/foo/bar")
643 )
644 );
645 }
646 }
General Comments 0
You need to be logged in to leave comments. Login now