405 lines
9.7 KiB
Rust
405 lines
9.7 KiB
Rust
/*
|
|
Module: fs
|
|
|
|
File system manipulation
|
|
*/
|
|
|
|
import core::ctypes;
|
|
import core::vec;
|
|
import core::option;
|
|
import os;
|
|
import os::getcwd;
|
|
import os_fs;
|
|
|
|
#[abi = "cdecl"]
|
|
native mod rustrt {
|
|
fn rust_path_is_dir(path: str::sbuf) -> int;
|
|
fn rust_path_exists(path: str::sbuf) -> int;
|
|
}
|
|
|
|
/*
|
|
Function: path_sep
|
|
|
|
Get the default path separator for the host platform
|
|
*/
|
|
fn path_sep() -> str { ret str::from_char(os_fs::path_sep); }
|
|
|
|
// FIXME: This type should probably be constrained
|
|
/*
|
|
Type: path
|
|
|
|
A path or fragment of a filesystem path
|
|
*/
|
|
type path = str;
|
|
|
|
/*
|
|
Function: dirname
|
|
|
|
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(p: path) -> path {
|
|
let i: int = str::rindex(p, os_fs::path_sep as u8);
|
|
if i == -1 {
|
|
i = str::rindex(p, os_fs::alt_path_sep as u8);
|
|
if i == -1 { ret "."; }
|
|
}
|
|
ret str::substr(p, 0u, i as uint);
|
|
}
|
|
|
|
/*
|
|
Function: basename
|
|
|
|
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(p: path) -> path {
|
|
let i: int = str::rindex(p, os_fs::path_sep as u8);
|
|
if i == -1 {
|
|
i = str::rindex(p, os_fs::alt_path_sep as u8);
|
|
if i == -1 { ret p; }
|
|
}
|
|
let len = str::byte_len(p);
|
|
if i + 1 as uint >= len { ret p; }
|
|
ret str::slice(p, i + 1 as uint, len);
|
|
}
|
|
|
|
|
|
// FIXME: Need some typestate to avoid bounds check when len(pre) == 0
|
|
/*
|
|
Function: connect
|
|
|
|
Connects to path segments
|
|
|
|
Given paths `pre` and `post` this function will return a path
|
|
that is equal to `post` appended to `pre`, inserting a path separator
|
|
between the two as needed.
|
|
*/
|
|
fn connect(pre: path, post: path) -> path {
|
|
let len = str::byte_len(pre);
|
|
ret if pre[len - 1u] == os_fs::path_sep as u8 {
|
|
|
|
// Trailing '/'?
|
|
pre + post
|
|
} else { pre + path_sep() + post };
|
|
}
|
|
|
|
/*
|
|
Function: connect_many
|
|
|
|
Connects a vector of path segments into a single path.
|
|
|
|
Inserts path separators as needed.
|
|
*/
|
|
fn connect_many(paths: [path]) : vec::is_not_empty(paths) -> path {
|
|
ret if vec::len(paths) == 1u {
|
|
paths[0]
|
|
} else {
|
|
let rest = vec::slice(paths, 1u, vec::len(paths));
|
|
check vec::is_not_empty(rest);
|
|
connect(paths[0], connect_many(rest))
|
|
}
|
|
}
|
|
|
|
/*
|
|
Function: path_is_dir
|
|
|
|
Indicates whether a path represents a directory.
|
|
*/
|
|
fn path_is_dir(p: path) -> bool {
|
|
ret str::as_buf(p, {|buf| rustrt::rust_path_is_dir(buf) != 0 });
|
|
}
|
|
|
|
/*
|
|
Function: path_exists
|
|
|
|
Indicates whether a path exists.
|
|
*/
|
|
fn path_exists(p: path) -> bool {
|
|
ret str::as_buf(p, {|buf| rustrt::rust_path_exists(buf) != 0 });
|
|
}
|
|
|
|
/*
|
|
Function: make_dir
|
|
|
|
Creates a directory at the specified path.
|
|
*/
|
|
fn make_dir(p: path, mode: ctypes::c_int) -> bool {
|
|
ret mkdir(p, mode);
|
|
|
|
#[cfg(target_os = "win32")]
|
|
fn mkdir(_p: path, _mode: ctypes::c_int) -> bool unsafe {
|
|
// FIXME: turn mode into something useful?
|
|
ret str::as_buf(_p, {|buf|
|
|
os::kernel32::CreateDirectoryA(
|
|
buf, unsafe::reinterpret_cast(0))
|
|
});
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
#[cfg(target_os = "macos")]
|
|
fn mkdir(_p: path, _mode: ctypes::c_int) -> bool {
|
|
ret str::as_buf(_p, {|buf| os::libc::mkdir(buf, _mode) == 0i32 });
|
|
}
|
|
}
|
|
|
|
/*
|
|
Function: list_dir
|
|
|
|
Lists the contents of a directory.
|
|
*/
|
|
fn list_dir(p: path) -> [str] {
|
|
let p = p;
|
|
let pl = str::byte_len(p);
|
|
if pl == 0u || p[pl - 1u] as char != os_fs::path_sep { p += path_sep(); }
|
|
let full_paths: [str] = [];
|
|
for filename: str in os_fs::list_dir(p) {
|
|
if !str::eq(filename, ".") {
|
|
if !str::eq(filename, "..") { full_paths += [p + filename]; }
|
|
}
|
|
}
|
|
ret full_paths;
|
|
}
|
|
|
|
/*
|
|
Function: remove_dir
|
|
|
|
Removes a directory at the specified path.
|
|
*/
|
|
fn remove_dir(p: path) -> bool {
|
|
ret rmdir(p);
|
|
|
|
#[cfg(target_os = "win32")]
|
|
fn rmdir(_p: path) -> bool {
|
|
ret str::as_buf(_p, {|buf| os::kernel32::RemoveDirectoryA(buf)});
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
#[cfg(target_os = "macos")]
|
|
fn rmdir(_p: path) -> bool {
|
|
ret str::as_buf(_p, {|buf| os::libc::rmdir(buf) == 0i32 });
|
|
}
|
|
}
|
|
|
|
fn change_dir(p: path) -> bool {
|
|
ret chdir(p);
|
|
|
|
#[cfg(target_os = "win32")]
|
|
fn chdir(_p: path) -> bool {
|
|
ret str::as_buf(_p, {|buf| os::kernel32::SetCurrentDirectoryA(buf)});
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
#[cfg(target_os = "macos")]
|
|
fn chdir(_p: path) -> bool {
|
|
ret str::as_buf(_p, {|buf| os::libc::chdir(buf) == 0i32 });
|
|
}
|
|
}
|
|
|
|
/*
|
|
Function: path_is_absolute
|
|
|
|
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.
|
|
*/
|
|
fn path_is_absolute(p: path) -> bool { ret os_fs::path_is_absolute(p); }
|
|
|
|
// FIXME: under Windows, we should prepend the current drive letter to paths
|
|
// that start with a slash.
|
|
/*
|
|
Function: make_absolute
|
|
|
|
Convert a relative path to an absolute path
|
|
|
|
If the given path is relative, return it prepended with the current working
|
|
directory. If the given path is already an absolute path, return it
|
|
as is.
|
|
*/
|
|
fn make_absolute(p: path) -> path {
|
|
if path_is_absolute(p) { ret p; } else { ret connect(getcwd(), p); }
|
|
}
|
|
|
|
/*
|
|
Function: split
|
|
|
|
Split a path into it's 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] {
|
|
let split1 = str::split(p, os_fs::path_sep as u8);
|
|
let split2 = [];
|
|
for s in split1 {
|
|
split2 += str::split(s, os_fs::alt_path_sep as u8);
|
|
}
|
|
ret split2;
|
|
}
|
|
|
|
/*
|
|
Function: splitext
|
|
|
|
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(p, '.' as u8);
|
|
if vec::len(parts) > 1u {
|
|
let base = str::connect(vec::init(parts), ".");
|
|
let ext = "." + option::get(vec::last(parts));
|
|
|
|
fn is_dotfile(base: str) -> bool {
|
|
str::is_empty(base)
|
|
|| str::ends_with(
|
|
base, str::from_char(os_fs::path_sep))
|
|
|| str::ends_with(
|
|
base, str::from_char(os_fs::alt_path_sep))
|
|
}
|
|
|
|
fn ext_contains_sep(ext: str) -> bool {
|
|
vec::len(split(ext)) > 1u
|
|
}
|
|
|
|
fn no_basename(ext: str) -> bool {
|
|
str::ends_with(
|
|
ext, str::from_char(os_fs::path_sep))
|
|
|| str::ends_with(
|
|
ext, str::from_char(os_fs::alt_path_sep))
|
|
}
|
|
|
|
if is_dotfile(base)
|
|
|| ext_contains_sep(ext)
|
|
|| no_basename(ext) {
|
|
(p, "")
|
|
} else {
|
|
(base, ext)
|
|
}
|
|
} else {
|
|
(p, "")
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
Function: normalize
|
|
|
|
Removes extra "." and ".." entries from paths.
|
|
|
|
Does not follow symbolic links.
|
|
*/
|
|
fn normalize(p: path) -> path {
|
|
let s = split(p);
|
|
let s = strip_dots(s);
|
|
let s = rollup_doubledots(s);
|
|
|
|
let s = if check vec::is_not_empty(s) {
|
|
connect_many(s)
|
|
} else {
|
|
""
|
|
};
|
|
let s = reabsolute(p, s);
|
|
let s = reterminate(p, s);
|
|
|
|
let s = if str::byte_len(s) == 0u {
|
|
"."
|
|
} else {
|
|
s
|
|
};
|
|
|
|
ret s;
|
|
|
|
fn strip_dots(s: [path]) -> [path] {
|
|
vec::filter_map(s, { |elem|
|
|
if elem == "." {
|
|
option::none
|
|
} else {
|
|
option::some(elem)
|
|
}
|
|
})
|
|
}
|
|
|
|
fn rollup_doubledots(s: [path]) -> [path] {
|
|
if vec::is_empty(s) {
|
|
ret [];
|
|
}
|
|
|
|
let t = [];
|
|
let i = vec::len(s);
|
|
let skip = 0;
|
|
do {
|
|
i -= 1u;
|
|
if s[i] == ".." {
|
|
skip += 1;
|
|
} else {
|
|
if skip == 0 {
|
|
t += [s[i]];
|
|
} else {
|
|
skip -= 1;
|
|
}
|
|
}
|
|
} while i != 0u;
|
|
let t = vec::reversed(t);
|
|
while skip > 0 {
|
|
t += [".."];
|
|
skip -= 1;
|
|
}
|
|
ret t;
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
#[cfg(target_os = "macos")]
|
|
fn reabsolute(orig: path, new: path) -> path {
|
|
if path_is_absolute(orig) {
|
|
path_sep() + new
|
|
} else {
|
|
new
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "win32")]
|
|
fn reabsolute(orig: path, new: path) -> path {
|
|
if path_is_absolute(orig) && orig[0] == os_fs::path_sep as u8 {
|
|
str::from_char(os_fs::path_sep) + new
|
|
} else {
|
|
new
|
|
}
|
|
}
|
|
|
|
fn reterminate(orig: path, new: path) -> path {
|
|
let last = orig[str::byte_len(orig) - 1u];
|
|
if last == os_fs::path_sep as u8
|
|
|| last == os_fs::path_sep as u8 {
|
|
ret new + path_sep();
|
|
} else {
|
|
ret new;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Local Variables:
|
|
// mode: rust;
|
|
// fill-column: 78;
|
|
// indent-tabs-mode: nil
|
|
// c-basic-offset: 4
|
|
// buffer-file-coding-system: utf-8-unix
|
|
// End:
|