path.rs 34.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

11 12 13 14 15 16
/*!

Cross-platform file path handling

*/

17 18
#[allow(missing_doc)];

19
use container::Container;
20
use cmp::Eq;
21
use iterator::IteratorUtil;
22
use libc;
23
use option::{None, Option, Some};
24
use str::{Str, StrSlice, StrVector};
25
use str;
26
use to_str::ToStr;
27
use ascii::{AsciiCast, AsciiStr};
28
use vec::{OwnedVector, ImmutableVector};
29

30 31 32 33 34
#[cfg(windows)]
pub use Path = self::WindowsPath;
#[cfg(unix)]
pub use Path = self::PosixPath;

35
#[deriving(Clone, Eq)]
36
pub struct WindowsPath {
37 38 39 40
    host: Option<~str>,
    device: Option<~str>,
    is_absolute: bool,
    components: ~[~str],
41 42
}

43
pub fn WindowsPath(s: &str) -> WindowsPath {
44
    GenericPath::from_str(s)
45 46
}

47
#[deriving(Clone, Eq)]
48
pub struct PosixPath {
49 50
    is_absolute: bool,
    components: ~[~str],
51 52
}

53
pub fn PosixPath(s: &str) -> PosixPath {
54
    GenericPath::from_str(s)
55 56
}

57
pub trait GenericPath {
58
    /// Converts a string to a Path
59
    fn from_str(&str) -> Self;
60

61
    /// Returns the directory component of `self`, as a string
62
    fn dirname(&self) -> ~str;
63 64
    /// Returns the file component of `self`, as a string option.
    /// Returns None if `self` names a directory.
65
    fn filename(&self) -> Option<~str>;
66 67 68 69
    /// Returns the stem of the file component of `self`, as a string option.
    /// The stem is the slice of a filename starting at 0 and ending just before
    /// the last '.' in the name.
    /// Returns None if `self` names a directory.
70
    fn filestem(&self) -> Option<~str>;
71 72 73 74
    /// Returns the type of the file component of `self`, as a string option.
    /// The file type is the slice of a filename starting just after the last
    /// '.' in the name and ending at the last index in the filename.
    /// Returns None if `self` names a directory.
75
    fn filetype(&self) -> Option<~str>;
76

77 78
    /// Returns a new path consisting of `self` with the parent directory component replaced
    /// with the given string.
79
    fn with_dirname(&self, (&str)) -> Self;
80 81
    /// Returns a new path consisting of `self` with the file component replaced
    /// with the given string.
82
    fn with_filename(&self, (&str)) -> Self;
83 84
    /// Returns a new path consisting of `self` with the file stem replaced
    /// with the given string.
85
    fn with_filestem(&self, (&str)) -> Self;
86 87
    /// Returns a new path consisting of `self` with the file type replaced
    /// with the given string.
88
    fn with_filetype(&self, (&str)) -> Self;
89

90 91
    /// Returns the directory component of `self`, as a new path.
    /// If `self` has no parent, returns `self`.
92
    fn dir_path(&self) -> Self;
93 94
    /// Returns the file component of `self`, as a new path.
    /// If `self` names a directory, returns the empty path.
95
    fn file_path(&self) -> Self;
96

97 98
    /// Returns a new Path whose parent directory is `self` and whose
    /// file component is the given string.
99
    fn push(&self, (&str)) -> Self;
100
    /// Returns a new Path consisting of the given path, made relative to `self`.
101
    fn push_rel(&self, (&Self)) -> Self;
102 103
    /// Returns a new Path consisting of the path given by the given vector
    /// of strings, relative to `self`.
104
    fn push_many<S: Str>(&self, (&[S])) -> Self;
105 106
    /// Identical to `dir_path` except in the case where `self` has only one
    /// component. In this case, `pop` returns the empty path.
107
    fn pop(&self) -> Self;
108

109 110
    /// The same as `push_rel`, except that the directory argument must not
    /// contain directory separators in any of its components.
111
    fn unsafe_join(&self, (&Self)) -> Self;
112 113 114
    /// On Unix, always returns false. On Windows, returns true iff `self`'s
    /// file stem is one of: `con` `aux` `com1` `com2` `com3` `com4`
    /// `lpt1` `lpt2` `lpt3` `prn` `nul`
115
    fn is_restricted(&self) -> bool;
116

117 118 119
    /// Returns a new path that names the same file as `self`, without containing
    /// any '.', '..', or empty components. On Windows, uppercases the drive letter
    /// as well.
120
    fn normalize(&self) -> Self;
121

122
    /// Returns `true` if `self` is an absolute path.
123
    fn is_absolute(&self) -> bool;
124 125
}

126
#[cfg(target_os = "linux")]
K
kyeongwoon 已提交
127
#[cfg(target_os = "android")]
128 129 130
mod stat {
    #[cfg(target_arch = "x86")]
    pub mod arch {
131 132
        use libc;

133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
        pub fn default_stat() -> libc::stat {
            libc::stat {
                st_dev: 0,
                __pad1: 0,
                st_ino: 0,
                st_mode: 0,
                st_nlink: 0,
                st_uid: 0,
                st_gid: 0,
                st_rdev: 0,
                __pad2: 0,
                st_size: 0,
                st_blksize: 0,
                st_blocks: 0,
                st_atime: 0,
                st_atime_nsec: 0,
                st_mtime: 0,
                st_mtime_nsec: 0,
                st_ctime: 0,
                st_ctime_nsec: 0,
                __unused4: 0,
                __unused5: 0,
            }
        }
    }

159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
    #[cfg(target_arch = "arm")]
    pub mod arch {
        use libc;

