about summary refs log tree commit diff
path: root/src/libcore/path.rs
diff options
context:
space:
mode:
authorGraydon Hoare <graydon@mozilla.com>2012-08-29 13:25:31 -0700
committerGraydon Hoare <graydon@mozilla.com>2012-08-29 13:26:51 -0700
commit6d22a805d73c6b6f42e496fc23cffbf6ffeeed1b (patch)
treee689224c339c233683520f3effb00e0773d1e93a /src/libcore/path.rs
parentcb8ecd79849160d8cff2eff8b070efd7f9a3b9a7 (diff)
downloadrust-6d22a805d73c6b6f42e496fc23cffbf6ffeeed1b.tar.gz
rust-6d22a805d73c6b6f42e496fc23cffbf6ffeeed1b.zip
Replace core::path with contents of core::path2. Back to one path module.
Diffstat (limited to 'src/libcore/path.rs')
-rw-r--r--src/libcore/path.rs875
1 files changed, 474 insertions, 401 deletions
diff --git a/src/libcore/path.rs b/src/libcore/path.rs
index 5352027a5b3..235dd686a1d 100644
--- a/src/libcore/path.rs
+++ b/src/libcore/path.rs
@@ -1,504 +1,577 @@
-//! Path data type and helper functions
-
-export Path;
-export consts;
-export path_is_absolute;
-export path_sep;
-export dirname;
-export basename;
-export connect;
-export connect_many;
-export split;
-export splitext;
-export normalize;
-
-// FIXME: This type should probably be constrained (#2624)
-/// A path or fragment of a filesystem path
-type Path = ~str;
-
-#[cfg(unix)]
-mod consts {
-    /**
-     * The primary path separator character for the platform
-     *
-     * On all platforms it is '/'
-     */
-    const path_sep: char = '/';
-    /**
-     * The secondary path separator character for the platform
-     *
-     * On Unixes it is '/'. On Windows it is '\'.
-     */
-    const alt_path_sep: char = '/';
+// NB: transitionary, de-mode-ing.
+#[forbid(deprecated_mode)];
+#[forbid(deprecated_pattern)];
+
+struct WindowsPath {
+    host: Option<~str>;
+    device: Option<~str>;
+    is_absolute: bool;
+    components: ~[~str];
 }
 
-#[cfg(windows)]
-mod consts {
-    const path_sep: char = '/';
-    const alt_path_sep: char = '\\';
+struct PosixPath {
+    is_absolute: bool;
+    components: ~[~str];
 }
 
-/**
- * Indicates whether a path is absolute.
- *
- * A path is considered absolute if it begins at the filesystem root ("/") or,
- * on Windows, begins with a drive letter.
- */
-#[cfg(unix)]
-fn path_is_absolute(p: Path) -> bool {
-    str::char_at(p, 0u) == '/'
-}
+trait GenericPath {
 
-#[cfg(windows)]
-fn path_is_absolute(p: ~str) -> bool {
-    return str::char_at(p, 0u) == '/' ||
-        str::char_at(p, 1u) == ':'
-        && (str::char_at(p, 2u) == consts::path_sep
-            || str::char_at(p, 2u) == consts::alt_path_sep);
-}
+    static pure fn from_str((&str)) -> self;
 
-/// Get the default path separator for the host platform
-fn path_sep() -> ~str { return str::from_char(consts::path_sep); }
+    pure fn dirname() -> ~str;
+    pure fn filename() -> Option<~str>;
+    pure fn filestem() -> Option<~str>;
+    pure fn filetype() -> Option<~str>;
 
-fn split_dirname_basename (pp: Path) -> {dirname: ~str, basename: ~str} {
-    match str::rfind(pp, |ch|
-        ch == consts::path_sep || ch == consts::alt_path_sep
-    ) {
-      Some(i) => {
-        dirname: str::slice(pp, 0u, i),
-        basename: str::slice(pp, i + 1u, str::len(pp))
-      },
-      None => {dirname: ~".", basename: pp}
-    }
+    pure fn with_dirname((&str)) -> self;
+    pure fn with_filename((&str)) -> self;
+    pure fn with_filestem((&str)) -> self;
+    pure fn with_filetype((&str)) -> self;
+
+    pure fn push((&str)) -> self;
+    pure fn push_rel((&self)) -> self;
+    pure fn push_many((&[~str])) -> self;
+    pure fn pop() -> self;
 }
 
-/**
- * Get the directory portion of a path
- *
- * Returns all of the path up to, but excluding, the final path separator.
- * The dirname of "/usr/share" will be "/usr", but the dirname of
- * "/usr/share/" is "/usr/share".
- *
- * If the path is not prefixed with a directory, then "." is returned.
- */
-fn dirname(pp: Path) -> Path {
-    return split_dirname_basename(pp).dirname;
+#[cfg(windows)]
+type Path = WindowsPath;
+
+#[cfg(windows)]
+pure fn Path(s: &str) -> Path {
+    from_str::<WindowsPath>(s)
 }
 
-/**
- * Get the file name portion of a path
- *
- * Returns the portion of the path after the final path separator.
- * The basename of "/usr/share" will be "share". If there are no
- * path separators in the path then the returned path is identical to
- * the provided path. If an empty path is provided or the path ends
- * with a path separator then an empty path is returned.
- */
-fn basename(pp: Path) -> Path {
-    return split_dirname_basename(pp).basename;
+#[cfg(unix)]
+type Path = PosixPath;
+
+#[cfg(unix)]
+pure fn Path(s: &str) -> Path {
+    from_str::<PosixPath>(s)
 }
 
-/**
- * Connects to path segments
- *
- * Given paths `pre` and `post, removes any trailing path separator on `pre`
- * and any leading path separator on `post`, and returns the concatenation of
- * the two with a single path separator between them.
- */
-fn connect(pre: Path, post: Path) -> Path {
-    let mut pre_ = pre;
-    let mut post_ = post;
-    let sep = consts::path_sep as u8;
-    let pre_len  = str::len(pre);
-    let post_len = str::len(post);
-    unsafe {
-        if pre_len > 1u && pre[pre_len-1u] == sep {
-            str::unsafe::pop_byte(pre_);
-        }
-        if post_len > 1u && post[0] == sep {
-            str::unsafe::shift_byte(post_);
+impl PosixPath : ToStr {
+    fn to_str() -> ~str {
+        let mut s = ~"";
+        if self.is_absolute {
+            s += "/";
         }
+        s + str::connect(self.components, "/")
     }
-    return pre_ + path_sep() + post_;
 }
 
-/**
- * Connects a vector of path segments into a single path.
- *
- * Inserts path separators as needed.
- */
-fn connect_many(paths: ~[Path]) -> Path {
-    return if vec::len(paths) == 1u {
-        paths[0]
-    } else {
-        let rest = vec::slice(paths, 1u, vec::len(paths));
-        connect(paths[0], connect_many(rest))
-    }
-}
+// FIXME (#3227): when default methods in traits are working, de-duplicate
+// PosixPath and WindowsPath, most of their methods are common.
+impl PosixPath : GenericPath {
 
-/**
- * Split a path into its individual components
- *
- * Splits a given path by path separators and returns a vector containing
- * each piece of the path. On Windows, if the path is absolute then
- * the first element of the returned vector will be the drive letter
- * followed by a colon.
- */
-fn split(p: Path) -> ~[Path] {
-    str::split_nonempty(p, |c| {
-        c == consts::path_sep || c == consts::alt_path_sep
-    })
-}
+    static pure fn from_str(s: &str) -> PosixPath {
+        let mut components = str::split_nonempty(s, |c| c == '/');
+        let is_absolute = (s.len() != 0 && s[0] == '/' as u8);
+        return PosixPath { is_absolute: is_absolute,
+                           components: normalize(components) }
+    }
 
-/**
- * Split a path into the part before the extension and the extension
- *
- * Split a path into a pair of strings with the first element being the
- * filename without the extension and the second being either empty or the
- * file extension including the period. Leading periods in the basename are
- * ignored.  If the path includes directory components then they are included
- * in the filename part of the result pair.
- */
-fn splitext(p: Path) -> (~str, ~str) {
-    if str::is_empty(p) { (~"", ~"") }
-    else {
-        let parts = str::split_char(p, '.');
-        if vec::len(parts) > 1u {
-            let base = str::connect(vec::init(parts), ~".");
-            // We just checked that parts is non-empty, so this is safe
-            let ext = ~"." + vec::last(parts);
-
-            fn is_dotfile(base: ~str) -> bool {
-                str::is_empty(base)
-                    || str::ends_with(
-                        base, str::from_char(consts::path_sep))
-                    || str::ends_with(
-                        base, str::from_char(consts::alt_path_sep))
+    pure fn dirname() -> ~str {
+        unchecked {
+            let s = self.dir_path().to_str();
+            if s.len() == 0 {
+                ~"."
+            } else {
+                s
             }
+        }
+    }
 
-            fn ext_contains_sep(ext: ~str) -> bool {
-                vec::len(split(ext)) > 1u
-            }
+    pure fn filename() -> Option<~str> {
+        match self.components.len() {
+          0 => None,
+          n => Some(copy self.components[n - 1])
+        }
+    }
 
-            fn no_basename(ext: ~str) -> bool {
-                str::ends_with(
-                    ext, str::from_char(consts::path_sep))
-                    || str::ends_with(
-                        ext, str::from_char(consts::alt_path_sep))
+    pure fn filestem() -> Option<~str> {
+        match self.filename() {
+          None => None,
+          Some(ref f) => {
+            match str::rfind_char(*f, '.') {
+              Some(p) => Some(f.slice(0, p)),
+              None => Some(copy *f)
             }
+          }
+        }
+    }
 
-            if is_dotfile(base)
-                || ext_contains_sep(ext)
-                || no_basename(ext) {
-                (p, ~"")
-            } else {
-                (base, ext)
+    pure fn filetype() -> Option<~str> {
+        match self.filename() {
+          None => None,
+          Some(ref f) => {
+            match str::rfind_char(*f, '.') {
+              Some(p) if p+1 < f.len() => Some(f.slice(p+1, f.len())),
+              _ => None
             }
-        } else {
-            (p, ~"")
+          }
         }
     }
-}
 
-/**
- * Collapses redundant path separators.
- *
- * Does not follow symbolic links.
- *
- * # Examples
- *
- * * '/a/../b' becomes '/b'
- * * 'a/./b/' becomes 'a/b/'
- * * 'a/b/../../../' becomes '..'
- * * '/a/b/c/../d/./../../e/' becomes '/a/e/'
- */
-fn normalize(p: Path) -> Path {
-    let s = split(p);
-    let s = strip_dots(s);
-    let s = rollup_doubledots(s);
-
-    let s = if vec::is_not_empty(s) {
-        connect_many(s)
-    } else {
-        ~""
-    };
-    let s = reabsolute(p, s);
-    let s = reterminate(p, s);
-
-    let s = if str::len(s) == 0u {
-        ~"."
-    } else {
-        s
-    };
-
-    return s;
-
-    fn strip_dots(s: ~[Path]) -> ~[Path] {
-        vec::filter_map(s, |elem|
-            if elem == ~"." {
-                option::None
-            } else {
-                option::Some(elem)
-            })
+    pure fn with_dirname(d: &str) -> PosixPath {
+        let dpath = from_str::<PosixPath>(d);
+        match self.filename() {
+          Some(ref f) => dpath.push(*f),
+          None => dpath
+        }
     }
 
-    fn rollup_doubledots(s: ~[Path]) -> ~[Path] {
-        if vec::is_empty(s) {
-            return ~[];
+    pure fn with_filename(f: &str) -> PosixPath {
+        unchecked {
+            assert ! str::any(f, |c| windows::is_sep(c as u8));
+            self.dir_path().push(f)
         }
+    }
 
-        let mut t = ~[];
-        let mut i = vec::len(s);
-        let mut skip = 0;
-        while i != 0u {
-            i -= 1u;
-            if s[i] == ~".." {
-                skip += 1;
-            } else {
-                if skip == 0 {
-                    vec::push(t, s[i]);
-                } else {
-                    skip -= 1;
-                }
-            }
+    pure fn with_filestem(s: &str) -> PosixPath {
+        match self.filetype() {
+          None => self.with_filename(s),
+          Some(ref t) =>
+          self.with_filename(str::from_slice(s) + "." + *t)
         }
-        let mut t = vec::reversed(t);
-        while skip > 0 {
-            vec::push(t, ~"..");
-            skip -= 1;
-        }
-        return t;
     }
 
-    #[cfg(unix)]
-    fn reabsolute(orig: Path, n: Path) -> Path {
-        if path_is_absolute(orig) {
-            path_sep() + n
+    pure fn with_filetype(t: &str) -> PosixPath {
+        if t.len() == 0 {
+            match self.filestem() {
+              None => copy self,
+              Some(s) => self.with_filename(s)
+            }
         } else {
-            n
+            let t = ~"." + str::from_slice(t);
+            match self.filestem() {
+              None => self.with_filename(t),
+              Some(ref s) =>
+              self.with_filename(*s + t)
+            }
         }
     }
 
-    #[cfg(windows)]
-    fn reabsolute(orig: Path, newp: Path) -> Path {
-       if path_is_absolute(orig) && orig[0] == consts::path_sep as u8 {
-           str::from_char(consts::path_sep) + newp
-       } else {
-           newp
-       }
-    }
-
-    fn reterminate(orig: Path, newp: Path) -> Path {
-        let last = orig[str::len(orig) - 1u];
-        if last == consts::path_sep as u8
-            || last == consts::path_sep as u8 {
-            return newp + path_sep();
+    pure fn dir_path() -> PosixPath {
+        if self.components.len() != 0 {
+            self.pop()
         } else {
-            return newp;
+            copy self
         }
     }
-}
 
-#[cfg(test)]
-mod tests {
-    #[test]
-    fn test_connect() {
-        let slash = path_sep();
-        log(error, connect(~"a", ~"b"));
-        assert (connect(~"a", ~"b") == ~"a" + slash + ~"b");
-        assert (connect(~"a" + slash, ~"b") == ~"a" + slash + ~"b");
+    pure fn file_path() -> PosixPath {
+        let cs = match self.filename() {
+          None => ~[],
+          Some(ref f) => ~[copy *f]
+        };
+        return PosixPath { is_absolute: false,
+                           components: cs }
     }
 
-    fn ps() -> ~str {
-        path_sep()
+    pure fn push_rel(other: &PosixPath) -> PosixPath {
+        assert !other.is_absolute;
+        self.push_many(other.components)
     }
 
-    fn aps() -> ~str {
-        ~"/"
+    pure fn push_many(cs: &[~str]) -> PosixPath {
+        return PosixPath { components: normalize(self.components + cs),
+                           ..self }
     }
 
-    #[test]
-    fn split1() {
-        let actual = split(~"a" + ps() + ~"b");
-        let expected = ~[~"a", ~"b"];
-        assert actual == expected;
+    pure fn push(s: &str) -> PosixPath {
+        let mut cs = self.components;
+        unchecked { vec::push(cs, move str::from_slice(s)); }
+        cs = normalize(cs);
+        return PosixPath { components: move cs,
+                           ..self }
     }
 
-    #[test]
-    fn split2() {
-        let actual = split(~"a" + aps() + ~"b");
-        let expected = ~[~"a", ~"b"];
-        assert actual == expected;
+    pure fn pop() -> PosixPath {
+        let mut cs = copy self.components;
+        if cs.len() != 0 {
+            unchecked { vec::pop(cs); }
+        }
+        return PosixPath { components: move cs, ..self }
     }
+}
 
-    #[test]
-    fn split3() {
-        let actual = split(ps() + ~"a" + ps() + ~"b");
-        let expected = ~[~"a", ~"b"];
-        assert actual == expected;
+
+impl WindowsPath : ToStr {
+    fn to_str() -> ~str {
+        let mut s = ~"";
+        match self.host {
+          Some(h) => { s += "\\\\"; s += h; }
+          None => { }
+        }
+        match self.device {
+          Some(d) => { s += d; s += ":"; }
+          None => { }
+        }
+        if self.is_absolute {
+            s += "\\";
+        }
+        s + str::connect(self.components, "\\")
     }
+}
 
-    #[test]
-    fn split4() {
-        let actual = split(~"a" + ps() + ~"b" + aps() + ~"c");
-        let expected = ~[~"a", ~"b", ~"c"];
-        assert actual == expected;
+
+impl WindowsPath : GenericPath {
+
+    static pure fn from_str(s: &str) -> WindowsPath {
+        let host;
+        let device;
+        let rest;
+
+        match windows::extract_drive_prefix(s) {
+          Some((ref d, ref r)) => {
+            host = None;
+            device = Some(copy *d);
+            rest = copy *r;
+          }
+          None => {
+            match windows::extract_unc_prefix(s) {
+              Some((ref h, ref r)) => {
+                host = Some(copy *h);
+                device = None;
+                rest = copy *r;
+              }
+              None => {
+                host = None;
+                device = None;
+                rest = str::from_slice(s);
+              }
+            }
+          }
+        }
+
+        let mut components =
+            str::split_nonempty(rest, |c| windows::is_sep(c as u8));
+        let is_absolute = (rest.len() != 0 && windows::is_sep(rest[0]));
+        return WindowsPath { host: host,
+                             device: device,
+                             is_absolute: is_absolute,
+                             components: normalize(components) }
     }
 
-    #[test]
-    fn normalize1() {
-        let actual = normalize(~"a/b/..");
-        let expected = ~"a";
-        assert actual == expected;
+    pure fn dirname() -> ~str {
+        unchecked {
+            let s = self.dir_path().to_str();
+            if s.len() == 0 {
+                ~"."
+            } else {
+                s
+            }
+        }
     }
 
-    #[test]
-    fn normalize2() {
-        let actual = normalize(~"/a/b/..");
-        let expected = ~"/a";
-        assert actual == expected;
+    pure fn filename() -> Option<~str> {
+        match self.components.len() {
+          0 => None,
+          n => Some(copy self.components[n - 1])
+        }
     }
 
-    #[test]
-    fn normalize3() {
-        let actual = normalize(~"a/../b");
-        let expected = ~"b";
-        assert actual == expected;
+    pure fn filestem() -> Option<~str> {
+        match self.filename() {
+          None => None,
+          Some(ref f) => {
+            match str::rfind_char(*f, '.') {
+              Some(p) => Some(f.slice(0, p)),
+              None => Some(copy *f)
+            }
+          }
+        }
     }
 
-    #[test]
-    fn normalize4() {
-        let actual = normalize(~"/a/../b");
-        let expected = ~"/b";
-        assert actual == expected;
+    pure fn filetype() -> Option<~str> {
+        match self.filename() {
+          None => None,
+          Some(ref f) => {
+            match str::rfind_char(*f, '.') {
+              Some(p) if p+1 < f.len() => Some(f.slice(p+1, f.len())),
+              _ => None
+            }
+          }
+        }
     }
 
-    #[test]
-    fn normalize5() {
-        let actual = normalize(~"a/.");
-        let expected = ~"a";
-        assert actual == expected;
+    pure fn with_dirname(d: &str) -> WindowsPath {
+        let dpath = from_str::<WindowsPath>(d);
+        match self.filename() {
+          Some(ref f) => dpath.push(*f),
+          None => dpath
+        }
     }
 
-    #[test]
-    fn normalize6() {
-        let actual = normalize(~"a/./b/");
-        let expected = ~"a/b/";
-        assert actual == expected;
+    pure fn with_filename(f: &str) -> WindowsPath {
+        assert ! str::any(f, |c| windows::is_sep(c as u8));
+        self.dir_path().push(f)
     }
 
-    #[test]
-    fn normalize7() {
-        let actual = normalize(~"a/..");
-        let expected = ~".";
-        assert actual == expected;
+    pure fn with_filestem(s: &str) -> WindowsPath {
+        match self.filetype() {
+          None => self.with_filename(s),
+          Some(ref t) =>
+          self.with_filename(str::from_slice(s) + "." + *t)
+        }
     }
 
-    #[test]
-    fn normalize8() {
-        let actual = normalize(~"../../..");
-        let expected = ~"../../..";
-        assert actual == expected;
+    pure fn with_filetype(t: &str) -> WindowsPath {
+        if t.len() == 0 {
+            match self.filestem() {
+              None => copy self,
+              Some(s) => self.with_filename(s)
+            }
+        } else {
+            let t = ~"." + str::from_slice(t);
+            match self.filestem() {
+              None => self.with_filename(t),
+              Some(ref s) =>
+              self.with_filename(*s + t)
+            }
+        }
     }
 
-    #[test]
-    fn normalize9() {
-        let actual = normalize(~"a/b/../../..");
-        let expected = ~"..";
-        assert actual == expected;
+    pure fn dir_path() -> WindowsPath {
+        if self.components.len() != 0 {
+            self.pop()
+        } else {
+            copy self
+        }
     }
 
-    #[test]
-    fn normalize10() {
-        let actual = normalize(~"/a/b/c/../d/./../../e/");
-        let expected = ~"/a/e/";
-        log(error, actual);
-        assert actual == expected;
+    pure fn file_path() -> WindowsPath {
+        let cs = match self.filename() {
+          None => ~[],
+          Some(ref f) => ~[copy *f]
+        };
+        return WindowsPath { host: None,
+                             device: None,
+                             is_absolute: false,
+                             components: cs }
     }
 
-    #[test]
-    fn normalize11() {
-        let actual = normalize(~"/a/..");
-        let expected = ~"/";
-        assert actual == expected;
+    pure fn push_rel(other: &WindowsPath) -> WindowsPath {
+        assert !other.is_absolute;
+        self.push_many(other.components)
     }
 
-    #[test]
-    #[cfg(windows)]
-    fn normalize12() {
-        let actual = normalize(~"C:/whatever");
-        let expected = ~"C:/whatever";
-        log(error, actual);
-        assert actual == expected;
+    pure fn push_many(cs: &[~str]) -> WindowsPath {
+        return WindowsPath { components: normalize(self.components + cs),
+                            ..self }
     }
 
-    #[test]
-    #[cfg(windows)]
-    fn path_is_absolute_win32() {
-        assert path_is_absolute(~"C:/whatever");
+    pure fn push(s: &str) -> WindowsPath {
+        let mut cs = self.components;
+        unchecked { vec::push(cs, move str::from_slice(s)); }
+        cs = normalize(cs);
+        return WindowsPath { components: move cs,
+                             ..self }
     }
 
-    #[test]
-    fn splitext_empty() {
-        let (base, ext) = splitext(~"");
-        assert base == ~"";
-        assert ext == ~"";
+    pure fn pop() -> WindowsPath {
+        let mut cs = copy self.components;
+        if cs.len() != 0 {
+            unchecked { vec::pop(cs); }
+        }
+        return WindowsPath { components: move cs, ..self }
     }
+}
 
-    #[test]
-    fn splitext_ext() {
-        let (base, ext) = splitext(~"grum.exe");
-        assert base == ~"grum";
-        assert ext == ~".exe";
+
+pure fn normalize(components: &[~str]) -> ~[~str] {
+    let mut cs = ~[];
+    unchecked {
+        for components.each |c| {
+            unchecked {
+                if c == ~"." && components.len() > 1 { again; }
+                if c == ~".." && cs.len() != 0 {
+                    vec::pop(cs);
+                    again;
+                }
+                vec::push(cs, copy c);
+            }
+        }
     }
+    cs
+}
+
+mod posix {
 
     #[test]
-    fn splitext_noext() {
-        let (base, ext) = splitext(~"grum");
-        assert base == ~"grum";
-        assert ext == ~"";
+    fn test_posix_paths() {
+        fn mk(s: &str) -> PosixPath { from_str::<PosixPath>(s) }
+        fn t(wp: &PosixPath, s: &str) {
+            let ss = wp.to_str();
+            let sss = str::from_slice(s);
+            if (ss != sss) {
+                debug!("got %s", ss);
+                debug!("expected %s", sss);
+                assert ss == sss;
+            }
+        }
+
+        t(&(mk("hi")), "hi");
+        t(&(mk("/lib")), "/lib");
+        t(&(mk("hi/there")), "hi/there");
+        t(&(mk("hi/there.txt")), "hi/there.txt");
+
+        t(&(mk("hi/there.txt")), "hi/there.txt");
+        t(&(mk("hi/there.txt")
+           .with_filetype("")), "hi/there");
+
+        t(&(mk("/a/b/c/there.txt")
+            .with_dirname("hi")), "hi/there.txt");
+
+        t(&(mk("hi/there.txt")
+            .with_dirname(".")), "there.txt");
+
+        t(&(mk("a/b/../c/././/../foo.txt/")),
+          "a/foo.txt");
+
+        t(&(mk("a/b/c")
+            .push("..")), "a/b");
+
+        t(&(mk("there.txt")
+            .with_filetype("o")), "there.o");
+
+        t(&(mk("hi/there.txt")
+            .with_filetype("o")), "hi/there.o");
+
+        t(&(mk("hi/there.txt")
+            .with_filetype("o")
+            .with_dirname("/usr/lib")),
+          "/usr/lib/there.o");
+
+        t(&(mk("hi/there.txt")
+            .with_filetype("o")
+            .with_dirname("/usr/lib/")),
+          "/usr/lib/there.o");
+
+        t(&(mk("hi/there.txt")
+            .with_filetype("o")
+            .with_dirname("/usr//lib//")),
+            "/usr/lib/there.o");
+
+        t(&(mk("/usr/bin/rust")
+            .push_many([~"lib", ~"thingy.so"])
+            .with_filestem("librustc")),
+          "/usr/bin/rust/lib/librustc.so");
+
     }
 
-    #[test]
-    fn splitext_dotfile() {
-        let (base, ext) = splitext(~".grum");
-        assert base == ~".grum";
-        assert ext == ~"";
+}
+
+// Various windows helpers, and tests for the impl.
+mod windows {
+
+    #[inline(always)]
+    pure fn is_sep(u: u8) -> bool {
+        u == '/' as u8 || u == '\\' as u8
     }
 
-    #[test]
-    fn splitext_path_ext() {
-        let (base, ext) = splitext(~"oh/grum.exe");
-        assert base == ~"oh/grum";
-        assert ext == ~".exe";
+    pure fn extract_unc_prefix(s: &str) -> Option<(~str,~str)> {
+        if (s.len() > 1 &&
+            s[0] == '\\' as u8 &&
+            s[1] == '\\' as u8) {
+            let mut i = 2;
+            while i < s.len() {
+                if s[i] == '\\' as u8 {
+                    let pre = s.slice(2, i);
+                    let rest = s.slice(i, s.len());
+                    return Some((pre, rest));
+                }
+                i += 1;
+            }
+        }
+        None
+    }
+
+    pure fn extract_drive_prefix(s: &str) -> Option<(~str,~str)> {
+        unchecked {
+            if (s.len() > 1 &&
+                libc::isalpha(s[0] as libc::c_int) != 0 &&
+                s[1] == ':' as u8) {
+                let rest = if s.len() == 2 {
+                    ~""
+                } else {
+                    s.slice(2, s.len())
+                };
+                return Some((s.slice(0,1), rest));
+            }
+            None
+        }
     }
 
     #[test]
-    fn splitext_path_noext() {
-        let (base, ext) = splitext(~"oh/grum");
-        assert base == ~"oh/grum";
-        assert ext == ~"";
+    fn test_extract_unc_prefixes() {
+        assert extract_unc_prefix("\\\\").is_none();
+        assert extract_unc_prefix("\\\\hi").is_none();
+        assert extract_unc_prefix("\\\\hi\\") == Some((~"hi", ~"\\"));
+        assert extract_unc_prefix("\\\\hi\\there") ==
+            Some((~"hi", ~"\\there"));
+        assert extract_unc_prefix("\\\\hi\\there\\friends.txt") ==
+            Some((~"hi", ~"\\there\\friends.txt"));
     }
 
     #[test]
-    fn splitext_dot_in_path() {
-        let (base, ext) = splitext(~"oh.my/grum");
-        assert base == ~"oh.my/grum";
-        assert ext == ~"";
+    fn test_extract_drive_prefixes() {
+        assert extract_drive_prefix("c").is_none();
+        assert extract_drive_prefix("c:") == Some((~"c", ~""));
+        assert extract_drive_prefix("d:") == Some((~"d", ~""));
+        assert extract_drive_prefix("z:") == Some((~"z", ~""));
+        assert extract_drive_prefix("c:\\hi") == Some((~"c", ~"\\hi"));
+        assert extract_drive_prefix("d:hi") == Some((~"d", ~"hi"));
+        assert extract_drive_prefix("c:hi\\there.txt") ==
+            Some((~"c", ~"hi\\there.txt"));
+        assert extract_drive_prefix("c:\\hi\\there.txt") ==
+            Some((~"c", ~"\\hi\\there.txt"));
     }
 
     #[test]
-    fn splitext_nobasename() {
-        let (base, ext) = splitext(~"oh.my/");
-        assert base == ~"oh.my/";
-        assert ext == ~"";
+    fn test_windows_paths() {
+        fn mk(s: &str) -> WindowsPath { from_str::<WindowsPath>(s) }
+        fn t(wp: &WindowsPath, s: &str) {
+            let ss = wp.to_str();
+            let sss = str::from_slice(s);
+            if (ss != sss) {
+                debug!("got %s", ss);
+                debug!("expected %s", sss);
+                assert ss == sss;
+            }
+        }
+
+        t(&(mk("hi")), "hi");
+        t(&(mk("hi/there")), "hi\\there");
+        t(&(mk("hi/there.txt")), "hi\\there.txt");
+
+        t(&(mk("there.txt")
+            .with_filetype("o")), "there.o");
+
+        t(&(mk("hi/there.txt")
+            .with_filetype("o")), "hi\\there.o");
+
+        t(&(mk("hi/there.txt")
+            .with_filetype("o")
+            .with_dirname("c:\\program files A")),
+          "c:\\program files A\\there.o");
+
+        t(&(mk("hi/there.txt")
+            .with_filetype("o")
+            .with_dirname("c:\\program files B\\")),
+          "c:\\program files B\\there.o");
+
+        t(&(mk("hi/there.txt")
+            .with_filetype("o")
+            .with_dirname("c:\\program files C\\/")),
+            "c:\\program files C\\there.o");
+
+        t(&(mk("c:\\program files (x86)\\rust")
+            .push_many([~"lib", ~"thingy.dll"])
+            .with_filename("librustc.dll")),
+          "c:\\program files (x86)\\rust\\lib\\librustc.dll");
+
     }
-}
 
-// Local Variables:
-// mode: rust;
-// fill-column: 78;
-// indent-tabs-mode: nil
-// c-basic-offset: 4
-// buffer-file-coding-system: utf-8-unix
-// End:
+}