Use new io + path + fs libraries (LOTS OF CHANGES)

Exa now uses the new IO, Path, and Filesystem libraries that have been out for a while now.

Unfortunately, the new libraries don't *entirely* cover the range of the old libraries just yet: in particular, to become more cross-platform, the data in `UnstableFileStat` isn't available in the Unix `MetadataExt` yet. Much of this is contained in rust-lang/rfcs#1044 (which is due to be implemented in rust-lang/rust#14711), but it's not *entirely* there yet.

As such, this commits a serious loss of functionality: no symlink viewing, no hard links or blocks, or users or groups. Also, some of the code could now be optimised. I just wanted to commit this to sort out most of the 'teething problems' of having a different path system in advance.

Here's an example problem that took ages to fix for you, just because you read this far: when I first got exa to compile, it worked mostly fine, except calling `exa` by itself didn't list the current directory. I traced where the command-line options were being generated, to where files and directories were sorted, to where the threads were spawned... and the problem turned out to be that it was using the full path as the file name, rather than just the last component, and these paths happened to begin with `.`, so it thought they were dotfiles.
This commit is contained in:
Ben S 2015-04-23 13:00:34 +01:00
parent eee49ecefe
commit adbaa51cb9
10 changed files with 262 additions and 217 deletions

112
Cargo.lock generated
View File