        pub fn default_stat() -> libc::stat {
            libc::stat {
                st_dev: 0,
                __pad0: [0, ..4],
                __st_ino: 0,
                st_mode: 0,
                st_nlink: 0,
                st_uid: 0,
                st_gid: 0,
                st_rdev: 0,
                __pad3: [0, ..4],
                st_size: 0,
                st_blksize: 0,
                st_blocks: 0,
                st_atime: 0,
                st_atime_nsec: 0,
                st_mtime: 0,
                st_mtime_nsec: 0,
                st_ctime: 0,
                st_ctime_nsec: 0,
                st_ino: 0
            }
        }
    }

188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
    #[cfg(target_arch = "mips")]
    pub mod arch {
        use libc;

        pub fn default_stat() -> libc::stat {
            libc::stat {
                st_dev: 0,
                st_pad1: [0, ..3],
                st_ino: 0,
                st_mode: 0,
                st_nlink: 0,
                st_uid: 0,
                st_gid: 0,
                st_rdev: 0,
                st_pad2: [0, ..2],
                st_size: 0,
                st_pad3: 0,
                st_atime: 0,
                st_atime_nsec: 0,
                st_mtime: 0,
                st_mtime_nsec: 0,
                st_ctime: 0,
                st_ctime_nsec: 0,
                st_blksize: 0,
                st_blocks: 0,
                st_pad5: [0, ..14],
            }
        }
    }

218 219
    #[cfg(target_arch = "x86_64")]
    pub mod arch {
220 221
        use libc;

222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
        pub fn default_stat() -> libc::stat {
            libc::stat {
                st_dev: 0,
                st_ino: 0,
                st_nlink: 0,
                st_mode: 0,
                st_uid: 0,
                st_gid: 0,
                __pad0: 0,
                st_rdev: 0,
                st_size: 0,
                st_blksize: 0,
                st_blocks: 0,
                st_atime: 0,
                st_atime_nsec: 0,
                st_mtime: 0,
                st_mtime_nsec: 0,
                st_ctime: 0,
                st_ctime_nsec: 0,
                __unused: [0, 0, 0],
            }
        }
    }
}

#[cfg(target_os = "freebsd")]
mod stat {
    #[cfg(target_arch = "x86_64")]
    pub mod arch {
251 252
        use libc;

253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
        pub fn default_stat() -> libc::stat {
            libc::stat {
                st_dev: 0,
                st_ino: 0,
                st_mode: 0,
                st_nlink: 0,
                st_uid: 0,
                st_gid: 0,
                st_rdev: 0,
                st_atime: 0,
                st_atime_nsec: 0,
                st_mtime: 0,
                st_mtime_nsec: 0,
                st_ctime: 0,
                st_ctime_nsec: 0,
                st_size: 0,
                st_blocks: 0,
                st_blksize: 0,
                st_flags: 0,
                st_gen: 0,
                st_lspare: 0,
                st_birthtime: 0,
                st_birthtime_nsec: 0,
                __unused: [0, 0],
            }
        }
    }
}

#[cfg(target_os = "macos")]
mod stat {
    pub mod arch {
285 286
        use libc;

287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
        pub fn default_stat() -> libc::stat {
            libc::stat {
                st_dev: 0,
                st_mode: 0,
                st_nlink: 0,
                st_ino: 0,
                st_uid: 0,
                st_gid: 0,
                st_rdev: 0,
                st_atime: 0,
                st_atime_nsec: 0,
                st_mtime: 0,
                st_mtime_nsec: 0,
                st_ctime: 0,
                st_ctime_nsec: 0,
                st_birthtime: 0,
                st_birthtime_nsec: 0,
                st_size: 0,
                st_blocks: 0,
                st_blksize: 0,
                st_flags: 0,
                st_gen: 0,
                st_lspare: 0,
                st_qspare: [0, 0],
            }
        }
    }
}

316
#[cfg(target_os = "win32")]
317 318
mod stat {
    pub mod arch {
319
        use libc;
320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
        pub fn default_stat() -> libc::stat {
            libc::stat {
                st_dev: 0,
                st_ino: 0,
                st_mode: 0,
                st_nlink: 0,
                st_uid: 0,
                st_gid: 0,
                st_rdev: 0,
                st_size: 0,
                st_atime: 0,
                st_mtime: 0,
                st_ctime: 0,
            }
        }
    }
}


339 340
impl Path {
    pub fn stat(&self) -> Option<libc::stat> {
341 342 343
        unsafe {
             do str::as_c_str(self.to_str()) |buf| {
                let mut st = stat::arch::default_stat();
344 345 346 347
                match libc::stat(buf, &mut st) {
                    0 => Some(st),
                    _ => None,
                }
348
            }
349 350 351
        }
    }

352
    #[cfg(unix)]
353
    pub fn lstat(&self) -> Option<libc::stat> {
354 355 356
        unsafe {
            do str::as_c_str(self.to_str()) |buf| {
                let mut st = stat::arch::default_stat();
357 358 359 360
                match libc::lstat(buf, &mut st) {
                    0 => Some(st),
                    _ => None,
                }
361
            }
362 363 364
        }
    }

365
    pub fn exists(&self) -> bool {
366 367 368 369 370 371
        match self.stat() {
            None => false,
            Some(_) => true,
        }
    }

372
    pub fn get_size(&self) -> Option<i64> {
373 374 375 376 377 378
        match self.stat() {
            None => None,
            Some(ref st) => Some(st.st_size as i64),
        }
    }

379
    pub fn get_mode(&self) -> Option<uint> {
380 381 382 383 384
        match self.stat() {
            None => None,
            Some(ref st) => Some(st.st_mode as uint),
        }
    }
T
Tim Chevalier 已提交
385 386 387 388 389 390 391 392 393

