]> git.lizzy.rs Git - rust.git/blob - src/libstd/path.rs
3ec3407b1cc55c2bd88261fafe945eb58eba5051
[rust.git] / src / libstd / path.rs
1 // Copyright 2012 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 /*!
12
13 Cross-platform file path handling
14
15 */
16
17 #[allow(missing_doc)];
18
19 use clone::Clone;
20 use container::Container;
21 use cmp::Eq;
22 use iterator::IteratorUtil;
23 use libc;
24 use option::{None, Option, Some};
25 use str::{OwnedStr, Str, StrSlice, StrVector};
26 use str;
27 use to_str::ToStr;
28 use ascii::{AsciiCast, AsciiStr};
29 use vec::{OwnedVector, ImmutableVector};
30
31 #[cfg(windows)]
32 pub use Path = self::WindowsPath;
33 #[cfg(unix)]
34 pub use Path = self::PosixPath;
35
36 #[deriving(Clone, Eq)]
37 pub struct WindowsPath {
38     host: Option<~str>,
39     device: Option<~str>,
40     is_absolute: bool,
41     components: ~[~str],
42 }
43
44 pub fn WindowsPath(s: &str) -> WindowsPath {
45     GenericPath::from_str(s)
46 }
47
48 #[deriving(Clone, Eq)]
49 pub struct PosixPath {
50     is_absolute: bool,
51     components: ~[~str],
52 }
53
54 pub fn PosixPath(s: &str) -> PosixPath {
55     GenericPath::from_str(s)
56 }
57
58 pub trait GenericPath {
59     /// Converts a string to a Path
60     fn from_str(&str) -> Self;
61
62     /// Returns the directory component of `self`, as a string
63     fn dirname(&self) -> ~str;
64     /// Returns the file component of `self`, as a string option.
65     /// Returns None if `self` names a directory.
66     fn filename(&self) -> Option<~str>;
67     /// Returns the stem of the file component of `self`, as a string option.
68     /// The stem is the slice of a filename starting at 0 and ending just before
69     /// the last '.' in the name.
70     /// Returns None if `self` names a directory.
71     fn filestem(&self) -> Option<~str>;
72     /// Returns the type of the file component of `self`, as a string option.
73     /// The file type is the slice of a filename starting just after the last
74     /// '.' in the name and ending at the last index in the filename.
75     /// Returns None if `self` names a directory.
76     fn filetype(&self) -> Option<~str>;
77
78     /// Returns a new path consisting of `self` with the parent directory component replaced
79     /// with the given string.
80     fn with_dirname(&self, (&str)) -> Self;
81     /// Returns a new path consisting of `self` with the file component replaced
82     /// with the given string.
83     fn with_filename(&self, (&str)) -> Self;
84     /// Returns a new path consisting of `self` with the file stem replaced
85     /// with the given string.
86     fn with_filestem(&self, (&str)) -> Self;
87     /// Returns a new path consisting of `self` with the file type replaced
88     /// with the given string.
89     fn with_filetype(&self, (&str)) -> Self;
90
91     /// Returns the directory component of `self`, as a new path.
92     /// If `self` has no parent, returns `self`.
93     fn dir_path(&self) -> Self;
94     /// Returns the file component of `self`, as a new path.
95     /// If `self` names a directory, returns the empty path.
96     fn file_path(&self) -> Self;
97
98     /// Returns a new Path whose parent directory is `self` and whose
99     /// file component is the given string.
100     fn push(&self, (&str)) -> Self;
101     /// Returns a new Path consisting of the given path, made relative to `self`.
102     fn push_rel(&self, (&Self)) -> Self;
103     /// Returns a new Path consisting of the path given by the given vector
104     /// of strings, relative to `self`.
105     fn push_many<S: Str>(&self, (&[S])) -> Self;
106     /// Identical to `dir_path` except in the case where `self` has only one
107     /// component. In this case, `pop` returns the empty path.
108     fn pop(&self) -> Self;
109
110     /// The same as `push_rel`, except that the directory argument must not
111     /// contain directory separators in any of its components.
112     fn unsafe_join(&self, (&Self)) -> Self;
113     /// On Unix, always returns false. On Windows, returns true iff `self`'s
114     /// file stem is one of: `con` `aux` `com1` `com2` `com3` `com4`
115     /// `lpt1` `lpt2` `lpt3` `prn` `nul`
116     fn is_restricted(&self) -> bool;
117
118     /// Returns a new path that names the same file as `self`, without containing
119     /// any '.', '..', or empty components. On Windows, uppercases the drive letter
120     /// as well.
121     fn normalize(&self) -> Self;
122
123     /// Returns `true` if `self` is an absolute path.
124     fn is_absolute(&self) -> bool;
125
126     /// True if `self` is an ancestor of `other`. See `test_is_ancestor_of` for examples
127     fn is_ancestor_of(&self, (&Self)) -> bool;
128 }
129
130 #[cfg(target_os = "linux")]
131 #[cfg(target_os = "android")]
132 mod stat {
133     #[cfg(target_arch = "x86")]
134     pub mod arch {
135         use libc;
136
137         pub fn default_stat() -> libc::stat {
138             libc::stat {
139                 st_dev: 0,
140                 __pad1: 0,
141                 st_ino: 0,
142                 st_mode: 0,
143                 st_nlink: 0,
144                 st_uid: 0,
145                 st_gid: 0,
146                 st_rdev: 0,
147                 __pad2: 0,
148                 st_size: 0,
149                 st_blksize: 0,
150                 st_blocks: 0,
151                 st_atime: 0,
152                 st_atime_nsec: 0,
153                 st_mtime: 0,
154                 st_mtime_nsec: 0,
155                 st_ctime: 0,
156                 st_ctime_nsec: 0,
157                 __unused4: 0,
158                 __unused5: 0,
159             }
160         }
161     }
162
163     #[cfg(target_arch = "arm")]
164     pub mod arch {
165         use libc;
166
167         pub fn default_stat() -> libc::stat {
168             libc::stat {
169                 st_dev: 0,
170                 __pad0: [0, ..4],
171                 __st_ino: 0,
172                 st_mode: 0,
173                 st_nlink: 0,
174                 st_uid: 0,
175                 st_gid: 0,
176                 st_rdev: 0,
177                 __pad3: [0, ..4],
178                 st_size: 0,
179                 st_blksize: 0,
180                 st_blocks: 0,
181                 st_atime: 0,
182                 st_atime_nsec: 0,
183                 st_mtime: 0,
184                 st_mtime_nsec: 0,
185                 st_ctime: 0,
186                 st_ctime_nsec: 0,
187                 st_ino: 0
188             }
189         }
190     }
191
192     #[cfg(target_arch = "mips")]
193     pub mod arch {
194         use libc;
195
196         pub fn default_stat() -> libc::stat {
197             libc::stat {
198                 st_dev: 0,
199                 st_pad1: [0, ..3],
200                 st_ino: 0,
201                 st_mode: 0,
202                 st_nlink: 0,
203                 st_uid: 0,
204                 st_gid: 0,
205                 st_rdev: 0,
206                 st_pad2: [0, ..2],
207                 st_size: 0,
208                 st_pad3: 0,
209                 st_atime: 0,
210                 st_atime_nsec: 0,
211                 st_mtime: 0,
212                 st_mtime_nsec: 0,
213                 st_ctime: 0,
214                 st_ctime_nsec: 0,
215                 st_blksize: 0,
216                 st_blocks: 0,
217                 st_pad5: [0, ..14],
218             }
219         }
220     }
221
222     #[cfg(target_arch = "x86_64")]
223     pub mod arch {
224         use libc;
225
226         pub fn default_stat() -> libc::stat {
227             libc::stat {
228                 st_dev: 0,
229                 st_ino: 0,
230                 st_nlink: 0,
231                 st_mode: 0,
232                 st_uid: 0,
233                 st_gid: 0,
234                 __pad0: 0,
235                 st_rdev: 0,
236                 st_size: 0,
237                 st_blksize: 0,
238                 st_blocks: 0,
239                 st_atime: 0,
240                 st_atime_nsec: 0,
241                 st_mtime: 0,
242                 st_mtime_nsec: 0,
243                 st_ctime: 0,
244                 st_ctime_nsec: 0,
245                 __unused: [0, 0, 0],
246             }
247         }
248     }
249 }
250
251 #[cfg(target_os = "freebsd")]
252 mod stat {
253     #[cfg(target_arch = "x86_64")]
254     pub mod arch {
255         use libc;
256
257         pub fn default_stat() -> libc::stat {
258             libc::stat {
259                 st_dev: 0,
260                 st_ino: 0,
261                 st_mode: 0,
262                 st_nlink: 0,
263                 st_uid: 0,
264                 st_gid: 0,
265                 st_rdev: 0,
266                 st_atime: 0,
267                 st_atime_nsec: 0,
268                 st_mtime: 0,
269                 st_mtime_nsec: 0,
270                 st_ctime: 0,
271                 st_ctime_nsec: 0,
272                 st_size: 0,
273                 st_blocks: 0,
274                 st_blksize: 0,
275                 st_flags: 0,
276                 st_gen: 0,
277                 st_lspare: 0,
278                 st_birthtime: 0,
279                 st_birthtime_nsec: 0,
280                 __unused: [0, 0],
281             }
282         }
283     }
284 }
285
286 #[cfg(target_os = "macos")]
287 mod stat {
288     pub mod arch {
289         use libc;
290
291         pub fn default_stat() -> libc::stat {
292             libc::stat {
293                 st_dev: 0,
294                 st_mode: 0,
295                 st_nlink: 0,
296                 st_ino: 0,
297                 st_uid: 0,
298                 st_gid: 0,
299                 st_rdev: 0,
300                 st_atime: 0,
301                 st_atime_nsec: 0,
302                 st_mtime: 0,
303                 st_mtime_nsec: 0,
304                 st_ctime: 0,
305                 st_ctime_nsec: 0,
306                 st_birthtime: 0,
307                 st_birthtime_nsec: 0,
308                 st_size: 0,
309                 st_blocks: 0,
310                 st_blksize: 0,
311                 st_flags: 0,
312                 st_gen: 0,
313                 st_lspare: 0,
314                 st_qspare: [0, 0],
315             }
316         }
317     }
318 }
319
320 #[cfg(target_os = "win32")]
321 mod stat {
322     pub mod arch {
323         use libc;
324         pub fn default_stat() -> libc::stat {
325             libc::stat {
326                 st_dev: 0,
327                 st_ino: 0,
328                 st_mode: 0,
329                 st_nlink: 0,
330                 st_uid: 0,
331                 st_gid: 0,
332                 st_rdev: 0,
333                 st_size: 0,
334                 st_atime: 0,
335                 st_mtime: 0,
336                 st_ctime: 0,
337             }
338         }
339     }
340 }
341
342 #[cfg(target_os = "win32")]
343 impl WindowsPath {
344     pub fn stat(&self) -> Option<libc::stat> {
345         unsafe {
346              do str::as_c_str(self.to_str()) |buf| {
347                 let mut st = stat::arch::default_stat();
348                 match libc::stat(buf, &mut st) {
349                     0 => Some(st),
350                     _ => None,
351                 }
352             }
353         }
354     }
355
356     pub fn exists(&self) -> bool {
357         match self.stat() {
358             None => false,
359             Some(_) => true,
360         }
361     }
362
363     pub fn get_size(&self) -> Option<i64> {
364         match self.stat() {
365             None => None,
366             Some(ref st) => Some(st.st_size as i64),
367         }
368     }
369
370     pub fn get_mode(&self) -> Option<uint> {
371         match self.stat() {
372             None => None,
373             Some(ref st) => Some(st.st_mode as uint),
374         }
375     }
376 }
377
378 #[cfg(not(target_os = "win32"))]
379 impl PosixPath {
380     pub fn stat(&self) -> Option<libc::stat> {
381         unsafe {
382              do str::as_c_str(self.to_str()) |buf| {
383                 let mut st = stat::arch::default_stat();
384                 match libc::stat(buf, &mut st) {
385                     0 => Some(st),
386                     _ => None,
387                 }
388             }
389         }
390     }
391
392     pub fn exists(&self) -> bool {
393         match self.stat() {
394             None => false,
395             Some(_) => true,
396         }
397     }
398
399     pub fn get_size(&self) -> Option<i64> {
400         match self.stat() {
401             None => None,
402             Some(ref st) => Some(st.st_size as i64),
403         }
404     }
405
406     pub fn get_mode(&self) -> Option<uint> {
407         match self.stat() {
408             None => None,
409             Some(ref st) => Some(st.st_mode as uint),
410         }
411     }
412
413     /// Execute a function on p as well as all of its ancestors
414     pub fn each_parent(&self, f: &fn(&Path)) {
415         if !self.components.is_empty() {
416             f(self);
417             self.pop().each_parent(f);
418         }
419     }
420
421 }
422
423 #[cfg(target_os = "freebsd")]
424 #[cfg(target_os = "linux")]
425 #[cfg(target_os = "macos")]
426 impl PosixPath {
427     pub fn get_atime(&self) -> Option<(i64, int)> {
428         match self.stat() {
429             None => None,
430             Some(ref st) => {
431                 Some((st.st_atime as i64,
432                       st.st_atime_nsec as int))
433             }
434         }
435     }
436
437     pub fn get_mtime(&self) -> Option<(i64, int)> {
438         match self.stat() {
439             None => None,
440             Some(ref st) => {
441                 Some((st.st_mtime as i64,
442                       st.st_mtime_nsec as int))
443             }
444         }
445     }
446
447     pub fn get_ctime(&self) -> Option<(i64, int)> {
448         match self.stat() {
449             None => None,
450             Some(ref st) => {
451                 Some((st.st_ctime as i64,
452                       st.st_ctime_nsec as int))
453             }
454         }
455     }
456 }
457
458 #[cfg(unix)]
459 impl PosixPath {
460     pub fn lstat(&self) -> Option<libc::stat> {
461         unsafe {
462             do str::as_c_str(self.to_str()) |buf| {
463                 let mut st = stat::arch::default_stat();
464                 match libc::lstat(buf, &mut st) {
465                     0 => Some(st),
466                     _ => None,
467                 }
468             }
469         }
470     }
471 }
472
473 #[cfg(target_os = "freebsd")]
474 #[cfg(target_os = "macos")]
475 impl PosixPath {
476     pub fn get_birthtime(&self) -> Option<(i64, int)> {
477         match self.stat() {
478             None => None,
479             Some(ref st) => {
480                 Some((st.st_birthtime as i64,
481                       st.st_birthtime_nsec as int))
482             }
483         }
484     }
485 }
486
487 #[cfg(target_os = "win32")]
488 impl WindowsPath {
489     pub fn get_atime(&self) -> Option<(i64, int)> {
490         match self.stat() {
491             None => None,
492             Some(ref st) => {
493                 Some((st.st_atime as i64, 0))
494             }
495         }
496     }
497
498     pub fn get_mtime(&self) -> Option<(i64, int)> {
499         match self.stat() {
500             None => None,
501             Some(ref st) => {
502                 Some((st.st_mtime as i64, 0))
503             }
504         }
505     }
506
507     pub fn get_ctime(&self) -> Option<(i64, int)> {
508         match self.stat() {
509             None => None,
510             Some(ref st) => {
511                 Some((st.st_ctime as i64, 0))
512             }
513         }
514     }
515
516     /// Execute a function on p as well as all of its ancestors
517     pub fn each_parent(&self, f: &fn(&Path)) {
518         if !self.components.is_empty() {
519             f(self);
520             self.pop().each_parent(f);
521         }
522     }
523 }
524
525 impl ToStr for PosixPath {
526     fn to_str(&self) -> ~str {
527         let mut s = ~"";
528         if self.is_absolute {
529             s.push_str("/");
530         }
531         s + self.components.connect("/")
532     }
533 }
534
535 // FIXME (#3227): when default methods in traits are working, de-duplicate
536 // PosixPath and WindowsPath, most of their methods are common.
537 impl GenericPath for PosixPath {
538     fn from_str(s: &str) -> PosixPath {
539         let components = s.split_iter('/')
540             .filter_map(|s| if s.is_empty() {None} else {Some(s.to_owned())})
541             .collect();
542         let is_absolute = (s.len() != 0 && s[0] == '/' as u8);
543         PosixPath {
544             is_absolute: is_absolute,
545             components: components,
546         }
547     }
548
549     fn dirname(&self) -> ~str {
550         let s = self.dir_path().to_str();
551         match s.len() {
552             0 => ~".",
553             _ => s,
554         }
555     }
556
557     fn filename(&self) -> Option<~str> {
558         match self.components.len() {
559             0 => None,
560             n => Some(self.components[n - 1].clone()),
561         }
562     }
563
564     fn filestem(&self) -> Option<~str> {
565         match self.filename() {
566             None => None,
567             Some(ref f) => {
568                 match f.rfind('.') {
569                     Some(p) => Some(f.slice_to(p).to_owned()),
570                     None => Some((*f).clone()),
571                 }
572             }
573         }
574     }
575
576     fn filetype(&self) -> Option<~str> {
577         match self.filename() {
578             None => None,
579             Some(ref f) => {
580                 match f.rfind('.') {
581                     Some(p) if p < f.len() => Some(f.slice_from(p).to_owned()),
582                     _ => None,
583                 }
584             }
585         }
586     }
587
588     fn with_dirname(&self, d: &str) -> PosixPath {
589         let dpath = PosixPath(d);
590         match self.filename() {
591             Some(ref f) => dpath.push(*f),
592             None => dpath,
593         }
594     }
595
596     fn with_filename(&self, f: &str) -> PosixPath {
597         assert!(! f.iter().all(windows::is_sep));
598         self.dir_path().push(f)
599     }
600
601     fn with_filestem(&self, s: &str) -> PosixPath {
602         match self.filetype() {
603             None => self.with_filename(s),
604             Some(ref t) => self.with_filename(s.to_owned() + *t),
605         }
606     }
607
608     fn with_filetype(&self, t: &str) -> PosixPath {
609         match (t.len(), self.filestem()) {
610             (0, None)        => (*self).clone(),
611             (0, Some(ref s)) => self.with_filename(*s),
612             (_, None)        => self.with_filename(fmt!(".%s", t)),
613             (_, Some(ref s)) => self.with_filename(fmt!("%s.%s", *s, t)),
614         }
615     }
616
617     fn dir_path(&self) -> PosixPath {
618         match self.components.len() {
619             0 => (*self).clone(),
620             _ => self.pop(),
621         }
622     }
623
624     fn file_path(&self) -> PosixPath {
625         let cs = match self.filename() {
626           None => ~[],
627           Some(ref f) => ~[(*f).clone()]
628         };
629         PosixPath {
630             is_absolute: false,
631             components: cs,
632         }
633     }
634
635     fn push_rel(&self, other: &PosixPath) -> PosixPath {
636         assert!(!other.is_absolute);
637         self.push_many(other.components)
638     }
639
640     fn unsafe_join(&self, other: &PosixPath) -> PosixPath {
641         if other.is_absolute {
642             PosixPath {
643                 is_absolute: true,
644                 components: other.components.clone(),
645             }
646         } else {
647             self.push_rel(other)
648         }
649     }
650
651     fn is_restricted(&self) -> bool {
652         false
653     }
654
655     fn push_many<S: Str>(&self, cs: &[S]) -> PosixPath {
656         let mut v = self.components.clone();
657         for cs.iter().advance |e| {
658             for e.as_slice().split_iter(windows::is_sep).advance |s| {
659                 if !s.is_empty() {
660                     v.push(s.to_owned())
661                 }
662             }
663         }
664         PosixPath {
665             is_absolute: self.is_absolute,
666             components: v,
667         }
668     }
669
670     fn push(&self, s: &str) -> PosixPath {
671         let mut v = self.components.clone();
672         for s.split_iter(windows::is_sep).advance |s| {
673             if !s.is_empty() {
674                 v.push(s.to_owned())
675             }
676         }
677         PosixPath {
678             components: v,
679             ..(*self).clone()
680         }
681     }
682
683     fn pop(&self) -> PosixPath {
684         let mut cs = self.components.clone();
685         if cs.len() != 0 {
686             cs.pop();
687         }
688         PosixPath {
689             is_absolute: self.is_absolute,
690             components: cs,
691         } //..self }
692     }
693
694     fn normalize(&self) -> PosixPath {
695         PosixPath {
696             is_absolute: self.is_absolute,
697             components: normalize(self.components),
698         } // ..self }
699     }
700
701     fn is_absolute(&self) -> bool {
702         self.is_absolute
703     }
704
705     fn is_ancestor_of(&self, other: &PosixPath) -> bool {
706         debug!("%s / %s %? %?", self.to_str(), other.to_str(), self.is_absolute,
707                self.components.len());
708         self == other ||
709             (!other.components.is_empty() && !(self.components.is_empty() && !self.is_absolute) &&
710              self.is_ancestor_of(&other.pop()))
711     }
712
713 }
714
715
716 impl ToStr for WindowsPath {
717     fn to_str(&self) -> ~str {
718         let mut s = ~"";
719         match self.host {
720           Some(ref h) => {
721             s.push_str("\\\\");
722             s.push_str(*h);
723           }
724           None => { }
725         }
726         match self.device {
727           Some(ref d) => {
728             s.push_str(*d);
729             s.push_str(":");
730           }
731           None => { }
732         }
733         if self.is_absolute {
734             s.push_str("\\");
735         }
736         s + self.components.connect("\\")
737     }
738 }
739
740
741 impl GenericPath for WindowsPath {
742     fn from_str(s: &str) -> WindowsPath {
743         let host;
744         let device;
745         let rest;
746
747         match (
748             windows::extract_drive_prefix(s),
749             windows::extract_unc_prefix(s),
750         ) {
751             (Some((ref d, ref r)), _) => {
752                 host = None;
753                 device = Some((*d).clone());
754                 rest = (*r).clone();
755             }
756             (None, Some((ref h, ref r))) => {
757                 host = Some((*h).clone());
758                 device = None;
759                 rest = (*r).clone();
760             }
761             (None, None) => {
762                 host = None;
763                 device = None;
764                 rest = s.to_owned();
765             }
766         }
767
768         let components = rest.split_iter(windows::is_sep)
769             .filter_map(|s| if s.is_empty() {None} else {Some(s.to_owned())})
770             .collect();
771
772         let is_absolute = (rest.len() != 0 && windows::is_sep(rest[0] as char));
773         WindowsPath {
774             host: host,
775             device: device,
776             is_absolute: is_absolute,
777             components: components,
778         }
779     }
780
781     fn dirname(&self) -> ~str {
782         let s = self.dir_path().to_str();
783         match s.len() {
784             0 => ~".",
785             _ => s,
786         }
787     }
788
789     fn filename(&self) -> Option<~str> {
790         match self.components.len() {
791             0 => None,
792             n => Some(self.components[n - 1].clone()),
793         }
794     }
795
796     fn filestem(&self) -> Option<~str> {
797         match self.filename() {
798             None => None,
799             Some(ref f) => {
800                 match f.rfind('.') {
801                     Some(p) => Some(f.slice_to(p).to_owned()),
802                     None => Some((*f).clone()),
803                 }
804             }
805         }
806     }
807
808     fn filetype(&self) -> Option<~str> {
809         match self.filename() {
810           None => None,
811           Some(ref f) => {
812             match f.rfind('.') {
813                 Some(p) if p < f.len() => Some(f.slice_from(p).to_owned()),
814                 _ => None,
815             }
816           }
817         }
818     }
819
820     fn with_dirname(&self, d: &str) -> WindowsPath {
821         let dpath = WindowsPath(d);
822         match self.filename() {
823             Some(ref f) => dpath.push(*f),
824             None => dpath,
825         }
826     }
827
828     fn with_filename(&self, f: &str) -> WindowsPath {
829         assert!(! f.iter().all(windows::is_sep));
830         self.dir_path().push(f)
831     }
832
833     fn with_filestem(&self, s: &str) -> WindowsPath {
834         match self.filetype() {
835             None => self.with_filename(s),
836             Some(ref t) => self.with_filename(s.to_owned() + *t),
837         }
838     }
839
840     fn with_filetype(&self, t: &str) -> WindowsPath {
841         match (t.len(), self.filestem()) {
842             (0, None)        => (*self).clone(),
843             (0, Some(ref s)) => self.with_filename(*s),
844             (_, None)        => self.with_filename(fmt!(".%s", t)),
845             (_, Some(ref s)) => self.with_filename(fmt!("%s.%s", *s, t)),
846         }
847     }
848
849     fn dir_path(&self) -> WindowsPath {
850         match self.components.len() {
851             0 => (*self).clone(),
852             _ => self.pop(),
853         }
854     }
855
856     fn file_path(&self) -> WindowsPath {
857         WindowsPath {
858             host: None,
859             device: None,
860             is_absolute: false,
861             components: match self.filename() {
862                 None => ~[],
863                 Some(ref f) => ~[(*f).clone()],
864             }
865         }
866     }
867
868     fn push_rel(&self, other: &WindowsPath) -> WindowsPath {
869         assert!(!other.is_absolute);
870         self.push_many(other.components)
871     }
872
873     fn unsafe_join(&self, other: &WindowsPath) -> WindowsPath {
874         /* rhs not absolute is simple push */
875         if !other.is_absolute {
876             return self.push_many(other.components);
877         }
878
879         /* if rhs has a host set, then the whole thing wins */
880         match other.host {
881             Some(ref host) => {
882                 return WindowsPath {
883                     host: Some((*host).clone()),
884                     device: other.device.clone(),
885                     is_absolute: true,
886                     components: other.components.clone(),
887                 };
888             }
889             _ => {}
890         }
891
892         /* if rhs has a device set, then a part wins */
893         match other.device {
894             Some(ref device) => {
895                 return WindowsPath {
896                     host: None,
897                     device: Some((*device).clone()),
898                     is_absolute: true,
899                     components: other.components.clone(),
900                 };
901             }
902             _ => {}
903         }
904
905         /* fallback: host and device of lhs win, but the
906            whole path of the right */
907         WindowsPath {
908             host: self.host.clone(),
909             device: self.device.clone(),
910             is_absolute: self.is_absolute || other.is_absolute,
911             components: other.components.clone(),
912         }
913     }
914
915     fn is_restricted(&self) -> bool {
916         match self.filestem() {
917             Some(stem) => {
918                 // FIXME: #4318 Instead of to_ascii and to_str_ascii, could use
919                 // to_ascii_consume and to_str_consume to not do a unnecessary copy.
920                 match stem.to_ascii().to_lower().to_str_ascii() {
921                     ~"con" | ~"aux" | ~"com1" | ~"com2" | ~"com3" | ~"com4" |
922                     ~"lpt1" | ~"lpt2" | ~"lpt3" | ~"prn" | ~"nul" => true,
923                     _ => false
924                 }
925             },
926             None => false
927         }
928     }
929
930     fn push_many<S: Str>(&self, cs: &[S]) -> WindowsPath {
931         let mut v = self.components.clone();
932         for cs.iter().advance |e| {
933             for e.as_slice().split_iter(windows::is_sep).advance |s| {
934                 if !s.is_empty() {
935                     v.push(s.to_owned())
936                 }
937             }
938         }
939         // tedious, but as-is, we can't use ..self
940         WindowsPath {
941             host: self.host.clone(),
942             device: self.device.clone(),
943             is_absolute: self.is_absolute,
944             components: v
945         }
946     }
947
948     fn push(&self, s: &str) -> WindowsPath {
949         let mut v = self.components.clone();
950         for s.split_iter(windows::is_sep).advance |s| {
951             if !s.is_empty() {
952                 v.push(s.to_owned())
953             }
954         }
955         WindowsPath { components: v, ..(*self).clone() }
956     }
957
958     fn pop(&self) -> WindowsPath {
959         let mut cs = self.components.clone();
960         if cs.len() != 0 {
961             cs.pop();
962         }
963         WindowsPath {
964             host: self.host.clone(),
965             device: self.device.clone(),
966             is_absolute: self.is_absolute,
967             components: cs,
968         }
969     }
970
971     fn normalize(&self) -> WindowsPath {
972         WindowsPath {
973             host: self.host.clone(),
974             device: match self.device {
975                 None => None,
976
977                 // FIXME: #4318 Instead of to_ascii and to_str_ascii, could use
978                 // to_ascii_consume and to_str_consume to not do a unnecessary copy.
979                 Some(ref device) => Some(device.to_ascii().to_upper().to_str_ascii())
980             },
981             is_absolute: self.is_absolute,
982             components: normalize(self.components)
983         }
984     }
985
986     fn is_absolute(&self) -> bool {
987         self.is_absolute
988     }
989
990     fn is_ancestor_of(&self, other: &WindowsPath) -> bool {
991         self == other ||
992             (!other.components.is_empty() && !(self.components.is_empty() && !self.is_absolute) &&
993              self.is_ancestor_of(&other.pop()))
994     }
995 }
996
997 pub fn normalize(components: &[~str]) -> ~[~str] {
998     let mut cs = ~[];
999     for components.iter().advance |c| {
1000         if *c == ~"." && components.len() > 1 { loop; }
1001         if *c == ~"" { loop; }
1002         if *c == ~".." && cs.len() != 0 {
1003             cs.pop();
1004             loop;
1005         }
1006         cs.push((*c).clone());
1007     }
1008     cs
1009 }
1010
1011 // Various windows helpers, and tests for the impl.
1012 pub mod windows {
1013     use libc;
1014     use option::{None, Option, Some};
1015
1016     #[inline]
1017     pub fn is_sep(u: char) -> bool {
1018         u == '/' || u == '\\'
1019     }
1020
1021     pub fn extract_unc_prefix(s: &str) -> Option<(~str,~str)> {
1022         if (s.len() > 1 &&
1023             (s[0] == '\\' as u8 || s[0] == '/' as u8) &&
1024             s[0] == s[1]) {
1025             let mut i = 2;
1026             while i < s.len() {
1027                 if is_sep(s[i] as char) {
1028                     let pre = s.slice(2, i).to_owned();
1029                     let rest = s.slice(i, s.len()).to_owned();
1030                     return Some((pre, rest));
1031                 }
1032                 i += 1;
1033             }
1034         }
1035         None
1036     }
1037
1038     pub fn extract_drive_prefix(s: &str) -> Option<(~str,~str)> {
1039         unsafe {
1040             if (s.len() > 1 &&
1041                 libc::isalpha(s[0] as libc::c_int) != 0 &&
1042                 s[1] == ':' as u8) {
1043                 let rest = if s.len() == 2 {
1044                     ~""
1045                 } else {
1046                     s.slice(2, s.len()).to_owned()
1047                 };
1048                 return Some((s.slice(0,1).to_owned(), rest));
1049             }
1050             None
1051         }
1052     }
1053 }
1054
1055 #[cfg(test)]
1056 mod tests {
1057     use option::{None, Some};
1058     use path::{PosixPath, WindowsPath, windows};
1059
1060     #[test]
1061     fn test_double_slash_collapsing() {
1062         let path = PosixPath("tmp/");
1063         let path = path.push("/hmm");
1064         let path = path.normalize();
1065         assert_eq!(~"tmp/hmm", path.to_str());
1066
1067         let path = WindowsPath("tmp/");
1068         let path = path.push("/hmm");
1069         let path = path.normalize();
1070         assert_eq!(~"tmp\\hmm", path.to_str());
1071     }
1072
1073     #[test]
1074     fn test_filetype_foo_bar() {
1075         let wp = PosixPath("foo.bar");
1076         assert_eq!(wp.filetype(), Some(~".bar"));
1077
1078         let wp = WindowsPath("foo.bar");
1079         assert_eq!(wp.filetype(), Some(~".bar"));
1080     }
1081
1082     #[test]
1083     fn test_filetype_foo() {
1084         let wp = PosixPath("foo");
1085         assert_eq!(wp.filetype(), None);
1086
1087         let wp = WindowsPath("foo");
1088         assert_eq!(wp.filetype(), None);
1089     }
1090
1091     #[test]
1092     fn test_posix_paths() {
1093         fn t(wp: &PosixPath, s: &str) {
1094             let ss = wp.to_str();
1095             let sss = s.to_owned();
1096             if (ss != sss) {
1097                 debug!("got %s", ss);
1098                 debug!("expected %s", sss);
1099                 assert_eq!(ss, sss);
1100             }
1101         }
1102
1103         t(&(PosixPath("hi")), "hi");
1104         t(&(PosixPath("/lib")), "/lib");
1105         t(&(PosixPath("hi/there")), "hi/there");
1106         t(&(PosixPath("hi/there.txt")), "hi/there.txt");
1107
1108         t(&(PosixPath("hi/there.txt")), "hi/there.txt");
1109         t(&(PosixPath("hi/there.txt")
1110            .with_filetype("")), "hi/there");
1111
1112         t(&(PosixPath("/a/b/c/there.txt")
1113             .with_dirname("hi")), "hi/there.txt");
1114
1115         t(&(PosixPath("hi/there.txt")
1116             .with_dirname(".")), "./there.txt");
1117
1118         t(&(PosixPath("a/b/c")
1119             .push("..")), "a/b/c/..");
1120
1121         t(&(PosixPath("there.txt")
1122             .with_filetype("o")), "there.o");
1123
1124         t(&(PosixPath("hi/there.txt")
1125             .with_filetype("o")), "hi/there.o");
1126
1127         t(&(PosixPath("hi/there.txt")
1128             .with_filetype("o")
1129             .with_dirname("/usr/lib")),
1130           "/usr/lib/there.o");
1131
1132         t(&(PosixPath("hi/there.txt")
1133             .with_filetype("o")
1134             .with_dirname("/usr/lib/")),
1135           "/usr/lib/there.o");
1136
1137         t(&(PosixPath("hi/there.txt")
1138             .with_filetype("o")
1139             .with_dirname("/usr//lib//")),
1140             "/usr/lib/there.o");
1141
1142         t(&(PosixPath("/usr/bin/rust")
1143             .push_many([~"lib", ~"thingy.so"])
1144             .with_filestem("librustc")),
1145           "/usr/bin/rust/lib/librustc.so");
1146
1147     }
1148
1149     #[test]
1150     fn test_normalize() {
1151         fn t(wp: &PosixPath, s: &str) {
1152             let ss = wp.to_str();
1153             let sss = s.to_owned();
1154             if (ss != sss) {
1155                 debug!("got %s", ss);
1156                 debug!("expected %s", sss);
1157                 assert_eq!(ss, sss);
1158             }
1159         }
1160
1161         t(&(PosixPath("hi/there.txt")
1162             .with_dirname(".").normalize()), "there.txt");
1163
1164         t(&(PosixPath("a/b/../c/././/../foo.txt/").normalize()),
1165           "a/foo.txt");
1166
1167         t(&(PosixPath("a/b/c")
1168             .push("..").normalize()), "a/b");
1169     }
1170
1171     #[test]
1172     fn test_extract_unc_prefixes() {
1173         assert!(windows::extract_unc_prefix("\\\\").is_none());
1174         assert!(windows::extract_unc_prefix("//").is_none());
1175         assert!(windows::extract_unc_prefix("\\\\hi").is_none());
1176         assert!(windows::extract_unc_prefix("//hi").is_none());
1177         assert!(windows::extract_unc_prefix("\\\\hi\\") ==
1178             Some((~"hi", ~"\\")));
1179         assert!(windows::extract_unc_prefix("//hi\\") ==
1180             Some((~"hi", ~"\\")));
1181         assert!(windows::extract_unc_prefix("\\\\hi\\there") ==
1182             Some((~"hi", ~"\\there")));
1183         assert!(windows::extract_unc_prefix("//hi/there") ==
1184             Some((~"hi", ~"/there")));
1185         assert!(windows::extract_unc_prefix(
1186             "\\\\hi\\there\\friends.txt") ==
1187             Some((~"hi", ~"\\there\\friends.txt")));
1188         assert!(windows::extract_unc_prefix(
1189             "//hi\\there\\friends.txt") ==
1190             Some((~"hi", ~"\\there\\friends.txt")));
1191     }
1192
1193     #[test]
1194     fn test_extract_drive_prefixes() {
1195         assert!(windows::extract_drive_prefix("c").is_none());
1196         assert!(windows::extract_drive_prefix("c:") ==
1197                      Some((~"c", ~"")));
1198         assert!(windows::extract_drive_prefix("d:") ==
1199                      Some((~"d", ~"")));
1200         assert!(windows::extract_drive_prefix("z:") ==
1201                      Some((~"z", ~"")));
1202         assert!(windows::extract_drive_prefix("c:\\hi") ==
1203                      Some((~"c", ~"\\hi")));
1204         assert!(windows::extract_drive_prefix("d:hi") ==
1205                      Some((~"d", ~"hi")));
1206         assert!(windows::extract_drive_prefix("c:hi\\there.txt") ==
1207                      Some((~"c", ~"hi\\there.txt")));
1208         assert!(windows::extract_drive_prefix("c:\\hi\\there.txt") ==
1209                      Some((~"c", ~"\\hi\\there.txt")));
1210     }
1211
1212     #[test]
1213     fn test_windows_paths() {
1214         fn t(wp: &WindowsPath, s: &str) {
1215             let ss = wp.to_str();
1216             let sss = s.to_owned();
1217             if (ss != sss) {
1218                 debug!("got %s", ss);
1219                 debug!("expected %s", sss);
1220                 assert_eq!(ss, sss);
1221             }
1222         }
1223
1224         t(&(WindowsPath("hi")), "hi");
1225         t(&(WindowsPath("hi/there")), "hi\\there");
1226         t(&(WindowsPath("hi/there.txt")), "hi\\there.txt");
1227
1228         t(&(WindowsPath("there.txt")
1229             .with_filetype("o")), "there.o");
1230
1231         t(&(WindowsPath("hi/there.txt")
1232             .with_filetype("o")), "hi\\there.o");
1233
1234         t(&(WindowsPath("hi/there.txt")
1235             .with_filetype("o")
1236             .with_dirname("c:\\program files A")),
1237           "c:\\program files A\\there.o");
1238
1239         t(&(WindowsPath("hi/there.txt")
1240             .with_filetype("o")
1241             .with_dirname("c:\\program files B\\")),
1242           "c:\\program files B\\there.o");
1243
1244         t(&(WindowsPath("hi/there.txt")
1245             .with_filetype("o")
1246             .with_dirname("c:\\program files C\\/")),
1247             "c:\\program files C\\there.o");
1248
1249         t(&(WindowsPath("c:\\program files (x86)\\rust")
1250             .push_many([~"lib", ~"thingy.dll"])
1251             .with_filename("librustc.dll")),
1252           "c:\\program files (x86)\\rust\\lib\\librustc.dll");
1253
1254         t(&(WindowsPath("\\\\computer\\share")
1255             .unsafe_join(&WindowsPath("\\a"))),
1256           "\\\\computer\\a");
1257
1258         t(&(WindowsPath("//computer/share")
1259             .unsafe_join(&WindowsPath("\\a"))),
1260           "\\\\computer\\a");
1261
1262         t(&(WindowsPath("//computer/share")
1263             .unsafe_join(&WindowsPath("\\\\computer\\share"))),
1264           "\\\\computer\\share");
1265
1266         t(&(WindowsPath("C:/whatever")
1267             .unsafe_join(&WindowsPath("//computer/share/a/b"))),
1268           "\\\\computer\\share\\a\\b");
1269
1270         t(&(WindowsPath("C:")
1271             .unsafe_join(&WindowsPath("D:/foo"))),
1272           "D:\\foo");
1273
1274         t(&(WindowsPath("C:")
1275             .unsafe_join(&WindowsPath("B"))),
1276           "C:B");
1277
1278         t(&(WindowsPath("C:")
1279             .unsafe_join(&WindowsPath("/foo"))),
1280           "C:\\foo");
1281
1282         t(&(WindowsPath("C:\\")
1283             .unsafe_join(&WindowsPath("\\bar"))),
1284           "C:\\bar");
1285
1286         t(&(WindowsPath("")
1287             .unsafe_join(&WindowsPath(""))),
1288           "");
1289
1290         t(&(WindowsPath("")
1291             .unsafe_join(&WindowsPath("a"))),
1292           "a");
1293
1294         t(&(WindowsPath("")
1295             .unsafe_join(&WindowsPath("C:\\a"))),
1296           "C:\\a");
1297
1298         t(&(WindowsPath("c:\\foo")
1299             .normalize()),
1300           "C:\\foo");
1301     }
1302
1303     #[test]
1304     fn test_windows_path_restrictions() {
1305         assert_eq!(WindowsPath("hi").is_restricted(), false);
1306         assert_eq!(WindowsPath("C:\\NUL").is_restricted(), true);
1307         assert_eq!(WindowsPath("C:\\COM1.TXT").is_restricted(), true);
1308         assert_eq!(WindowsPath("c:\\prn.exe").is_restricted(), true);
1309     }
1310
1311     #[test]
1312     fn test_is_ancestor_of() {
1313         assert!(&PosixPath("/a/b").is_ancestor_of(&PosixPath("/a/b/c/d")));
1314         assert!(!&PosixPath("/a/b/c/d").is_ancestor_of(&PosixPath("/a/b")));
1315         assert!(!&PosixPath("/a/b").is_ancestor_of(&PosixPath("/c/d")));
1316         assert!(&PosixPath("/a/b").is_ancestor_of(&PosixPath("/a/b/c/d")));
1317         assert!(&PosixPath("/").is_ancestor_of(&PosixPath("/a/b/c")));
1318         assert!(!&PosixPath("/").is_ancestor_of(&PosixPath("")));
1319         assert!(!&PosixPath("/a/b/c").is_ancestor_of(&PosixPath("")));
1320         assert!(!&PosixPath("").is_ancestor_of(&PosixPath("/a/b/c")));
1321
1322         assert!(&WindowsPath("C:\\a\\b").is_ancestor_of(&WindowsPath("C:\\a\\b\\c\\d")));
1323         assert!(!&WindowsPath("C:\\a\\b\\c\\d").is_ancestor_of(&WindowsPath("C:\\a\\b")));
1324         assert!(!&WindowsPath("C:\\a\\b").is_ancestor_of(&WindowsPath("C:\\c\\d")));
1325         assert!(&WindowsPath("C:\\a\\b").is_ancestor_of(&WindowsPath("C:\\a\\b\\c\\d")));
1326         assert!(&WindowsPath("C:\\").is_ancestor_of(&WindowsPath("C:\\a\\b\\c")));
1327         assert!(!&WindowsPath("C:\\").is_ancestor_of(&WindowsPath("")));
1328         assert!(!&WindowsPath("C:\\a\\b\\c").is_ancestor_of(&WindowsPath("")));
1329         assert!(!&WindowsPath("").is_ancestor_of(&WindowsPath("C:\\a\\b\\c")));
1330
1331     }
1332
1333 }