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