    /// Execute a function on p as well as all of its ancestors
    pub fn each_parent(&self, f: &fn(&Path)) {
        if !self.components.is_empty() {
            f(self);
            self.pop().each_parent(f);
        }
    }

394 395 396 397 398
}

#[cfg(target_os = "freebsd")]
#[cfg(target_os = "linux")]
#[cfg(target_os = "macos")]
399 400
impl Path {
    pub fn get_atime(&self) -> Option<(i64, int)> {
401 402 403 404 405 406 407 408 409
        match self.stat() {
            None => None,
            Some(ref st) => {
                Some((st.st_atime as i64,
                      st.st_atime_nsec as int))
            }
        }
    }

410
    pub fn get_mtime(&self) -> Option<(i64, int)> {
411 412 413 414 415 416 417 418 419
        match self.stat() {
            None => None,
            Some(ref st) => {
                Some((st.st_mtime as i64,
                      st.st_mtime_nsec as int))
            }
        }
    }

420
    pub fn get_ctime(&self) -> Option<(i64, int)> {
421 422 423 424 425 426 427 428 429 430 431 432
        match self.stat() {
            None => None,
            Some(ref st) => {
                Some((st.st_ctime as i64,
                      st.st_ctime_nsec as int))
            }
        }
    }
}

#[cfg(target_os = "freebsd")]
#[cfg(target_os = "macos")]
433 434
impl Path {
    pub fn get_birthtime(&self) -> Option<(i64, int)> {
435 436 437 438 439 440 441 442 443 444 445
        match self.stat() {
            None => None,
            Some(ref st) => {
                Some((st.st_birthtime as i64,
                      st.st_birthtime_nsec as int))
            }
        }
    }
}

#[cfg(target_os = "win32")]
446 447
impl Path {
    pub fn get_atime(&self) -> Option<(i64, int)> {
448 449 450 451 452 453 454 455
        match self.stat() {
            None => None,
            Some(ref st) => {
                Some((st.st_atime as i64, 0))
            }
        }
    }

456
    pub fn get_mtime(&self) -> Option<(i64, int)> {
457 458 459 460 461 462 463 464
        match self.stat() {
            None => None,
            Some(ref st) => {
                Some((st.st_mtime as i64, 0))
            }
        }
    }

465
    pub fn get_ctime(&self) -> Option<(i64, int)> {
466 467 468 469 470 471 472 473 474
        match self.stat() {
            None => None,
            Some(ref st) => {
                Some((st.st_ctime as i64, 0))
            }
        }
    }
}

475
impl ToStr for PosixPath {
476
    fn to_str(&self) -> ~str {
477 478
        let mut s = ~"";
        if self.is_absolute {
479
            s.push_str("/");
480
        }
481
        s + self.components.connect("/")
482
    }
483 484
}