@ -4,14 +4,14 @@ version = "0.2.0"
dependencies = [
"ansi_term 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"datetime 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"getopts 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
"datetime 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"getopts 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"git2 0.2.9 (git+https://github.com/alexcrichton/git2-rs.git)",
"locale 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"locale 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"natord 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"number_prefix 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"pad 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"number_prefix 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
"pad 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"users 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -27,13 +27,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "datetime"
version = "0.1.5"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"locale 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"pad 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)",
"regex_macros 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
"locale 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"num 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)",
"pad 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
"regex_macros 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -43,7 +44,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "getopts"
version = "0.2.9"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"log 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -52,12 +53,12 @@ dependencies = [
[[package]]
name = "git2"
version = "0.2.9"
source = "git+https://github.com/alexcrichton/git2-rs.git#f06a248ad1bfd57e3783867b7f54c200ebc8014a"
source = "git+https://github.com/alexcrichton/git2-rs.git#315a6f2e6f20babaa2f48416de9a14d15420cf1b"
dependencies = [
"bitflags 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"libgit2-sys 0.2.11 (git+https://github.com/alexcrichton/git2-rs.git)",
"url 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)",
"libgit2-sys 0.2.12 (git+https://github.com/alexcrichton/git2-rs.git)",
"url 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -67,22 +68,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libgit2-sys"
version = "0.2.11"
source = "git+https://github.com/alexcrichton/git2-rs.git#f06a248ad1bfd57e3783867b7f54c200ebc8014a"
version = "0.2.12"
source = "git+https://github.com/alexcrichton/git2-rs.git#315a6f2e6f20babaa2f48416de9a14d15420cf1b"
dependencies = [
"libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"libssh2-sys 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)",
"libz-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl-sys 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "libressl-pnacl-sys"
version = "2.1.4"
version = "2.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"pnacl-build-helper 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"pnacl-build-helper 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -92,7 +93,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"libz-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl-sys 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -106,8 +107,12 @@ dependencies = [
[[package]]
name = "locale"
version = "0.1.7"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"num 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "log"
@ -128,34 +133,48 @@ version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "num_cpus"
version = "0.1.0"
name = "num"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num_cpus"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"gcc 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "number_prefix"
version = "0.2.3"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "openssl-sys"
version = "0.6.0"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"gcc 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"libressl-pnacl-sys 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"libressl-pnacl-sys 2.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "pad"
version = "0.1.3"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-width 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "pkg-config"
@ -164,20 +183,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "pnacl-build-helper"
version = "1.3.2"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"tempdir 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex"
version = "0.1.27"
version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "regex_macros"
version = "0.1.15"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"regex 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -185,9 +216,22 @@ name = "rustc-serialize"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "tempdir"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "unicode-width"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "url"
version = "0.2.29"
version = "0.2.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -1,5 +1,4 @@
use std::iter::repeat;
use unicode::str::UnicodeStr;
use ansi_term::Style;

View File

@ -1,10 +1,10 @@
use std::old_io::{fs, IoResult};
use std::old_path::GenericPath;
use std::old_path::posix::Path;
use feature::Git;
use file::{File, GREY};
use std::io;
use std::fs;
use std::path::{Path, PathBuf};
/// A **Dir** provides a cached list of the file paths in a directory that's
/// being listed.
///
@ -12,8 +12,8 @@ use file::{File, GREY};
/// check the existence of surrounding files, then highlight themselves
/// accordingly. (See `File#get_source_files`)
pub struct Dir {
contents: Vec<Path>,
path: Path,
contents: Vec<PathBuf>,
path: PathBuf,
git: Option<Git>,
}
@ -22,10 +22,10 @@ impl Dir {
/// Create a new Dir object filled with all the files in the directory
/// pointed to by the given path. Fails if the directory can't be read, or
/// isn't actually a directory.
pub fn readdir(path: &Path) -> IoResult<Dir> {
fs::readdir(path).map(|paths| Dir {
contents: paths,
path: path.clone(),
pub fn readdir(path: &Path) -> io::Result<Dir> {
fs::read_dir(path).map(|dir_obj| Dir {
contents: dir_obj.map(|entry| entry.unwrap().path()).collect(),
path: path.to_path_buf(),
git: Git::scan(path).ok(),
})
}
@ -50,11 +50,11 @@ impl Dir {
/// Whether this directory contains a file with the given path.
pub fn contains(&self, path: &Path) -> bool {
self.contents.contains(path)
self.contents.iter().any(|ref p| p.as_path() == path)
}
/// Append a path onto the path specified by this directory.
pub fn join(&self, child: Path) -> Path {
pub fn join(&self, child: &Path) -> PathBuf {
self.path.join(child)
}

View File

@ -1,5 +1,4 @@
use std::old_path::GenericPath;
use std::old_path::posix::Path;
use std::path::{Path, PathBuf};
use ansi_term::{ANSIString, ANSIStrings};
use ansi_term::Colour::*;
@ -9,7 +8,7 @@ use file::GREY;
/// Container of Git statuses for all the files in this folder's Git repository.
pub struct Git {
statuses: Vec<(Path, git2::Status)>,
statuses: Vec<(PathBuf, git2::Status)>,
}
impl Git {
@ -17,30 +16,23 @@ impl Git {
/// Discover a Git repository on or above this directory, scanning it for
/// the files' statuses if one is found.
pub fn scan(path: &Path) -> Result<Git, git2::Error> {
use std::os::unix::ffi::OsStrExt;
use std::ffi::AsOsStr;
// TODO: libgit2-rs uses the new Path module, but exa still uses the
// old_path one, and will have to continue to do so until the new IO
// module gets a bit more developed. So we have to turn Paths into
// old_path::Paths. Yes, this is hacky, but hopefully temporary.
let new_path = path.as_os_str();
let repo = try!(git2::Repository::discover(new_path));
let repo = try!(git2::Repository::discover(path));
let workdir = match repo.workdir() {
Some(w) => Path::new(w.as_os_str().as_bytes()),
Some(w) => w,
None => return Ok(Git { statuses: vec![] }), // bare repo
};
let statuses = try!(repo.statuses(None)).iter()
.map(|e| (workdir.join(e.path_bytes()), e.status()))
.map(|e| (workdir.join(Path::new(e.path().unwrap())), e.status()))
.collect();
Ok(Git { statuses: statuses })
}
/// Get the status for the file at the given path, if present.
pub fn status(&self, path: &Path) -> String {
let status = self.statuses.iter()
.find(|p| &p.0 == path);
.find(|p| p.0.as_path() == path);
match status {
Some(&(_, s)) => ANSIStrings( &[Git::index_status(s), Git::working_tree_status(s) ]).to_string(),
None => GREY.paint("--").to_string(),
@ -52,7 +44,7 @@ impl Git {
/// directories, which don't really have an 'official' status.
pub fn dir_status(&self, dir: &Path) -> String {
let s = self.statuses.iter()
.filter(|p| dir.is_ancestor_of(&p.0))
.filter(|p| p.0.starts_with(dir))
.fold(git2::Status::empty(), |a, b| a | b.1);
ANSIStrings( &[Git::index_status(s), Git::working_tree_status(s)] ).to_string()
@ -83,3 +75,4 @@ impl Git {
}
}
}

View File

@ -1,14 +1,13 @@
//! Extended attribute support for darwin
extern crate libc;
use std::ffi::CString;
use std::io;
use std::path::Path;
use std::ptr;
use std::mem;
use std::old_io as io;
use std::old_path::GenericPath;
use std::old_path::posix::Path;
use self::libc::{c_int, size_t, ssize_t, c_char, c_void, uint32_t};
/// Don't follow symbolic links
const XATTR_NOFOLLOW: c_int = 0x0001;
/// Expose HFS Compression extended attributes
@ -41,15 +40,21 @@ pub struct Attribute {
impl Attribute {
/// Lists the extended attribute of `path`.
/// Does follow symlinks by default.
pub fn list_attrs(path: &Path, flags: &[ListFlags]) -> io::IoResult<Vec<Attribute>> {
pub fn list_attrs(path: &Path, flags: &[ListFlags]) -> io::Result<Vec<Attribute>> {
let mut c_flags: c_int = 0;
for &flag in flags.iter() {
c_flags |= flag as c_int
}
let c_path = try!(CString::new(path.as_vec()));
let c_path = match path.as_os_str().to_cstring() {
Some(cstring) => cstring,
None => return Err(io::Error::new(io::ErrorKind::Other, "could not read extended attributes")),
};
let bufsize = unsafe {
listxattr(c_path.as_ptr(), ptr::null_mut(), 0, c_flags)
};
if bufsize > 0 {
let mut buf = vec![0u8; bufsize as usize];
let err = unsafe { listxattr(
@ -88,18 +93,10 @@ impl Attribute {
}
Ok(names)
} else {
Err(io::IoError {
kind: io::OtherIoError,
desc: "could not read extended attributes",
detail: None
})
Err(io::Error::new(io::ErrorKind::Other, "could not read extended attributes"))
}
} else {
Err(io::IoError {
kind: io::OtherIoError,
desc: "could not read extended attributes",
detail: None
})
Err(io::Error::new(io::ErrorKind::Other, "could not read extended attributes"))
}
}
@ -115,12 +112,12 @@ impl Attribute {
/// Lists the extended attributes.
/// Follows symlinks like `stat`
pub fn list(path: &Path) -> io::IoResult<Vec<Attribute>> {
pub fn list(path: &Path) -> io::Result<Vec<Attribute>> {
Attribute::list_attrs(path, &[])
}
/// Lists the extended attributes.
/// Does not follow symlinks like `lstat`
pub fn llist(path: &Path) -> io::IoResult<Vec<Attribute>> {
pub fn llist(path: &Path) -> io::Result<Vec<Attribute>> {
Attribute::list_attrs(path, &[ListFlags::NoFollow])
}

View File

@ -2,10 +2,9 @@
extern crate libc;
use std::ffi::CString;
use std::io;
use std::path::Path;
use std::ptr;
use std::old_io as io;
use std::old_path::GenericPath;
use std::old_path::posix::Path;
use self::libc::{size_t, ssize_t, c_char, c_void};
extern "C" {
@ -36,15 +35,21 @@ pub struct Attribute {
impl Attribute {
/// Lists the extended attribute of `path`.
/// Does follow symlinks by default.
pub fn list_attrs(path: &Path, do_follow: FollowSymlinks) -> io::IoResult<Vec<Attribute>> {
pub fn list_attrs(path: &Path, do_follow: FollowSymlinks) -> io::Result<Vec<Attribute>> {
let (listxattr, getxattr) = match do_follow {
FollowSymlinks::Yes => (listxattr, getxattr),
FollowSymlinks::No => (llistxattr, lgetxattr),
};
let c_path = try!(CString::new(path.as_vec()));
let c_path = match path.as_os_str().to_cstring() {
Some(cstring) => cstring,
None => return Err(io::Error::new(io::ErrorKind::Other, "could not read extended attributes")),
};
let bufsize = unsafe {
listxattr(c_path.as_ptr(), ptr::null_mut(), 0)
};
if bufsize > 0 {
let mut buf = vec![0u8; bufsize as usize];
let err = unsafe { listxattr(
@ -79,18 +84,10 @@ impl Attribute {
}
Ok(names)
} else {
Err(io::IoError {
kind: io::OtherIoError,
desc: "could not read extended attributes",
detail: None
})
Err(io::Error::new(io::ErrorKind::Other, "could not read extended attributes"))
}
} else {
Err(io::IoError {
kind: io::OtherIoError,
desc: "could not read extended attributes",
detail: None
})
Err(io::Error::new(io::ErrorKind::Other, "could not read extended attributes"))
}
}
@ -106,12 +103,12 @@ impl Attribute {
/// Lists the extended attributes.
/// Follows symlinks like `stat`
pub fn list(path: &Path) -> io::IoResult<Vec<Attribute>> {
pub fn list(path: &Path) -> io::Result<Vec<Attribute>> {
Attribute::list_attrs(path, FollowSymlinks::Yes)
}
/// Lists the extended attributes.
/// Does not follow symlinks like `lstat`
pub fn llist(path: &Path) -> io::IoResult<Vec<Attribute>> {
pub fn llist(path: &Path) -> io::Result<Vec<Attribute>> {
Attribute::list_attrs(path, FollowSymlinks::No)
}

View File

@ -4,13 +4,12 @@
// There's a tracking issue for it:
// https://github.com/rust-lang/rfcs/issues/939
use std::old_io::{fs, IoResult};
use std::old_io as io;
use std::old_path::GenericPath;
use std::old_path::posix::Path;
use std::ascii::AsciiExt;
use std::env::current_dir;
use unicode::str::UnicodeStr;
use std::fs;
use std::io;
use std::os::unix::fs::PermissionsExt;
use std::path::{Component, Path, PathBuf};
use ansi_term::{ANSIString, ANSIStrings, Colour, Style};
use ansi_term::Style::Plain;
@ -48,8 +47,8 @@ pub struct File<'a> {
pub name: String,
pub dir: Option<&'a Dir>,
pub ext: Option<String>,
pub path: Path,
pub stat: io::FileStat,
pub path: PathBuf,
pub stat: fs::Metadata,
pub xattrs: Vec<Attribute>,
pub this: Option<Dir>,
}
@ -59,18 +58,18 @@ impl<'a> File<'a> {
/// appropriate. Paths specified directly on the command-line have no Dirs.
///
/// This uses lstat instead of stat, which doesn't follow symbolic links.
pub fn from_path(path: &Path, parent: Option<&'a Dir>, recurse: bool) -> IoResult<File<'a>> {
fs::lstat(path).map(|stat| File::with_stat(stat, path, parent, recurse))
pub fn from_path(path: &Path, parent: Option<&'a Dir>, recurse: bool) -> io::Result<File<'a>> {
fs::metadata(path).map(|stat| File::with_stat(stat, path, parent, recurse)) // todo: lstat
}
/// Create a new File object from the given Stat result, and other data.
pub fn with_stat(stat: io::FileStat, path: &Path, parent: Option<&'a Dir>, recurse: bool) -> File<'a> {
pub fn with_stat(stat: fs::Metadata, path: &Path, parent: Option<&'a Dir>, recurse: bool) -> File<'a> {
let filename = path_filename(path);
// If we are recursing, then the `this` field contains a Dir object
// that represents the current File as a directory, if it is a
// directory. This is used for the --tree option.
let this = if recurse && stat.kind == io::FileType::Directory {
let this = if recurse && stat.is_dir() {
Dir::readdir(path).ok()
}
else {
@ -78,7 +77,7 @@ impl<'a> File<'a> {
};
File {
path: path.clone(),
path: path.to_path_buf(),
dir: parent,
stat: stat,
ext: ext(&filename),
@ -88,6 +87,22 @@ impl<'a> File<'a> {
}
}
pub fn is_directory(&self) -> bool {
self.stat.is_dir()
}
pub fn is_file(&self) -> bool {
self.stat.is_file()
}
pub fn is_link(&self) -> bool {
false
}
pub fn is_pipe(&self) -> bool {
false
}
/// Whether this file is a dotfile or not.
pub fn is_dotfile(&self) -> bool {
self.name.starts_with(".")
@ -99,11 +114,6 @@ impl<'a> File<'a> {
name.ends_with("~") || (name.starts_with("#") && name.ends_with("#"))
}
/// Whether this file is a directory or not.
pub fn is_directory(&self) -> bool {
self.stat.kind == io::FileType::Directory
}
/// Get the data for a column, formatted as a coloured string.
pub fn display<U: Users>(&self, column: &Column, users_cache: &mut U, locale: &UserLocale) -> Cell {
match *column {
@ -125,7 +135,7 @@ impl<'a> File<'a> {
/// It consists of the file name coloured in the appropriate style,
/// with special formatting for a symlink.
pub fn file_name_view(&self) -> String {
if self.stat.kind == io::FileType::Symlink {
if self.is_link() {
self.symlink_file_name_view()
}
else {
@ -145,9 +155,9 @@ impl<'a> File<'a> {
let name = &*self.name;
let style = self.file_colour();
if let Ok(path) = fs::readlink(&self.path) {
if let Ok(path) = fs::read_link(&self.path) {
let target_path = match self.dir {
Some(dir) => dir.join(path),
Some(dir) => dir.join(&*path),
None => path,
};
@ -167,14 +177,13 @@ impl<'a> File<'a> {
path_prefix.push_str("/");
}
let path_bytes: Vec<&[u8]> = file.path.components().collect();
let path_bytes: Vec<Component> = file.path.components().collect();
if !path_bytes.is_empty() {
// Use init() to add all but the last component of the
// path to the prefix. init() panics when given an
// empty list, hence the check.
for component in path_bytes.init().iter() {
let string = String::from_utf8_lossy(component).to_string();
path_prefix.push_str(&string);
path_prefix.push_str(&*component.as_os_str().to_string_lossy());
path_prefix.push_str("/");
}
}
@ -220,9 +229,9 @@ impl<'a> File<'a> {
let filename = path_filename(target_path);
// Use stat instead of lstat - we *want* to follow links.
if let Ok(stat) = fs::stat(target_path) {
if let Ok(stat) = fs::metadata(target_path) {
Ok(File {
path: target_path.clone(),
path: target_path.to_path_buf(),
dir: self.dir,
stat: stat,
ext: ext(&filename),
@ -239,7 +248,7 @@ impl<'a> File<'a> {
/// This file's number of hard links as a coloured string.
fn hard_links(&self, locale: &locale::Numeric) -> Cell {
let style = if self.has_multiple_links() { Red.on(Yellow) } else { Red.normal() };
Cell::paint(style, &locale.format_int(self.stat.unstable.nlink as isize)[..])
Cell::paint(style, &locale.format_int(0 /*self.stat.unstable.nlink*/ as isize)[..])
}
/// Whether this is a regular file with more than one link.
@ -248,18 +257,19 @@ impl<'a> File<'a> {
/// while you can come across directories and other types with multiple
/// links much more often.
fn has_multiple_links(&self) -> bool {
self.stat.kind == io::FileType::RegularFile && self.stat.unstable.nlink > 1
self.is_file() && (0 /*self.stat.unstable.nlink*/) > 1
}
/// This file's inode as a coloured string.
fn inode(&self) -> Cell {
Cell::paint(Purple.normal(), &*self.stat.unstable.inode.to_string())
let inode = 0i32; /* self.stat.unstable.inode */
Cell::paint(Purple.normal(), &inode.to_string()[..])
}
/// This file's number of filesystem blocks (if available) as a coloured string.
fn blocks(&self, locale: &locale::Numeric) -> Cell {
if self.stat.kind == io::FileType::RegularFile || self.stat.kind == io::FileType::Symlink {
Cell::paint(Cyan.normal(), &locale.format_int(self.stat.unstable.blocks as isize)[..])
if self.is_file() || self.is_link() {
Cell::paint(Cyan.normal(), &locale.format_int(0 /*self.stat.unstable.blocks*/)[..])
}
else {
Cell { text: GREY.paint("-").to_string(), length: 1 }
@ -272,11 +282,11 @@ impl<'a> File<'a> {
/// instead. This usually happens when a user is deleted, but still owns
/// files.
fn user<U: Users>(&self, users_cache: &mut U) -> Cell {
let uid = self.stat.unstable.uid as i32;
let uid = 0; // self.stat.unstable.uid as u32
let user_name = match users_cache.get_user_by_uid(uid) {
Some(user) => user.name,
None => self.stat.unstable.uid.to_string(),
None => uid.to_string(),
};
let style = if users_cache.get_current_uid() == uid { Yellow.bold() } else { Plain };
@ -287,10 +297,10 @@ impl<'a> File<'a> {
///
/// As above, if not present, it formats the gid as a number instead.
fn group<U: Users>(&self, users_cache: &mut U) -> Cell {
let gid = self.stat.unstable.gid as u32;
let gid = 0; // self.stat.unstable.gid as u32;
let mut style = Plain;
let group_name = match users_cache.get_group_by_gid(gid) {
let group_name = match users_cache.get_group_by_gid(gid as u32) {
Some(group) => {
let current_uid = users_cache.get_current_uid();
if let Some(current_user) = users_cache.get_user_by_uid(current_uid) {
@ -300,7 +310,7 @@ impl<'a> File<'a> {
}
group.name
},
None => self.stat.unstable.gid.to_string(),
None => gid.to_string(),
};
Cell::paint(style, &*group_name)
@ -318,9 +328,9 @@ impl<'a> File<'a> {
}
else {
let result = match size_format {
SizeFormat::DecimalBytes => decimal_prefix(self.stat.size as f64),
SizeFormat::BinaryBytes => binary_prefix(self.stat.size as f64),
SizeFormat::JustBytes => return Cell::paint(Green.bold(), &locale.format_int(self.stat.size as isize)[..]),
SizeFormat::DecimalBytes => decimal_prefix(self.stat.len() as f64),
SizeFormat::BinaryBytes => binary_prefix(self.stat.len() as f64),
SizeFormat::JustBytes => return Cell::paint(Green.bold(), &locale.format_int(self.stat.len())[..]),
};
match result {
@ -342,9 +352,9 @@ impl<'a> File<'a> {
// Need to convert these values from milliseconds into seconds.
let time_in_seconds = match time_type {
TimeType::FileAccessed => self.stat.accessed,
TimeType::FileModified => self.stat.modified,
TimeType::FileCreated => self.stat.created,
TimeType::FileAccessed => self.stat.accessed(),
TimeType::FileModified => self.stat.modified(),
TimeType::FileCreated => 0 // self.stat.created(),
} as i64 / 1000;
let date = LocalDateTime::at(time_in_seconds);
@ -364,19 +374,26 @@ impl<'a> File<'a> {
/// Although the file type can usually be guessed from the colour of the
/// file, `ls` puts this character there, so people will expect it.
fn type_char(&self) -> ANSIString {
return match self.stat.kind {
io::FileType::RegularFile => Plain.paint("."),
io::FileType::Directory => Blue.paint("d"),
io::FileType::NamedPipe => Yellow.paint("|"),
io::FileType::BlockSpecial => Purple.paint("s"),
io::FileType::Symlink => Cyan.paint("l"),
io::FileType::Unknown => Plain.paint("?"),
if self.is_file() {
Plain.paint(".")
}
else if self.is_directory() {
Blue.paint("d")
}
else if self.is_pipe() {
Yellow.paint("|")
}
else if self.is_link() {
Cyan.paint("l")
}
else {
Purple.paint("?")
}
}
/// Marker indicating that the file contains extended attributes
///
/// Returns “@” or “ ” depending on wheter the file contains an extented
/// Returns "@" or " ” depending on wheter the file contains an extented
/// attribute or not. Also returns “ ” in case the attributes cannot be read
/// for some reason.
fn attribute_marker(&self) -> ANSIString {
@ -389,23 +406,22 @@ impl<'a> File<'a> {
/// bits are bold because they're the ones used most often, and executable
/// files are underlined to make them stand out more.
fn permissions_string(&self) -> Cell {
let bits = self.stat.perm;
let executable_colour = match self.stat.kind {
io::FileType::RegularFile => Green.bold().underline(),
_ => Green.bold(),
};
let bits = self.stat.permissions().mode();
let executable_colour = if self.is_file() { Green.bold().underline() }
else { Green.bold() };
let string = ANSIStrings(&[
self.type_char(),
File::permission_bit(&bits, io::USER_READ, "r", Yellow.bold()),
File::permission_bit(&bits, io::USER_WRITE, "w", Red.bold()),
File::permission_bit(&bits, io::USER_EXECUTE, "x", executable_colour),
File::permission_bit(&bits, io::GROUP_READ, "r", Yellow.normal()),
File::permission_bit(&bits, io::GROUP_WRITE, "w", Red.normal()),
File::permission_bit(&bits, io::GROUP_EXECUTE, "x", Green.normal()),
File::permission_bit(&bits, io::OTHER_READ, "r", Yellow.normal()),
File::permission_bit(&bits, io::OTHER_WRITE, "w", Red.normal()),
File::permission_bit(&bits, io::OTHER_EXECUTE, "x", Green.normal()),
File::permission_bit(bits, Permission::UserRead, "r", Yellow.bold()),
File::permission_bit(bits, Permission::UserWrite, "w", Red.bold()),
File::permission_bit(bits, Permission::UserExecute, "x", executable_colour),
File::permission_bit(bits, Permission::GroupRead, "r", Yellow.normal()),
File::permission_bit(bits, Permission::GroupWrite, "w", Red.normal()),
File::permission_bit(bits, Permission::GroupExecute, "x", Green.normal()),
File::permission_bit(bits, Permission::OtherRead, "r", Yellow.normal()),
File::permission_bit(bits, Permission::OtherWrite, "w", Red.normal()),
File::permission_bit(bits, Permission::OtherExecute, "x", Green.normal()),
self.attribute_marker()
]).to_string();
@ -413,8 +429,9 @@ impl<'a> File<'a> {
}
/// Helper method for the permissions string.
fn permission_bit(bits: &io::FilePermission, bit: io::FilePermission, character: &'static str, style: Style) -> ANSIString<'static> {
if bits.contains(bit) {
fn permission_bit(bits: i32, bit: Permission, character: &'static str, style: Style) -> ANSIString<'static> {
let bi32 = bit as i32;
if bits & bi32 == bi32 {
style.paint(character)
}
else {
@ -430,7 +447,7 @@ impl<'a> File<'a> {
/// dangerous to highlight *all* compiled, so the paths in this vector
/// are checked for existence first: for example, `foo.js` is perfectly
/// valid without `foo.coffee`.
pub fn get_source_files(&self) -> Vec<Path> {
pub fn get_source_files(&self) -> Vec<PathBuf> {
if let Some(ref ext) = self.ext {
match &ext[..] {
"class" => vec![self.path.with_extension("java")], // Java
@ -458,15 +475,12 @@ impl<'a> File<'a> {
}
fn git_status(&self) -> Cell {
use std::os::unix::ffi::OsStrExt;
use std::ffi::AsOsStr;
let status = match self.dir {
None => GREY.paint("--").to_string(),
Some(d) => {
let cwd = match current_dir() {
Err(_) => Path::new(".").join(&self.path),
Ok(dir) => Path::new(dir.as_os_str().as_bytes()).join(&self.path),
Ok(dir) => dir.join(&self.path),
};
d.git_status(&cwd, self.is_directory())
@ -484,12 +498,10 @@ impl<'a> File<'a> {
/// the path has no components for `.`, `..`, and `/`, so in these
/// cases, the entire path is used.
fn path_filename(path: &Path) -> String {
let bytes = match path.components().last() {
Some(b) => b,
None => path.as_vec(),
};
String::from_utf8_lossy(bytes).to_string()
match path.iter().last() {
Some(os_str) => os_str.to_string_lossy().to_string(),
None => ".".to_string(), // can this even be reached?
}
}
/// Extract an extension from a string, if one is present, in lowercase.
@ -504,10 +516,21 @@ fn ext<'a>(name: &'a str) -> Option<String> {
name.rfind('.').map(|p| name[p+1..].to_ascii_lowercase())
}
enum Permission {
UserRead = 0o400,
UserWrite = 0o200,
UserExecute = 0o100,
GroupRead = 0o040,
GroupWrite = 0o020,
GroupExecute = 0o010,
OtherRead = 0o004,
OtherWrite = 0o002,
OtherExecute = 0o001,
}
#[cfg(test)]
pub mod test {
pub use super::*;
use super::path_filename;
pub use column::{Cell, Column};
pub use std::old_io as io;
@ -521,18 +544,6 @@ pub mod test {
pub use ansi_term::Style::Plain;
pub use ansi_term::Colour::Yellow;
#[test]
fn current_filename() {
let filename = path_filename(&Path::new("."));
assert_eq!(&filename[..], ".")
}
#[test]
fn parent_filename() {
let filename = path_filename(&Path::new(".."));
assert_eq!(&filename[..], "..")
}
#[test]
fn extension() {
assert_eq!(Some("dat".to_string()), super::ext("fester.dat"))

View File

@ -1,8 +1,6 @@
use file::{File, GREY};
use self::FileType::*;
use std::old_io as io;
use ansi_term::Style;
use ansi_term::Style::Plain;
use ansi_term::Colour::{Red, Green, Yellow, Blue, Cyan, Fixed};
@ -84,13 +82,14 @@ pub trait HasType {
impl<'a> HasType for File<'a> {
fn get_type(&self) -> FileType {
match self.stat.kind {
io::FileType::Directory => return Directory,
io::FileType::Symlink => return Symlink,
io::FileType::BlockSpecial => return Special,
io::FileType::NamedPipe => return Special,
io::FileType::Unknown => return Special,
_ => {}
if self.is_directory() {
return Directory;
}
else if self.is_link() {
return Symlink;
}
else if !self.is_file() {
return Special;
}
if self.name.starts_with("README") || BUILD_TYPES.contains(&&self.name[..]) {

View File

@ -1,4 +1,4 @@
#![feature(collections, core, exit_status, io, libc, old_fs, old_io, old_path, os, std_misc, unicode)]
#![feature(convert, core, exit_status, fs_ext, fs_time, io, libc, os, scoped, std_misc, unicode)]
#![allow(deprecated)]
// Other platforms than macos don't need std_misc but you can't
@ -13,16 +13,14 @@ extern crate natord;
extern crate num_cpus;
extern crate number_prefix;
extern crate pad;
extern crate unicode;
extern crate users;
#[cfg(feature="git")]
extern crate git2;
use std::env;
use std::old_io::{fs, FileType};
use std::old_path::GenericPath;
use std::old_path::posix::Path;
use std::fs;
use std::path::{Component, Path, PathBuf};
use std::sync::mpsc::{channel, sync_channel};
use std::thread;
@ -44,7 +42,7 @@ pub mod term;
struct Exa<'a> {
count: usize,
options: Options,
dirs: Vec<Path>,
dirs: Vec<PathBuf>,
files: Vec<File<'a>>,
}
@ -64,7 +62,7 @@ impl<'a> Exa<'a> {
// Files are shown first, and then each directory is expanded
// and listed second.
let is_tree = self.options.dir_action.is_tree();
let is_tree = self.options.dir_action.is_tree() || self.options.dir_action.is_as_file();
let total_files = files.len();
// Denotes the maxinum number of concurrent threads
@ -73,9 +71,10 @@ impl<'a> Exa<'a> {
// Communication between consumer thread and producer threads
enum StatResult<'a> {
File(File<'a>),
Path(Path),
Path(PathBuf),
Error
}
let (results_tx, results_rx) = channel();
// Spawn consumer thread
@ -92,7 +91,7 @@ impl<'a> Exa<'a> {
StatResult::Path(path) => self.dirs.push(path),
StatResult::Error => ()
},
Err(_) => unreachable!()
Err(_) => unreachable!(),
}
self.count += 1;
}
@ -107,17 +106,17 @@ impl<'a> Exa<'a> {
// Spawn producer thread
thread::spawn(move || {
let path = Path::new(file.clone());
let _ = results_tx.send(match fs::stat(&path) {
let path = Path::new(&*file);
let _ = results_tx.send(match fs::metadata(&path) {
Ok(stat) => {
if stat.kind != FileType::Directory {
if !stat.is_dir() {
StatResult::File(File::with_stat(stat, &path, None, false))
}
else if is_tree {
StatResult::File(File::with_stat(stat, &path, None, true))
}
else {
StatResult::Path(path)
StatResult::Path(path.to_path_buf())
}
}
Err(e) => {
@ -127,7 +126,6 @@ impl<'a> Exa<'a> {
});
});
}
}
fn print_files(&self) {
@ -166,9 +164,9 @@ impl<'a> Exa<'a> {
// time, so by inserting them backwards, they get displayed in
// the correct sort order.
if let Some(recurse_opts) = self.options.dir_action.recurse_options() {
let depth = dir_path.components().filter(|&c| c != b".").count() + 1;
let depth = dir_path.components().filter(|&c| c != Component::CurDir).count() + 1;
if !recurse_opts.tree && !recurse_opts.is_too_deep(depth) {
for dir in files.iter().filter(|f| f.stat.kind == FileType::Directory).rev() {
for dir in files.iter().filter(|f| f.is_directory()).rev() {
self.dirs.push(dir.path.clone());
}
}

View File

@ -137,15 +137,15 @@ impl FileFilter {
match self.sort_field {
SortField::Unsorted => {},
SortField::Name => files.sort_by(|a, b| natord::compare(&*a.name, &*b.name)),
SortField::Size => files.sort_by(|a, b| a.stat.size.cmp(&b.stat.size)),
SortField::FileInode => files.sort_by(|a, b| a.stat.unstable.inode.cmp(&b.stat.unstable.inode)),
SortField::Size => files.sort_by(|a, b| a.stat.len().cmp(&b.stat.len())),
SortField::FileInode => {}, // files.sort_by(|a, b| a.stat.unstable.inode.cmp(&b.stat.unstable.inode)),
SortField::Extension => files.sort_by(|a, b| match a.ext.cmp(&b.ext) {
Ordering::Equal => natord::compare(&*a.name, &*b.name),
order => order
}),
SortField::ModifiedDate => files.sort_by(|a, b| a.stat.modified.cmp(&b.stat.modified)),
SortField::AccessedDate => files.sort_by(|a, b| a.stat.accessed.cmp(&b.stat.accessed)),
SortField::CreatedDate => files.sort_by(|a, b| a.stat.created.cmp(&b.stat.created)),
SortField::ModifiedDate => files.sort_by(|a, b| a.stat.modified().cmp(&b.stat.modified())),
SortField::AccessedDate => files.sort_by(|a, b| a.stat.accessed().cmp(&b.stat.accessed())),
SortField::CreatedDate => {}, // files.sort_by(|a, b| a.stat.created().cmp(&b.stat.created())),
}
if self.reverse {
@ -443,6 +443,13 @@ impl DirAction {
}
}
pub fn is_as_file(&self) -> bool {
match *self {
DirAction::AsFile => true,
_ => false,
}
}
pub fn is_tree(&self) -> bool {
match *self {
DirAction::Recurse(RecurseOptions { max_depth: _, tree }) => tree,