485 486
// FIXME (#3227): when default methods in traits are working, de-duplicate
// PosixPath and WindowsPath, most of their methods are common.
487
impl GenericPath for PosixPath {
488
    fn from_str(s: &str) -> PosixPath {
489 490 491
        let components = s.split_iter('/')
            .filter_map(|s| if s.is_empty() {None} else {Some(s.to_owned())})
            .collect();
492
        let is_absolute = (s.len() != 0 && s[0] == '/' as u8);
493 494 495 496
        PosixPath {
            is_absolute: is_absolute,
            components: components,
        }
497
    }
498

499
    fn dirname(&self) -> ~str {
500
        let s = self.dir_path().to_str();
501 502 503
        match s.len() {
            0 => ~".",
            _ => s,
504 505
        }
    }
506

507
    fn filename(&self) -> Option<~str> {
508
        match self.components.len() {
509 510
            0 => None,
            n => Some(copy self.components[n - 1]),
511 512
        }
    }
513

514
    fn filestem(&self) -> Option<~str> {
515
        match self.filename() {
516 517
            None => None,
            Some(ref f) => {
518 519
                match f.rfind('.') {
                    Some(p) => Some(f.slice_to(p).to_owned()),
520 521
                    None => Some(copy *f),
                }
522
            }
523 524
        }
    }
525

526
    fn filetype(&self) -> Option<~str> {
527
        match self.filename() {
528 529
            None => None,
            Some(ref f) => {
530 531
                match f.rfind('.') {
                    Some(p) if p < f.len() => Some(f.slice_from(p).to_owned()),
532 533
                    _ => None,
                }
534 535 536 537
            }
        }
    }

538
    fn with_dirname(&self, d: &str) -> PosixPath {
539
        let dpath = PosixPath(d);
540
        match self.filename() {
541 542
            Some(ref f) => dpath.push(*f),
            None => dpath,
543
        }
544 545
    }

546
    fn with_filename(&self, f: &str) -> PosixPath {
547
        assert!(! f.iter().all(windows::is_sep));
548
        self.dir_path().push(f)
549
    }
550

551
    fn with_filestem(&self, s: &str) -> PosixPath {
552
        match self.filetype() {
553
            None => self.with_filename(s),
554
            Some(ref t) => self.with_filename(s.to_owned() + *t),
555
        }
556 557
    }

558
    fn with_filetype(&self, t: &str) -> PosixPath {
559 560 561 562 563
        match (t.len(), self.filestem()) {
            (0, None)        => copy *self,
            (0, Some(ref s)) => self.with_filename(*s),
            (_, None)        => self.with_filename(fmt!(".%s", t)),
            (_, Some(ref s)) => self.with_filename(fmt!("%s.%s", *s, t)),
564 565 566
        }
    }

567
    fn dir_path(&self) -> PosixPath {
568 569 570
        match self.components.len() {
            0 => copy *self,
            _ => self.pop(),
571 572 573
        }
    }

574
    fn file_path(&self) -> PosixPath {
575 576 577 578
        let cs = match self.filename() {
          None => ~[],
          Some(ref f) => ~[copy *f]
        };
579 580 581 582
        PosixPath {
            is_absolute: false,
            components: cs,
        }
583 584
    }

585
    fn push_rel(&self, other: &PosixPath) -> PosixPath {
P
Patrick Walton 已提交
586
        assert!(!other.is_absolute);
587
        self.push_many(other.components)
588 589
    }

590
    fn unsafe_join(&self, other: &PosixPath) -> PosixPath {
591
        if other.is_absolute {
592 593 594 595
            PosixPath {
                is_absolute: true,
                components: copy other.components,
            }
596 597 598 599 600
        } else {
            self.push_rel(other)
        }
    }

601
    fn is_restricted(&self) -> bool {
A
Armin Ronacher 已提交
602 603 604
        false
    }

605
    fn push_many<S: Str>(&self, cs: &[S]) -> PosixPath {
606
        let mut v = copy self.components;
607
        for cs.iter().advance |e| {
608
            for e.as_slice().split_iter(windows::is_sep).advance |s| {
609 610 611
                if !s.is_empty() {
                    v.push(s.to_owned())
                }
612
            }
613
        }
614 615 616 617
        PosixPath {
            is_absolute: self.is_absolute,
            components: v,
        }
618 619
    }

620
    fn push(&self, s: &str) -> PosixPath {
621
        let mut v = copy self.components;
622 623 624 625
        for s.split_iter(windows::is_sep).advance |s| {
            if !s.is_empty() {
                v.push(s.to_owned())
            }
626
        }
B
Ben Striegel 已提交
627
        PosixPath { components: v, ..copy *self }
628 629
    }

630
    fn pop(&self) -> PosixPath {
631 632
        let mut cs = copy self.components;
        if cs.len() != 0 {
633
            cs.pop();
634
        }
635
        PosixPath {
T
Tim Chevalier 已提交
636
            is_absolute: self.is_absolute,
637 638
            components: cs,
        } //..self }
639
    }
640

641
    fn normalize(&self) -> PosixPath {
642
        PosixPath {
T
Tim Chevalier 已提交
643
            is_absolute: self.is_absolute,
644 645
            components: normalize(self.components),
        } // ..self }
646
    }
647 648 649 650

    fn is_absolute(&self) -> bool {
        self.is_absolute
    }
651
}
652

653

654
impl ToStr for WindowsPath {
655
    fn to_str(&self) -> ~str {
656 657
        let mut s = ~"";
        match self.host {
658 659 660 661
          Some(ref h) => {
            s.push_str("\\\\");
            s.push_str(*h);
          }
662 663 664
          None => { }
        }
        match self.device {
665 666 667 668
          Some(ref d) => {
            s.push_str(*d);
            s.push_str(":");
          }
669 670 671
          None => { }
        }
        if self.is_absolute {
672
            s.push_str("\\");
673
        }
674
        s + self.components.connect("\\")
675
    }
676
}
677

678

679
impl GenericPath for WindowsPath {
680
    fn from_str(s: &str) -> WindowsPath {
681 682 683 684
        let host;
        let device;
        let rest;

685 686 687 688 689 690 691 692 693 694
        match (
            windows::extract_drive_prefix(s),
            windows::extract_unc_prefix(s),
        ) {
            (Some((ref d, ref r)), _) => {
                host = None;
                device = Some(copy *d);
                rest = copy *r;
            }
            (None, Some((ref h, ref r))) => {
695 696 697
                host = Some(copy *h);
                device = None;
                rest = copy *r;
698 699
            }
            (None, None) => {
700 701
                host = None;
                device = None;
702
                rest = s.to_owned();
703 704 705
            }
        }

706 707 708 709 710
        let components = rest.split_iter(windows::is_sep)
            .filter_map(|s| if s.is_empty() {None} else {Some(s.to_owned())})
            .collect();

        let is_absolute = (rest.len() != 0 && windows::is_sep(rest[0] as char));
711 712 713 714 715 716
        WindowsPath {
            host: host,
            device: device,
            is_absolute: is_absolute,
            components: components,
        }
717 718
    }

719
    fn dirname(&self) -> ~str {
720
        let s = self.dir_path().to_str();
721 722 723
        match s.len() {
            0 => ~".",
            _ => s,
724
        }
725 726
    }

727
    fn filename(&self) -> Option<~str> {
728
        match self.components.len() {
729 730
            0 => None,
            n => Some(copy self.components[n - 1]),
731
        }
732 733
    }

734
    fn filestem(&self) -> Option<~str> {
735
        match self.filename() {
736 737
            None => None,
            Some(ref f) => {
738 739
                match f.rfind('.') {
                    Some(p) => Some(f.slice_to(p).to_owned()),
740 741
                    None => Some(copy *f),
                }
742 743
            }
        }
744 745
    }

746
    fn filetype(&self) -> Option<~str> {
747 748 749
        match self.filename() {
          None => None,
          Some(ref f) => {
750 751
            match f.rfind('.') {
                Some(p) if p < f.len() => Some(f.slice_from(p).to_owned()),
752
                _ => None,
753 754 755
            }
          }
        }
756 757
    }

758
    fn with_dirname(&self, d: &str) -> WindowsPath {
759
        let dpath = WindowsPath(d);
760
        match self.filename() {
761 762
            Some(ref f) => dpath.push(*f),
            None => dpath,
763
        }
764 765
    }

766
    fn with_filename(&self, f: &str) -> WindowsPath {
767
        assert!(! f.iter().all(windows::is_sep));
768
        self.dir_path().push(f)
769 770
    }

771
    fn with_filestem(&self, s: &str) -> WindowsPath {
772
        match self.filetype() {
773
            None => self.with_filename(s),
774
            Some(ref t) => self.with_filename(s.to_owned() + *t),
775
        }
776 777
    }

778
    fn with_filetype(&self, t: &str) -> WindowsPath {
779 780 781 782 783
        match (t.len(), self.filestem()) {
            (0, None)        => copy *self,
            (0, Some(ref s)) => self.with_filename(*s),
            (_, None)        => self.with_filename(fmt!(".%s", t)),
            (_, Some(ref s)) => self.with_filename(fmt!("%s.%s", *s, t)),
784
        }
785 786
    }

787
    fn dir_path(&self) -> WindowsPath {
788 789 790
        match self.components.len() {
            0 => copy *self,
            _ => self.pop(),
791
        }
792 793
    }

794
    fn file_path(&self) -> WindowsPath {
795 796 797 798 799 800 801 802 803
        WindowsPath {
            host: None,
            device: None,
            is_absolute: false,
            components: match self.filename() {
                None => ~[],
                Some(ref f) => ~[copy *f],
            }
        }
804 805
    }

806
    fn push_rel(&self, other: &WindowsPath) -> WindowsPath {
P
Patrick Walton 已提交
807
        assert!(!other.is_absolute);
808
        self.push_many(other.components)
809 810
    }

811
    fn unsafe_join(&self, other: &WindowsPath) -> WindowsPath {
812
        /* rhs not absolute is simple push */
813
        if !other.is_absolute {
814 815 816 817 818
            return self.push_many(other.components);
        }

        /* if rhs has a host set, then the whole thing wins */
        match other.host {
819
            Some(ref host) => {
820
                return WindowsPath {
821
                    host: Some(copy *host),
822 823
                    device: copy other.device,
                    is_absolute: true,
824
                    components: copy other.components,
825 826 827 828 829 830 831
                };
            }
            _ => {}
        }

        /* if rhs has a device set, then a part wins */
        match other.device {
832
            Some(ref device) => {
833 834
                return WindowsPath {
                    host: None,
835
                    device: Some(copy *device),
836
                    is_absolute: true,
837
                    components: copy other.components,
838
                };
839
            }
840 841 842 843 844 845 846 847 848
            _ => {}
        }

        /* fallback: host and device of lhs win, but the
           whole path of the right */
        WindowsPath {
            host: copy self.host,
            device: copy self.device,
            is_absolute: self.is_absolute || other.is_absolute,
849
            components: copy other.components,
850 851 852
        }
    }

853
    fn is_restricted(&self) -> bool {
A
Armin Ronacher 已提交
854 855
        match self.filestem() {
            Some(stem) => {
856 857
                // FIXME: #4318 Instead of to_ascii and to_str_ascii, could use
                // to_ascii_consume and to_str_consume to not do a unnecessary copy.
858
                match stem.to_ascii().to_lower().to_str_ascii() {
A
Armin Ronacher 已提交
859 860 861 862 863 864 865 866 867
                    ~"con" | ~"aux" | ~"com1" | ~"com2" | ~"com3" | ~"com4" |
                    ~"lpt1" | ~"lpt2" | ~"lpt3" | ~"prn" | ~"nul" => true,
                    _ => false
                }
            },
            None => false
        }
    }

868
    fn push_many<S: Str>(&self, cs: &[S]) -> WindowsPath {
869
        let mut v = copy self.components;
870
        for cs.iter().advance |e| {
871
            for e.as_slice().split_iter(windows::is_sep).advance |s| {
872 873 874
                if !s.is_empty() {
                    v.push(s.to_owned())
                }
875
            }
876
        }
T
Tim Chevalier 已提交
877
        // tedious, but as-is, we can't use ..self
878
        WindowsPath {
T
Tim Chevalier 已提交
879 880 881
            host: copy self.host,
            device: copy self.device,
            is_absolute: self.is_absolute,
L
Luqman Aden 已提交
882
            components: v
T
Tim Chevalier 已提交
883
        }
884 885
    }

886
    fn push(&self, s: &str) -> WindowsPath {
887
        let mut v = copy self.components;
888 889 890 891
        for s.split_iter(windows::is_sep).advance |s| {
            if !s.is_empty() {
                v.push(s.to_owned())
            }
892
        }
893
        WindowsPath { components: v, ..copy *self }
894 895
    }

896
    fn pop(&self) -> WindowsPath {
897 898
        let mut cs = copy self.components;
        if cs.len() != 0 {
899
            cs.pop();
900
        }
901
        WindowsPath {
T
Tim Chevalier 已提交
902 903 904
            host: copy self.host,
            device: copy self.device,
            is_absolute: self.is_absolute,
905
            components: cs,
T
Tim Chevalier 已提交
906
        }
907
    }
908

909
    fn normalize(&self) -> WindowsPath {
910
        WindowsPath {
T
Tim Chevalier 已提交
911
            host: copy self.host,
912 913
            device: match self.device {
                None => None,
914

915 916
                // FIXME: #4318 Instead of to_ascii and to_str_ascii, could use
                // to_ascii_consume and to_str_consume to not do a unnecessary copy.
917
                Some(ref device) => Some(device.to_ascii().to_upper().to_str_ascii())
918
            },
T
Tim Chevalier 已提交
919 920
            is_absolute: self.is_absolute,
            components: normalize(self.components)
921 922
        }
    }
923 924 925 926

    fn is_absolute(&self) -> bool {
        self.is_absolute
    }
927
}
928

929

930
pub fn normalize(components: &[~str]) -> ~[~str] {
931
    let mut cs = ~[];
932
    for components.iter().advance |c| {
933 934 935 936 937 938 939
        if *c == ~"." && components.len() > 1 { loop; }
        if *c == ~"" { loop; }
        if *c == ~".." && cs.len() != 0 {
            cs.pop();
            loop;
        }
        cs.push(copy *c);
940
    }
L
Luqman Aden 已提交
941
    cs
942 943
}

E
Erick Tryzelaar 已提交
944
// Various windows helpers, and tests for the impl.
945
pub mod windows {
946
    use libc;
947
    use option::{None, Option, Some};
948

949
    #[inline]
950 951
    pub fn is_sep(u: char) -> bool {
        u == '/' || u == '\\'
E
Erick Tryzelaar 已提交
952
    }
953

954
    pub fn extract_unc_prefix(s: &str) -> Option<(~str,~str)> {
E
Erick Tryzelaar 已提交
955
        if (s.len() > 1 &&
956 957
            (s[0] == '\\' as u8 || s[0] == '/' as u8) &&
            s[0] == s[1]) {
E
Erick Tryzelaar 已提交
958 959
            let mut i = 2;
            while i < s.len() {
960
                if is_sep(s[i] as char) {
961
                    let pre = s.slice(2, i).to_owned();
962
                    let rest = s.slice(i, s.len()).to_owned();
L
Luqman Aden 已提交
963
                    return Some((pre, rest));
E
Erick Tryzelaar 已提交
964 965 966 967 968 969
                }
                i += 1;
            }
        }
        None
    }
970

971
    pub fn extract_drive_prefix(s: &str) -> Option<(~str,~str)> {
E
Erick Tryzelaar 已提交
972 973 974 975 976 977 978
        unsafe {
            if (s.len() > 1 &&
                libc::isalpha(s[0] as libc::c_int) != 0 &&
                s[1] == ':' as u8) {
                let rest = if s.len() == 2 {
                    ~""
                } else {
979
                    s.slice(2, s.len()).to_owned()
E
Erick Tryzelaar 已提交
980
                };
981
                return Some((s.slice(0,1).to_owned(), rest));
E
Erick Tryzelaar 已提交
982 983
            }
            None
984
        }
985
    }
E
Erick Tryzelaar 已提交
986 987
}

988
#[cfg(test)]
E
Erick Tryzelaar 已提交
989
mod tests {
990 991
    use option::{None, Some};
    use path::{PosixPath, WindowsPath, windows};
992

E
Erick Tryzelaar 已提交
993 994 995 996 997
    #[test]
    fn test_double_slash_collapsing() {
        let path = PosixPath("tmp/");
        let path = path.push("/hmm");
        let path = path.normalize();
998
        assert_eq!(~"tmp/hmm", path.to_str());
E
Erick Tryzelaar 已提交
999 1000 1001 1002

        let path = WindowsPath("tmp/");
        let path = path.push("/hmm");
        let path = path.normalize();
1003
        assert_eq!(~"tmp\\hmm", path.to_str());
E
Erick Tryzelaar 已提交
1004
    }
1005

1006 1007
    #[test]
    fn test_filetype_foo_bar() {
E
Erick Tryzelaar 已提交
1008
        let wp = PosixPath("foo.bar");
1009
        assert_eq!(wp.filetype(), Some(~".bar"));
E
Erick Tryzelaar 已提交
1010 1011

        let wp = WindowsPath("foo.bar");
1012
        assert_eq!(wp.filetype(), Some(~".bar"));
1013 1014 1015 1016
    }

    #[test]
    fn test_filetype_foo() {
E
Erick Tryzelaar 已提交
1017
        let wp = PosixPath("foo");
1018
        assert_eq!(wp.filetype(), None);
E
Erick Tryzelaar 已提交
1019 1020

        let wp = WindowsPath("foo");
1021
        assert_eq!(wp.filetype(), None);
1022 1023
    }

1024 1025
    #[test]
    fn test_posix_paths() {
E
Erick Tryzelaar 已提交
1026 1027
        fn t(wp: &PosixPath, s: &str) {
            let ss = wp.to_str();
1028
            let sss = s.to_owned();
E
Erick Tryzelaar 已提交
1029 1030 1031
            if (ss != sss) {
                debug!("got %s", ss);
                debug!("expected %s", sss);
1032
                assert_eq!(ss, sss);
E
Erick Tryzelaar 已提交
1033 1034 1035 1036 1037 1038 1039
            }
        }

        t(&(PosixPath("hi")), "hi");
        t(&(PosixPath("/lib")), "/lib");
        t(&(PosixPath("hi/there")), "hi/there");
        t(&(PosixPath("hi/there.txt")), "hi/there.txt");
1040

E
Erick Tryzelaar 已提交
1041 1042
        t(&(PosixPath("hi/there.txt")), "hi/there.txt");
        t(&(PosixPath("hi/there.txt")
1043 1044
           .with_filetype("")), "hi/there");

E
Erick Tryzelaar 已提交
1045
        t(&(PosixPath("/a/b/c/there.txt")
1046 1047
            .with_dirname("hi")), "hi/there.txt");

E
Erick Tryzelaar 已提交
1048
        t(&(PosixPath("hi/there.txt")
1049
            .with_dirname(".")), "./there.txt");
1050

E
Erick Tryzelaar 已提交
1051
        t(&(PosixPath("a/b/c")
1052
            .push("..")), "a/b/c/..");
1053

E
Erick Tryzelaar 已提交
1054
        t(&(PosixPath("there.txt")
1055 1056
            .with_filetype("o")), "there.o");

E
Erick Tryzelaar 已提交
1057
        t(&(PosixPath("hi/there.txt")
1058 1059
            .with_filetype("o")), "hi/there.o");

E
Erick Tryzelaar 已提交
1060
        t(&(PosixPath("hi/there.txt")
1061 1062 1063 1064
            .with_filetype("o")
            .with_dirname("/usr/lib")),
          "/usr/lib/there.o");

E
Erick Tryzelaar 已提交
1065
        t(&(PosixPath("hi/there.txt")
1066 1067 1068 1069
            .with_filetype("o")
            .with_dirname("/usr/lib/")),
          "/usr/lib/there.o");

E
Erick Tryzelaar 已提交
1070
        t(&(PosixPath("hi/there.txt")
1071 1072 1073 1074
            .with_filetype("o")
            .with_dirname("/usr//lib//")),
            "/usr/lib/there.o");

E
Erick Tryzelaar 已提交
1075
        t(&(PosixPath("/usr/bin/rust")
1076 1077 1078 1079
            .push_many([~"lib", ~"thingy.so"])
            .with_filestem("librustc")),
          "/usr/bin/rust/lib/librustc.so");

1080 1081
    }

1082 1083
    #[test]
    fn test_normalize() {
E
Erick Tryzelaar 已提交
1084 1085
        fn t(wp: &PosixPath, s: &str) {
            let ss = wp.to_str();
1086
            let sss = s.to_owned();
E
Erick Tryzelaar 已提交
1087 1088 1089
            if (ss != sss) {
                debug!("got %s", ss);
                debug!("expected %s", sss);
1090
                assert_eq!(ss, sss);
E
Erick Tryzelaar 已提交
1091 1092 1093 1094
            }
        }

        t(&(PosixPath("hi/there.txt")
1095 1096
            .with_dirname(".").normalize()), "there.txt");

E
Erick Tryzelaar 已提交
1097
        t(&(PosixPath("a/b/../c/././/../foo.txt/").normalize()),
1098 1099
          "a/foo.txt");

E
Erick Tryzelaar 已提交
1100
        t(&(PosixPath("a/b/c")
1101 1102
            .push("..").normalize()), "a/b");
    }
1103 1104

    #[test]
1105
    fn test_extract_unc_prefixes() {
P
Patrick Walton 已提交
1106 1107 1108 1109 1110
        assert!(windows::extract_unc_prefix("\\\\").is_none());
        assert!(windows::extract_unc_prefix("//").is_none());
        assert!(windows::extract_unc_prefix("\\\\hi").is_none());
        assert!(windows::extract_unc_prefix("//hi").is_none());
        assert!(windows::extract_unc_prefix("\\\\hi\\") ==
1111
            Some((~"hi", ~"\\")));
P
Patrick Walton 已提交
1112
        assert!(windows::extract_unc_prefix("//hi\\") ==
1113
            Some((~"hi", ~"\\")));
P
Patrick Walton 已提交
1114
        assert!(windows::extract_unc_prefix("\\\\hi\\there") ==
1115
            Some((~"hi", ~"\\there")));
P
Patrick Walton 已提交
1116
        assert!(windows::extract_unc_prefix("//hi/there") ==
1117
            Some((~"hi", ~"/there")));
P
Patrick Walton 已提交
1118
        assert!(windows::extract_unc_prefix(
1119 1120
            "\\\\hi\\there\\friends.txt") ==
            Some((~"hi", ~"\\there\\friends.txt")));
P
Patrick Walton 已提交
1121
        assert!(windows::extract_unc_prefix(
1122 1123
            "//hi\\there\\friends.txt") ==
            Some((~"hi", ~"\\there\\friends.txt")));
1124 1125 1126
    }

    #[test]
1127
    fn test_extract_drive_prefixes() {
P
Patrick Walton 已提交
1128 1129
        assert!(windows::extract_drive_prefix("c").is_none());
        assert!(windows::extract_drive_prefix("c:") ==
1130
                     Some((~"c", ~"")));
P
Patrick Walton 已提交
1131
        assert!(windows::extract_drive_prefix("d:") ==
1132
                     Some((~"d", ~"")));
P
Patrick Walton 已提交
1133
        assert!(windows::extract_drive_prefix("z:") ==
1134
                     Some((~"z", ~"")));
P
Patrick Walton 已提交
1135
        assert!(windows::extract_drive_prefix("c:\\hi") ==
1136
                     Some((~"c", ~"\\hi")));
P
Patrick Walton 已提交
1137
        assert!(windows::extract_drive_prefix("d:hi") ==
1138
                     Some((~"d", ~"hi")));
P
Patrick Walton 已提交
1139
        assert!(windows::extract_drive_prefix("c:hi\\there.txt") ==
1140
                     Some((~"c", ~"hi\\there.txt")));
P
Patrick Walton 已提交
1141
        assert!(windows::extract_drive_prefix("c:\\hi\\there.txt") ==
1142
                     Some((~"c", ~"\\hi\\there.txt")));
1143 1144 1145
    }

    #[test]
1146 1147 1148
    fn test_windows_paths() {
        fn t(wp: &WindowsPath, s: &str) {
            let ss = wp.to_str();
1149
            let sss = s.to_owned();
1150 1151 1152
            if (ss != sss) {
                debug!("got %s", ss);
                debug!("expected %s", sss);
1153
                assert_eq!(ss, sss);
1154 1155 1156
            }
        }

E
Erick Tryzelaar 已提交
1157 1158 1159
        t(&(WindowsPath("hi")), "hi");
        t(&(WindowsPath("hi/there")), "hi\\there");
        t(&(WindowsPath("hi/there.txt")), "hi\\there.txt");
1160

E
Erick Tryzelaar 已提交
1161
        t(&(WindowsPath("there.txt")
1162 1163
            .with_filetype("o")), "there.o");

E
Erick Tryzelaar 已提交
1164
        t(&(WindowsPath("hi/there.txt")
1165 1166
            .with_filetype("o")), "hi\\there.o");

E
Erick Tryzelaar 已提交
1167
        t(&(WindowsPath("hi/there.txt")
1168 1169 1170 1171
            .with_filetype("o")
            .with_dirname("c:\\program files A")),
          "c:\\program files A\\there.o");

E
Erick Tryzelaar 已提交
1172
        t(&(WindowsPath("hi/there.txt")
1173 1174 1175 1176
            .with_filetype("o")
            .with_dirname("c:\\program files B\\")),
          "c:\\program files B\\there.o");

E
Erick Tryzelaar 已提交
1177
        t(&(WindowsPath("hi/there.txt")
1178 1179 1180 1181
            .with_filetype("o")
            .with_dirname("c:\\program files C\\/")),
            "c:\\program files C\\there.o");

E
Erick Tryzelaar 已提交
1182
        t(&(WindowsPath("c:\\program files (x86)\\rust")
1183 1184 1185
            .push_many([~"lib", ~"thingy.dll"])
            .with_filename("librustc.dll")),
          "c:\\program files (x86)\\rust\\lib\\librustc.dll");
1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233

        t(&(WindowsPath("\\\\computer\\share")
            .unsafe_join(&WindowsPath("\\a"))),
          "\\\\computer\\a");

        t(&(WindowsPath("//computer/share")
            .unsafe_join(&WindowsPath("\\a"))),
          "\\\\computer\\a");

        t(&(WindowsPath("//computer/share")
            .unsafe_join(&WindowsPath("\\\\computer\\share"))),
          "\\\\computer\\share");

        t(&(WindowsPath("C:/whatever")
            .unsafe_join(&WindowsPath("//computer/share/a/b"))),
          "\\\\computer\\share\\a\\b");

        t(&(WindowsPath("C:")
            .unsafe_join(&WindowsPath("D:/foo"))),
          "D:\\foo");

        t(&(WindowsPath("C:")
            .unsafe_join(&WindowsPath("B"))),
          "C:B");

        t(&(WindowsPath("C:")
            .unsafe_join(&WindowsPath("/foo"))),
          "C:\\foo");

        t(&(WindowsPath("C:\\")
            .unsafe_join(&WindowsPath("\\bar"))),
          "C:\\bar");

        t(&(WindowsPath("")
            .unsafe_join(&WindowsPath(""))),
          "");

        t(&(WindowsPath("")
            .unsafe_join(&WindowsPath("a"))),
          "a");

        t(&(WindowsPath("")
            .unsafe_join(&WindowsPath("C:\\a"))),
          "C:\\a");

        t(&(WindowsPath("c:\\foo")
            .normalize()),
          "C:\\foo");
1234
    }
A
Armin Ronacher 已提交
1235 1236 1237

    #[test]
    fn test_windows_path_restrictions() {
1238 1239 1240 1241
        assert_eq!(WindowsPath("hi").is_restricted(), false);
        assert_eq!(WindowsPath("C:\\NUL").is_restricted(), true);
        assert_eq!(WindowsPath("C:\\COM1.TXT").is_restricted(), true);
        assert_eq!(WindowsPath("c:\\prn.exe").is_restricted(), true);
A
Armin Ronacher 已提交
1242
    }
1243
}