mirror of
https://github.com/Llewellynvdm/exa.git
synced 2024-11-22 20:15:11 +00:00
Merge branch 'child-nodes'
This commit is contained in:
commit
eee49ece04
@ -1,3 +1,8 @@
|
||||
before_install:
|
||||
- sudo add-apt-repository --yes ppa:kubuntu-ppa/backports
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install cmake
|
||||
sudo: true
|
||||
language: rust
|
||||
rust: nightly
|
||||
|
||||
|
146
Cargo.lock
generated
146
Cargo.lock
generated
@ -5,18 +5,27 @@ dependencies = [
|
||||
"ansi_term 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"datetime 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"getopts 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"git2 0.2.13 (git+https://github.com/alexcrichton/git2-rs.git)",
|
||||
"libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"getopts 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"git2 0.3.0 (git+https://github.com/alexcrichton/git2-rs.git)",
|
||||
"libc 0.1.10 (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)",
|
||||
"natord 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num_cpus 0.2.6 (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)",
|
||||
"term_grid 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"threadpool 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-width 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"users 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-width 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"users 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "advapi32-sys"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -24,7 +33,7 @@ name = "aho-corasick"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"memchr 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -42,13 +51,21 @@ name = "byteorder"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"gcc 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "datetime"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"locale 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num 0.1.27 (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.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex_macros 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -57,12 +74,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "gcc"
|
||||
version = "0.3.9"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"advapi32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getopts"
|
||||
version = "0.2.11"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"log 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -70,28 +91,29 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "git2"
|
||||
version = "0.2.13"
|
||||
source = "git+https://github.com/alexcrichton/git2-rs.git#889cf3dd62bcf8406d7c5381699467cdcb79d55e"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/alexcrichton/git2-rs.git#cbe8e1a65ac9b16bc05137f80673e74c4d36f6e5"
|
||||
dependencies = [
|
||||
"bitflags 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libgit2-sys 0.2.18 (git+https://github.com/alexcrichton/git2-rs.git)",
|
||||
"url 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libgit2-sys 0.3.2 (git+https://github.com/alexcrichton/git2-rs.git)",
|
||||
"url 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.1.8"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "libgit2-sys"
|
||||
version = "0.2.18"
|
||||
source = "git+https://github.com/alexcrichton/git2-rs.git#889cf3dd62bcf8406d7c5381699467cdcb79d55e"
|
||||
version = "0.3.2"
|
||||
source = "git+https://github.com/alexcrichton/git2-rs.git#cbe8e1a65ac9b16bc05137f80673e74c4d36f6e5"
|
||||
dependencies = [
|
||||
"libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libssh2-sys 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libz-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cmake 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libssh2-sys 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libz-sys 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"openssl-sys 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pkg-config 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@ -101,26 +123,28 @@ name = "libressl-pnacl-sys"
|
||||
version = "2.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"pnacl-build-helper 1.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pnacl-build-helper 1.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libssh2-sys"
|
||||
version = "0.1.26"
|
||||
version = "0.1.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libz-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cmake 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libz-sys 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"openssl-sys 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pkg-config 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libz-sys"
|
||||
version = "0.1.6"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gcc 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pkg-config 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@ -129,8 +153,8 @@ name = "locale"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -138,7 +162,7 @@ name = "log"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -148,24 +172,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "0.1.3"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "natord"
|
||||
version = "1.0.8"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "num"
|
||||
version = "0.1.25"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -173,7 +197,7 @@ name = "num_cpus"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -181,7 +205,7 @@ name = "number_prefix"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"num 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -189,8 +213,8 @@ name = "openssl-sys"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"gcc 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gcc 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libressl-pnacl-sys 2.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pkg-config 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@ -200,7 +224,7 @@ name = "pad"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-width 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-width 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -210,7 +234,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "pnacl-build-helper"
|
||||
version = "1.4.5"
|
||||
version = "1.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"tempdir 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -218,10 +242,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.3.8"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"advapi32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -230,13 +256,13 @@ version = "0.1.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"aho-corasick 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
@ -249,7 +275,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustc-serialize"
|
||||
version = "0.3.15"
|
||||
version = "0.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
@ -257,7 +283,7 @@ name = "tempdir"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -265,7 +291,7 @@ name = "term_grid"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-width 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-width 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -283,23 +309,33 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "0.2.36"
|
||||
version = "0.2.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "users"
|
||||
version = "0.4.2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-build"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
|
46
src/dir.rs
46
src/dir.rs
@ -1,6 +1,7 @@
|
||||
use std::io;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::slice::Iter as SliceIter;
|
||||
|
||||
use feature::Git;
|
||||
use file::{File, fields};
|
||||
@ -22,31 +23,26 @@ 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.
|
||||
/// isn't actually a directory, or if there's an IO error that occurs
|
||||
/// while scanning.
|
||||
pub fn readdir(path: &Path, git: bool) -> io::Result<Dir> {
|
||||
fs::read_dir(path).map(|dir_obj| Dir {
|
||||
contents: dir_obj.map(|entry| entry.unwrap().path()).collect(),
|
||||
let reader = try!(fs::read_dir(path));
|
||||
let contents = try!(reader.map(|e| e.map(|e| e.path())).collect());
|
||||
|
||||
Ok(Dir {
|
||||
contents: contents,
|
||||
path: path.to_path_buf(),
|
||||
git: if git { Git::scan(path).ok() } else { None },
|
||||
})
|
||||
}
|
||||
|
||||
/// Produce a vector of File objects from an initialised directory,
|
||||
/// printing out an error if any of the Files fail to be created.
|
||||
///
|
||||
/// Passing in `recurse` means that any directories will be scanned for
|
||||
/// their contents, as well.
|
||||
pub fn files(&self, recurse: bool) -> Vec<File> {
|
||||
let mut files = vec![];
|
||||
|
||||
for path in self.contents.iter() {
|
||||
match File::from_path(path, Some(self), recurse) {
|
||||
Ok(file) => files.push(file),
|
||||
Err(e) => println!("{}: {}", path.display(), e),
|
||||
}
|
||||
/// Produce an iterator of IO results of trying to read all the files in
|
||||
/// this directory.
|
||||
pub fn files<'dir>(&'dir self) -> Files<'dir> {
|
||||
Files {
|
||||
inner: self.contents.iter(),
|
||||
dir: &self,
|
||||
}
|
||||
|
||||
files
|
||||
}
|
||||
|
||||
/// Whether this directory contains a file with the given path.
|
||||
@ -73,3 +69,17 @@ impl Dir {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct Files<'dir> {
|
||||
inner: SliceIter<'dir, PathBuf>,
|
||||
dir: &'dir Dir,
|
||||
}
|
||||
|
||||
impl<'dir> Iterator for Files<'dir> {
|
||||
type Item = Result<File<'dir>, (PathBuf, io::Error)>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next().map(|path| File::from_path(path, Some(self.dir)).map_err(|t| (path.clone(), t)))
|
||||
}
|
||||
}
|
@ -1,13 +1,5 @@
|
||||
// Extended attribute support
|
||||
|
||||
#[cfg(target_os = "macos")] mod xattr_darwin;
|
||||
#[cfg(target_os = "macos")] pub use self::xattr_darwin::Attribute;
|
||||
|
||||
#[cfg(target_os = "linux")] mod xattr_linux;
|
||||
#[cfg(target_os = "linux")] pub use self::xattr_linux::Attribute;
|
||||
|
||||
#[cfg(not(any(target_os = "macos", target_os = "linux")))] mod xattr_dummy;
|
||||
#[cfg(not(any(target_os = "macos", target_os = "linux")))] pub use self::xattr_dummy::Attribute;
|
||||
pub mod xattr;
|
||||
|
||||
// Git support
|
||||
|
||||
|
238
src/feature/xattr.rs
Normal file
238
src/feature/xattr.rs
Normal file
@ -0,0 +1,238 @@
|
||||
//! Extended attribute support for Darwin and Linux systems.
|
||||
extern crate libc;
|
||||
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
|
||||
pub const ENABLED: bool = cfg!(feature="git") && cfg!(any(target_os="macos", target_os="linux"));
|
||||
|
||||
pub trait FileAttributes {
|
||||
fn attributes(&self) -> io::Result<Vec<Attribute>>;
|
||||
fn symlink_attributes(&self) -> io::Result<Vec<Attribute>>;
|
||||
}
|
||||
|
||||
impl FileAttributes for Path {
|
||||
fn attributes(&self) -> io::Result<Vec<Attribute>> {
|
||||
list_attrs(lister::Lister::new(FollowSymlinks::Yes), &self)
|
||||
}
|
||||
|
||||
fn symlink_attributes(&self) -> io::Result<Vec<Attribute>> {
|
||||
list_attrs(lister::Lister::new(FollowSymlinks::No), &self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Attributes which can be passed to `Attribute::list_with_flags`
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum FollowSymlinks {
|
||||
Yes,
|
||||
No
|
||||
}
|
||||
|
||||
/// Extended attribute
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Attribute {
|
||||
pub name: String,
|
||||
pub size: usize,
|
||||
}
|
||||
|
||||
pub fn list_attrs(lister: lister::Lister, path: &Path) -> io::Result<Vec<Attribute>> {
|
||||
let c_path = match path.as_os_str().to_cstring() {
|
||||
Some(cstring) => cstring,
|
||||
None => return Err(io::Error::new(io::ErrorKind::Other, "Error: path somehow contained a NUL?")),
|
||||
};
|
||||
|
||||
let mut names = Vec::new();
|
||||
let bufsize = lister.listxattr_first(&c_path);
|
||||
|
||||
if bufsize < 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
else if bufsize > 0 {
|
||||
let mut buf = vec![0u8; bufsize as usize];
|
||||
let err = lister.listxattr_second(&c_path, &mut buf, bufsize);
|
||||
|
||||
if err < 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
if err > 0 {
|
||||
// End indicies of the attribute names
|
||||
// the buffer contains 0-terminates c-strings
|
||||
let idx = buf.iter().enumerate().filter_map(|(i, v)|
|
||||
if *v == 0 { Some(i) } else { None }
|
||||
);
|
||||
let mut start = 0;
|
||||
|
||||
for end in idx {
|
||||
let c_end = end + 1; // end of the c-string (including 0)
|
||||
let size = lister.getxattr(&c_path, &buf[start..c_end]);
|
||||
|
||||
if size > 0 {
|
||||
names.push(Attribute {
|
||||
name: lister.translate_attribute_name(&buf[start..end]),
|
||||
size: size as usize
|
||||
});
|
||||
}
|
||||
|
||||
start = c_end;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Ok(names)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod lister {
|
||||
use std::ffi::CString;
|
||||
use libc::{c_int, size_t, ssize_t, c_char, c_void, uint32_t};
|
||||
use super::FollowSymlinks;
|
||||
use std::ptr;
|
||||
|
||||
extern "C" {
|
||||
fn listxattr(
|
||||
path: *const c_char, namebuf: *mut c_char,
|
||||
size: size_t, options: c_int
|
||||
) -> ssize_t;
|
||||
|
||||
fn getxattr(
|
||||
path: *const c_char, name: *const c_char,
|
||||
value: *mut c_void, size: size_t, position: uint32_t,
|
||||
options: c_int
|
||||
) -> ssize_t;
|
||||
}
|
||||
|
||||
pub struct Lister {
|
||||
c_flags: c_int,
|
||||
}
|
||||
|
||||
impl Lister {
|
||||
pub fn new(do_follow: FollowSymlinks) -> Lister {
|
||||
let c_flags: c_int = match do_follow {
|
||||
FollowSymlinks::Yes => 0x0001,
|
||||
FollowSymlinks::No => 0x0000,
|
||||
};
|
||||
|
||||
Lister { c_flags: c_flags }
|
||||
}
|
||||
|
||||
pub fn translate_attribute_name(&self, input: &[u8]) -> String {
|
||||
use std::str::from_utf8_unchecked;
|
||||
|
||||
unsafe {
|
||||
from_utf8_unchecked(input).into()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn listxattr_first(&self, c_path: &CString) -> ssize_t {
|
||||
unsafe {
|
||||
listxattr(c_path.as_ptr(), ptr::null_mut(), 0, self.c_flags)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn listxattr_second(&self, c_path: &CString, buf: &mut Vec<u8>, bufsize: ssize_t) -> ssize_t {
|
||||
unsafe {
|
||||
listxattr(
|
||||
c_path.as_ptr(),
|
||||
buf.as_mut_ptr() as *mut c_char,
|
||||
bufsize as size_t, self.c_flags
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getxattr(&self, c_path: &CString, buf: &[u8]) -> ssize_t {
|
||||
unsafe {
|
||||
getxattr(
|
||||
c_path.as_ptr(),
|
||||
buf.as_ptr() as *const c_char,
|
||||
ptr::null_mut(), 0, 0, self.c_flags
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
mod lister {
|
||||
use std::ffi::CString;
|
||||
use libc::{size_t, ssize_t, c_char, c_void};
|
||||
use super::FollowSymlinks;
|
||||
use std::ptr;
|
||||
|
||||
extern "C" {
|
||||
fn listxattr(
|
||||
path: *const c_char, list: *mut c_char, size: size_t
|
||||
) -> ssize_t;
|
||||
|
||||
fn llistxattr(
|
||||
path: *const c_char, list: *mut c_char, size: size_t
|
||||
) -> ssize_t;
|
||||
|
||||
fn getxattr(
|
||||
path: *const c_char, name: *const c_char,
|
||||
value: *mut c_void, size: size_t
|
||||
) -> ssize_t;
|
||||
|
||||
fn lgetxattr(
|
||||
path: *const c_char, name: *const c_char,
|
||||
value: *mut c_void, size: size_t
|
||||
) -> ssize_t;
|
||||
}
|
||||
|
||||
pub struct Lister {
|
||||
follow_symlinks: FollowSymlinks,
|
||||
}
|
||||
|
||||
impl Lister {
|
||||
pub fn new(follow_symlinks: FollowSymlinks) -> Lister {
|
||||
Lister { follow_symlinks: follow_symlinks }
|
||||
}
|
||||
|
||||
pub fn translate_attribute_name(&self, input: &[u8]) -> String {
|
||||
String::from_utf8_lossy(input).into_owned()
|
||||
}
|
||||
|
||||
pub fn listxattr_first(&self, c_path: &CString) -> ssize_t {
|
||||
let listxattr = match self.follow_symlinks {
|
||||
FollowSymlinks::Yes => listxattr,
|
||||
FollowSymlinks::No => llistxattr,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
listxattr(c_path.as_ptr(), ptr::null_mut(), 0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn listxattr_second(&self, c_path: &CString, buf: &mut Vec<u8>, bufsize: ssize_t) -> ssize_t {
|
||||
let listxattr = match self.follow_symlinks {
|
||||
FollowSymlinks::Yes => listxattr,
|
||||
FollowSymlinks::No => llistxattr,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
listxattr(
|
||||
c_path.as_ptr(),
|
||||
buf.as_mut_ptr() as *mut c_char,
|
||||
bufsize as size_t
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getxattr(&self, c_path: &CString, buf: &[u8]) -> ssize_t {
|
||||
let getxattr = match self.follow_symlinks {
|
||||
FollowSymlinks::Yes => getxattr,
|
||||
FollowSymlinks::No => lgetxattr,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
getxattr(
|
||||
c_path.as_ptr(),
|
||||
buf.as_ptr() as *const c_char,
|
||||
ptr::null_mut(), 0
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,133 +0,0 @@
|
||||
//! Extended attribute support for darwin
|
||||
extern crate libc;
|
||||
|
||||
use std::io;
|
||||
use std::mem;
|
||||
use std::path::Path;
|
||||
use std::ptr;
|
||||
|
||||
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
|
||||
const XATTR_SHOWCOMPRESSION: c_int = 0x0020;
|
||||
|
||||
|
||||
extern "C" {
|
||||
fn listxattr(path: *const c_char, namebuf: *mut c_char,
|
||||
size: size_t, options: c_int) -> ssize_t;
|
||||
fn getxattr(path: *const c_char, name: *const c_char,
|
||||
value: *mut c_void, size: size_t, position: uint32_t,
|
||||
options: c_int) -> ssize_t;
|
||||
}
|
||||
|
||||
|
||||
/// Attributes which can be passed to `Attribute::list_with_flags`
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum ListFlags {
|
||||
/// Don't follow symbolic links
|
||||
NoFollow = XATTR_NOFOLLOW as isize,
|
||||
/// Expose HFS Compression extended attributes
|
||||
ShowCompression = XATTR_SHOWCOMPRESSION as isize
|
||||
}
|
||||
|
||||
|
||||
/// Extended attribute
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Attribute {
|
||||
name: String,
|
||||
size: usize,
|
||||
}
|
||||
|
||||
impl Attribute {
|
||||
/// Lists the extended attribute of `path`.
|
||||
/// Does follow symlinks by default.
|
||||
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 = 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(
|
||||
c_path.as_ptr(),
|
||||
buf.as_mut_ptr() as *mut c_char,
|
||||
bufsize as size_t, c_flags
|
||||
)};
|
||||
if err > 0 {
|
||||
// End indicies of the attribute names
|
||||
// the buffer contains 0-terminates c-strings
|
||||
let idx = buf.iter().enumerate().filter_map(|(i, v)|
|
||||
if *v == 0 { Some(i) } else { None }
|
||||
);
|
||||
let mut names = Vec::new();
|
||||
let mut start = 0;
|
||||
for end in idx {
|
||||
let c_end = end + 1; // end of the c-string (including 0)
|
||||
let size = unsafe {
|
||||
getxattr(
|
||||
c_path.as_ptr(),
|
||||
buf[start..c_end].as_ptr() as *const c_char,
|
||||
ptr::null_mut(), 0, 0, c_flags
|
||||
)
|
||||
};
|
||||
if size > 0 {
|
||||
names.push(Attribute {
|
||||
name: unsafe {
|
||||
// buf is guaranteed to contain valid utf8 strings
|
||||
// see man listxattr
|
||||
mem::transmute::<&[u8], &str>(&buf[start..end]).to_string()
|
||||
},
|
||||
size: size as usize
|
||||
});
|
||||
}
|
||||
start = c_end;
|
||||
}
|
||||
Ok(names)
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Other, "could not read extended attributes"))
|
||||
}
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Other, "could not read extended attributes"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Getter for name
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
/// Getter for size
|
||||
pub fn size(&self) -> usize {
|
||||
self.size
|
||||
}
|
||||
|
||||
/// Lists the extended attributes.
|
||||
/// Follows symlinks like `metadata`
|
||||
pub fn list(path: &Path) -> io::Result<Vec<Attribute>> {
|
||||
Attribute::list_attrs(path, &[])
|
||||
}
|
||||
/// Lists the extended attributes.
|
||||
/// Does not follow symlinks like `symlink_metadata`
|
||||
pub fn llist(path: &Path) -> io::Result<Vec<Attribute>> {
|
||||
Attribute::list_attrs(path, &[ListFlags::NoFollow])
|
||||
}
|
||||
|
||||
/// Returns true if the extended attribute feature is implemented on this platform.
|
||||
#[inline(always)]
|
||||
pub fn feature_implemented() -> bool { true }
|
||||
}
|
||||
|
@ -1,32 +0,0 @@
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Attribute;
|
||||
|
||||
impl Attribute {
|
||||
|
||||
/// Getter for name
|
||||
pub fn name(&self) -> &str {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
/// Getter for size
|
||||
pub fn size(&self) -> usize {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
/// Lists the extended attributes. Follows symlinks like `metadata`
|
||||
pub fn list(_: &Path) -> io::Result<Vec<Attribute>> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
/// Lists the extended attributes. Does not follow symlinks like `symlink_metadata`
|
||||
pub fn llist(_: &Path) -> io::Result<Vec<Attribute>> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
pub fn feature_implemented() -> bool { false }
|
||||
}
|
||||
|
||||
|
@ -1,122 +0,0 @@
|
||||
//! Extended attribute support for darwin
|
||||
extern crate libc;
|
||||
|
||||
use std::ffi::CString;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::ptr;
|
||||
|
||||
use self::libc::{size_t, ssize_t, c_char, c_void};
|
||||
|
||||
|
||||
extern "C" {
|
||||
fn listxattr(path: *const c_char, list: *mut c_char, size: size_t) -> ssize_t;
|
||||
fn llistxattr(path: *const c_char, list: *mut c_char, size: size_t) -> ssize_t;
|
||||
fn getxattr(path: *const c_char, name: *const c_char,
|
||||
value: *mut c_void, size: size_t
|
||||
) -> ssize_t;
|
||||
fn lgetxattr(path: *const c_char, name: *const c_char,
|
||||
value: *mut c_void, size: size_t
|
||||
) -> ssize_t;
|
||||
}
|
||||
|
||||
|
||||
/// Attributes which can be passed to `Attribute::list_with_flags`
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum FollowSymlinks {
|
||||
Yes,
|
||||
No
|
||||
}
|
||||
|
||||
|
||||
/// Extended attribute
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Attribute {
|
||||
name: String,
|
||||
size: usize,
|
||||
}
|
||||
|
||||
impl Attribute {
|
||||
/// Lists the extended attribute of `path`.
|
||||
/// Does follow symlinks by default.
|
||||
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 = 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(
|
||||
c_path.as_ptr(),
|
||||
buf.as_mut_ptr() as *mut c_char,
|
||||
bufsize as size_t
|
||||
)};
|
||||
if err > 0 {
|
||||
// End indicies of the attribute names
|
||||
// the buffer contains 0-terminates c-strings
|
||||
let idx = buf.iter().enumerate().filter_map(|(i, v)|
|
||||
if *v == 0 { Some(i) } else { None }
|
||||
);
|
||||
let mut names = Vec::new();
|
||||
let mut start = 0;
|
||||
for end in idx {
|
||||
let c_end = end + 1; // end of the c-string (including 0)
|
||||
let size = unsafe {
|
||||
getxattr(
|
||||
c_path.as_ptr(),
|
||||
buf[start..c_end].as_ptr() as *const c_char,
|
||||
ptr::null_mut(), 0
|
||||
)
|
||||
};
|
||||
if size > 0 {
|
||||
names.push(Attribute {
|
||||
name: String::from_utf8_lossy(&buf[start..end]).into_owned(),
|
||||
size: size as usize
|
||||
});
|
||||
}
|
||||
start = c_end;
|
||||
}
|
||||
Ok(names)
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Other, "could not read extended attributes"))
|
||||
}
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Other, "could not read extended attributes"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Getter for name
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
/// Getter for size
|
||||
pub fn size(&self) -> usize {
|
||||
self.size
|
||||
}
|
||||
|
||||
/// Lists the extended attributes.
|
||||
/// Follows symlinks like `metadata`
|
||||
pub fn list(path: &Path) -> io::Result<Vec<Attribute>> {
|
||||
Attribute::list_attrs(path, FollowSymlinks::Yes)
|
||||
}
|
||||
/// Lists the extended attributes.
|
||||
/// Does not follow symlinks like `symlink_metadata`
|
||||
pub fn llist(path: &Path) -> io::Result<Vec<Attribute>> {
|
||||
Attribute::list_attrs(path, FollowSymlinks::No)
|
||||
}
|
||||
|
||||
/// Returns true if the extended attribute feature is implemented on this platform.
|
||||
#[inline(always)]
|
||||
pub fn feature_implemented() -> bool { true }
|
||||
}
|
45
src/file.rs
45
src/file.rs
@ -12,7 +12,6 @@ use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use dir::Dir;
|
||||
use options::TimeType;
|
||||
use feature::Attribute;
|
||||
|
||||
use self::fields as f;
|
||||
|
||||
@ -27,27 +26,23 @@ use self::fields as f;
|
||||
pub struct File<'dir> {
|
||||
|
||||
/// This file's name, as a UTF-8 encoded String.
|
||||
pub name: String,
|
||||
pub name: String,
|
||||
|
||||
/// The file's name's extension, if present, extracted from the name. This
|
||||
/// is queried a lot, so it's worth being cached.
|
||||
pub ext: Option<String>,
|
||||
pub ext: Option<String>,
|
||||
|
||||
/// The path that begat this file. Even though the file's name is
|
||||
/// extracted, the path needs to be kept around, as certain operations
|
||||
/// involve looking up the file's absolute location (such as the Git
|
||||
/// status, or searching for compiled files).
|
||||
pub path: PathBuf,
|
||||
pub path: PathBuf,
|
||||
|
||||
/// A cached `metadata` call for this file. This is queried multiple
|
||||
/// times, and is *not* cached by the OS, as it could easily change
|
||||
/// between invocations - but exa is so short-lived it's better to just
|
||||
/// cache it.
|
||||
pub metadata: fs::Metadata,
|
||||
|
||||
/// List of this file's extended attributes. These are only loaded if the
|
||||
/// `xattr` feature is in use.
|
||||
pub xattrs: Vec<Attribute>,
|
||||
pub metadata: fs::Metadata,
|
||||
|
||||
/// A reference to the directory that contains this file, if present.
|
||||
///
|
||||
@ -57,11 +52,7 @@ pub struct File<'dir> {
|
||||
/// However, *directories* that get passed in will produce files that
|
||||
/// contain a reference to it, which is used in certain operations (such
|
||||
/// as looking up a file's Git status).
|
||||
pub dir: Option<&'dir Dir>,
|
||||
|
||||
/// If this `File` is also a directory, then this field is the same file
|
||||
/// as a `Dir`.
|
||||
pub this: Option<Dir>,
|
||||
pub dir: Option<&'dir Dir>,
|
||||
}
|
||||
|
||||
impl<'dir> File<'dir> {
|
||||
@ -70,32 +61,20 @@ impl<'dir> File<'dir> {
|
||||
///
|
||||
/// This uses `symlink_metadata` instead of `metadata`, which doesn't
|
||||
/// follow symbolic links.
|
||||
pub fn from_path(path: &Path, parent: Option<&'dir Dir>, recurse: bool) -> io::Result<File<'dir>> {
|
||||
fs::symlink_metadata(path).map(|metadata| File::with_metadata(metadata, path, parent, recurse))
|
||||
pub fn from_path(path: &Path, parent: Option<&'dir Dir>) -> io::Result<File<'dir>> {
|
||||
fs::symlink_metadata(path).map(|metadata| File::with_metadata(metadata, path, parent))
|
||||
}
|
||||
|
||||
/// Create a new File object from the given metadata result, and other data.
|
||||
pub fn with_metadata(metadata: fs::Metadata, path: &Path, parent: Option<&'dir Dir>, recurse: bool) -> File<'dir> {
|
||||
pub fn with_metadata(metadata: fs::Metadata, path: &Path, parent: Option<&'dir Dir>) -> File<'dir> {
|
||||
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 && metadata.is_dir() {
|
||||
Dir::readdir(path, false).ok()
|
||||
}
|
||||
else {
|
||||
None
|
||||
};
|
||||
|
||||
File {
|
||||
path: path.to_path_buf(),
|
||||
dir: parent,
|
||||
metadata: metadata,
|
||||
ext: ext(&filename),
|
||||
xattrs: Attribute::llist(path).unwrap_or(Vec::new()),
|
||||
name: filename.to_string(),
|
||||
this: this,
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,6 +83,10 @@ impl<'dir> File<'dir> {
|
||||
self.metadata.is_dir()
|
||||
}
|
||||
|
||||
pub fn to_dir(&self) -> io::Result<Dir> {
|
||||
Dir::readdir(&*self.path, false)
|
||||
}
|
||||
|
||||
/// Whether this file is a regular file on the filesystem - that is, not a
|
||||
/// directory, a link, or anything else treated specially.
|
||||
pub fn is_file(&self) -> bool {
|
||||
@ -199,9 +182,7 @@ impl<'dir> File<'dir> {
|
||||
dir: self.dir,
|
||||
metadata: metadata,
|
||||
ext: ext(&filename),
|
||||
xattrs: Attribute::list(&target_path).unwrap_or(Vec::new()),
|
||||
name: filename.to_string(),
|
||||
this: None,
|
||||
})
|
||||
}
|
||||
else {
|
||||
@ -316,7 +297,7 @@ impl<'dir> File<'dir> {
|
||||
other_read: has_bit(unix::fs::OTHER_READ),
|
||||
other_write: has_bit(unix::fs::OTHER_WRITE),
|
||||
other_execute: has_bit(unix::fs::OTHER_EXECUTE),
|
||||
attribute: !self.xattrs.is_empty()
|
||||
attribute: false, // !self.xattrs.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
|
18
src/main.rs
18
src/main.rs
@ -1,3 +1,4 @@
|
||||
#![feature(iter_arith)]
|
||||
#![feature(convert, fs_mode)]
|
||||
#![feature(slice_splits, vec_resize)]
|
||||
|
||||
@ -89,11 +90,8 @@ impl<'dir> Exa<'dir> {
|
||||
let path = Path::new(&*file);
|
||||
let _ = tx.send(match fs::metadata(&path) {
|
||||
Ok(metadata) => {
|
||||
if !metadata.is_dir() {
|
||||
StatResult::File(File::with_metadata(metadata, &path, None, false))
|
||||
}
|
||||
else if is_tree {
|
||||
StatResult::File(File::with_metadata(metadata, &path, None, true))
|
||||
if is_tree || !metadata.is_dir() {
|
||||
StatResult::File(File::with_metadata(metadata, &path, None))
|
||||
}
|
||||
else {
|
||||
StatResult::Dir(path.to_path_buf())
|
||||
@ -146,7 +144,15 @@ impl<'dir> Exa<'dir> {
|
||||
|
||||
match Dir::readdir(&dir_path, self.options.should_scan_for_git()) {
|
||||
Ok(ref dir) => {
|
||||
let mut files = dir.files(false);
|
||||
let mut files = Vec::new();
|
||||
|
||||
for file in dir.files() {
|
||||
match file {
|
||||
Ok(file) => files.push(file),
|
||||
Err((path, e)) => println!("[{}: {}]", path.display(), e),
|
||||
}
|
||||
}
|
||||
|
||||
self.options.transform_files(&mut files);
|
||||
|
||||
// When recursing, add any directories to the dirs stack
|
||||
|
@ -11,7 +11,7 @@ use colours::Colours;
|
||||
use column::Column;
|
||||
use column::Column::*;
|
||||
use dir::Dir;
|
||||
use feature::Attribute;
|
||||
use feature::xattr;
|
||||
use file::File;
|
||||
use output::{Grid, Details, GridDetails, Lines};
|
||||
use term::dimensions;
|
||||
@ -62,7 +62,7 @@ impl Options {
|
||||
opts.optflag("", "git", "show git status");
|
||||
}
|
||||
|
||||
if Attribute::feature_implemented() {
|
||||
if xattr::ENABLED {
|
||||
opts.optflag("@", "extended", "display extended attribute keys and sizes in long (-l) output");
|
||||
}
|
||||
|
||||
@ -281,7 +281,7 @@ impl View {
|
||||
columns: Some(try!(Columns::deduce(matches))),
|
||||
header: matches.opt_present("header"),
|
||||
recurse: dir_action.recurse_options().map(|o| (o, filter)),
|
||||
xattr: Attribute::feature_implemented() && matches.opt_present("extended"),
|
||||
xattr: xattr::ENABLED && matches.opt_present("extended"),
|
||||
colours: if dimensions().is_some() { Colours::colourful() } else { Colours::plain() },
|
||||
};
|
||||
|
||||
@ -302,7 +302,7 @@ impl View {
|
||||
else if matches.opt_present("level") && !matches.opt_present("recurse") && !matches.opt_present("tree") {
|
||||
Err(Useless2("level", "recurse", "tree"))
|
||||
}
|
||||
else if Attribute::feature_implemented() && matches.opt_present("extended") {
|
||||
else if xattr::ENABLED && matches.opt_present("extended") {
|
||||
Err(Useless("extended", false, "long"))
|
||||
}
|
||||
else {
|
||||
@ -640,7 +640,7 @@ impl Columns {
|
||||
mod test {
|
||||
use super::Options;
|
||||
use super::Misfire;
|
||||
use feature::Attribute;
|
||||
use feature::xattr;
|
||||
|
||||
fn is_helpful<T>(misfire: Result<T, Misfire>) -> bool {
|
||||
match misfire {
|
||||
@ -742,7 +742,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn extended_without_long() {
|
||||
if Attribute::feature_implemented() {
|
||||
if xattr::ENABLED {
|
||||
let opts = Options::getopts(&[ "--extended".to_string() ]);
|
||||
assert_eq!(opts.unwrap_err(), Misfire::Useless("extended", false, "long"))
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
use std::iter::repeat;
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use std::string::ToString;
|
||||
|
||||
use colours::Colours;
|
||||
use column::{Alignment, Column, Cell};
|
||||
use dir::Dir;
|
||||
use feature::Attribute;
|
||||
use feature::xattr::{Attribute, FileAttributes};
|
||||
use file::fields as f;
|
||||
use file::File;
|
||||
use options::{Columns, FileFilter, RecurseOptions, SizeFormat};
|
||||
@ -75,7 +77,7 @@ impl Details {
|
||||
|
||||
// Then add files to the table and print it out.
|
||||
self.add_files_to_table(&mut table, files, 0);
|
||||
for cell in table.print_table(self.xattr, self.recurse.is_some()) {
|
||||
for cell in table.print_table() {
|
||||
println!("{}", cell.text);
|
||||
}
|
||||
}
|
||||
@ -84,26 +86,82 @@ impl Details {
|
||||
/// is present.
|
||||
fn add_files_to_table<U: Users>(&self, table: &mut Table<U>, src: &[File], depth: usize) {
|
||||
for (index, file) in src.iter().enumerate() {
|
||||
table.add_file(file, depth, index == src.len() - 1, true);
|
||||
let mut xattrs = Vec::new();
|
||||
let mut errors = Vec::new();
|
||||
|
||||
let has_xattrs = match file.path.attributes() {
|
||||
Ok(xs) => {
|
||||
let r = !xs.is_empty();
|
||||
if self.xattr {
|
||||
for xattr in xs {
|
||||
xattrs.push(xattr);
|
||||
}
|
||||
}
|
||||
r
|
||||
},
|
||||
Err(e) => {
|
||||
if self.xattr {
|
||||
errors.push((e, None));
|
||||
}
|
||||
true
|
||||
},
|
||||
};
|
||||
|
||||
table.add_file(file, depth, index == src.len() - 1, true, has_xattrs);
|
||||
|
||||
// There are two types of recursion that exa supports: a tree
|
||||
// view, which is dealt with here, and multiple listings, which is
|
||||
// dealt with in the main module. So only actually recurse if we
|
||||
// are in tree mode - the other case will be dealt with elsewhere.
|
||||
if let Some((r, filter)) = self.recurse {
|
||||
if r.tree == false || r.is_too_deep(depth) {
|
||||
continue;
|
||||
}
|
||||
if file.is_directory() && r.tree && !r.is_too_deep(depth) {
|
||||
|
||||
// Use the filter to remove unwanted files *before* expanding
|
||||
// them, so we don't examine any directories that wouldn't
|
||||
// have their contents listed anyway.
|
||||
if let Some(ref dir) = file.this {
|
||||
let mut files = dir.files(true);
|
||||
filter.transform_files(&mut files);
|
||||
self.add_files_to_table(table, &files, depth + 1);
|
||||
// Use the filter to remove unwanted files *before* expanding
|
||||
// them, so we don't examine any directories that wouldn't
|
||||
// have their contents listed anyway.
|
||||
match file.to_dir() {
|
||||
Ok(ref dir) => {
|
||||
let mut files = Vec::new();
|
||||
|
||||
for file_to_add in dir.files() {
|
||||
match file_to_add {
|
||||
Ok(f) => files.push(f),
|
||||
Err((path, e)) => errors.push((e, Some(path)))
|
||||
}
|
||||
}
|
||||
|
||||
filter.transform_files(&mut files);
|
||||
|
||||
if !files.is_empty() {
|
||||
for xattr in xattrs {
|
||||
table.add_xattr(xattr, depth + 1, false);
|
||||
}
|
||||
|
||||
for (error, path) in errors {
|
||||
table.add_error(&error, depth + 1, false, path);
|
||||
}
|
||||
|
||||
self.add_files_to_table(table, &files, depth + 1);
|
||||
continue;
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
errors.push((e, None));
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let count = xattrs.len();
|
||||
for (index, xattr) in xattrs.into_iter().enumerate() {
|
||||
table.add_xattr(xattr, depth + 1, errors.is_empty() && index == count - 1);
|
||||
}
|
||||
|
||||
let count = errors.len();
|
||||
for (index, (error, path)) in errors.into_iter().enumerate() {
|
||||
table.add_error(&error, depth + 1, index == count - 1, path);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -112,26 +170,38 @@ impl Details {
|
||||
struct Row {
|
||||
|
||||
/// Vector of cells to display.
|
||||
cells: Vec<Cell>,
|
||||
///
|
||||
/// Most of the rows will be files that have had their metadata
|
||||
/// successfully queried and displayed in these cells, so this will almost
|
||||
/// always be `Some`. It will be `None` for a row that's only displaying
|
||||
/// an attribute or an error.
|
||||
cells: Option<Vec<Cell>>,
|
||||
|
||||
// Did You Know?
|
||||
// A Vec<Cell> and an Option<Vec<Cell>> actually have the same byte size!
|
||||
|
||||
/// This file's name, in coloured output. The name is treated separately
|
||||
/// from the other cells, as it never requires padding.
|
||||
name: Cell,
|
||||
name: Cell,
|
||||
|
||||
/// How many directories deep into the tree structure this is. Directories
|
||||
/// on top have depth 0.
|
||||
depth: usize,
|
||||
|
||||
/// Vector of this file's extended attributes, if that feature is active.
|
||||
attrs: Vec<Attribute>,
|
||||
depth: usize,
|
||||
|
||||
/// Whether this is the last entry in the directory. This flag is used
|
||||
/// when calculating the tree view.
|
||||
last: bool,
|
||||
last: bool,
|
||||
}
|
||||
|
||||
/// Whether this file is a directory and has any children. Also used when
|
||||
/// calculating the tree view.
|
||||
children: bool,
|
||||
impl Row {
|
||||
|
||||
/// Gets the 'width' of the indexed column, if present. If not, returns 0.
|
||||
fn column_width(&self, index: usize) -> usize {
|
||||
match self.cells {
|
||||
Some(ref cells) => cells[index].length,
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -191,30 +261,53 @@ impl<U> Table<U> where U: Users {
|
||||
pub fn add_header(&mut self) {
|
||||
let row = Row {
|
||||
depth: 0,
|
||||
cells: self.columns.iter().map(|c| Cell::paint(self.colours.header, c.header())).collect(),
|
||||
cells: Some(self.columns.iter().map(|c| Cell::paint(self.colours.header, c.header())).collect()),
|
||||
name: Cell::paint(self.colours.header, "Name"),
|
||||
last: false,
|
||||
attrs: Vec::new(),
|
||||
children: false,
|
||||
};
|
||||
|
||||
self.rows.push(row);
|
||||
}
|
||||
|
||||
fn add_error(&mut self, error: &io::Error, depth: usize, last: bool, path: Option<PathBuf>) {
|
||||
let error_message = match path {
|
||||
Some(path) => format!("<{}: {}>", path.display(), error),
|
||||
None => format!("<{}>", error),
|
||||
};
|
||||
|
||||
let row = Row {
|
||||
depth: depth,
|
||||
cells: None,
|
||||
name: Cell::paint(self.colours.broken_arrow, &error_message),
|
||||
last: last,
|
||||
};
|
||||
|
||||
self.rows.push(row);
|
||||
}
|
||||
|
||||
fn add_xattr(&mut self, xattr: Attribute, depth: usize, last: bool) {
|
||||
let row = Row {
|
||||
depth: depth,
|
||||
cells: None,
|
||||
name: Cell::paint(self.colours.perms.attribute, &format!("{}\t{}", xattr.name, xattr.size)),
|
||||
last: last,
|
||||
};
|
||||
|
||||
self.rows.push(row);
|
||||
}
|
||||
|
||||
/// Get the cells for the given file, and add the result to the table.
|
||||
pub fn add_file(&mut self, file: &File, depth: usize, last: bool, links: bool) {
|
||||
let cells = self.cells_for_file(file);
|
||||
fn add_file(&mut self, file: &File, depth: usize, last: bool, links: bool, xattrs: bool) {
|
||||
let cells = self.cells_for_file(file, xattrs);
|
||||
self.add_file_with_cells(cells, file, depth, last, links)
|
||||
}
|
||||
|
||||
pub fn add_file_with_cells(&mut self, cells: Vec<Cell>, file: &File, depth: usize, last: bool, links: bool) {
|
||||
let row = Row {
|
||||
depth: depth,
|
||||
cells: cells,
|
||||
cells: Some(cells),
|
||||
name: Cell { text: filename(file, &self.colours, links), length: file.file_name_width() },
|
||||
last: last,
|
||||
attrs: file.xattrs.clone(),
|
||||
children: file.this.is_some(),
|
||||
};
|
||||
|
||||
self.rows.push(row);
|
||||
@ -222,15 +315,15 @@ impl<U> Table<U> where U: Users {
|
||||
|
||||
/// Use the list of columns to find which cells should be produced for
|
||||
/// this file, per-column.
|
||||
pub fn cells_for_file(&mut self, file: &File) -> Vec<Cell> {
|
||||
pub fn cells_for_file(&mut self, file: &File, xattrs: bool) -> Vec<Cell> {
|
||||
self.columns.clone().iter()
|
||||
.map(|c| self.display(file, c))
|
||||
.map(|c| self.display(file, c, xattrs))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn display(&mut self, file: &File, column: &Column) -> Cell {
|
||||
fn display(&mut self, file: &File, column: &Column, xattrs: bool) -> Cell {
|
||||
match *column {
|
||||
Column::Permissions => self.render_permissions(file.permissions()),
|
||||
Column::Permissions => self.render_permissions(file.permissions(), xattrs),
|
||||
Column::FileSize(fmt) => self.render_size(file.size(), fmt),
|
||||
Column::Timestamp(t) => self.render_time(file.timestamp(t)),
|
||||
Column::HardLinks => self.render_links(file.links()),
|
||||
@ -242,7 +335,7 @@ impl<U> Table<U> where U: Users {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_permissions(&self, permissions: f::Permissions) -> Cell {
|
||||
fn render_permissions(&self, permissions: f::Permissions, xattrs: bool) -> Cell {
|
||||
let c = self.colours.perms;
|
||||
let bit = |bit, chr: &'static str, style: Style| {
|
||||
if bit { style.paint(chr) } else { self.colours.punctuation.paint("-") }
|
||||
@ -272,7 +365,7 @@ impl<U> Table<U> where U: Users {
|
||||
bit(permissions.other_execute, "x", c.other_execute),
|
||||
];
|
||||
|
||||
if permissions.attribute {
|
||||
if xattrs {
|
||||
columns.push(c.attribute.paint("@"));
|
||||
}
|
||||
|
||||
@ -388,8 +481,8 @@ impl<U> Table<U> where U: Users {
|
||||
Cell::paint(style, &*group_name)
|
||||
}
|
||||
|
||||
/// Print the table to standard output, consuming it in the process.
|
||||
pub fn print_table(&self, xattr: bool, show_children: bool) -> Vec<Cell> {
|
||||
/// Render the table as a vector of Cells, to be displayed on standard output.
|
||||
pub fn print_table(&self) -> Vec<Cell> {
|
||||
let mut stack = Vec::new();
|
||||
let mut cells = Vec::new();
|
||||
|
||||
@ -397,19 +490,26 @@ impl<U> Table<U> where U: Users {
|
||||
// each column, then formatting each cell in that column to be the
|
||||
// width of that one.
|
||||
let column_widths: Vec<usize> = (0 .. self.columns.len())
|
||||
.map(|n| self.rows.iter().map(|row| row.cells[n].length).max().unwrap_or(0))
|
||||
.map(|n| self.rows.iter().map(|row| row.column_width(n)).max().unwrap_or(0))
|
||||
.collect();
|
||||
|
||||
let total_width: usize = self.columns.len() + column_widths.iter().sum::<usize>();
|
||||
|
||||
for row in self.rows.iter() {
|
||||
let mut cell = Cell::empty();
|
||||
|
||||
for (n, width) in column_widths.iter().enumerate() {
|
||||
match self.columns[n].alignment() {
|
||||
Alignment::Left => { cell.append(&row.cells[n]); cell.add_spaces(width - row.cells[n].length); }
|
||||
Alignment::Right => { cell.add_spaces(width - row.cells[n].length); cell.append(&row.cells[n]); }
|
||||
}
|
||||
if let Some(ref cells) = row.cells {
|
||||
for (n, width) in column_widths.iter().enumerate() {
|
||||
match self.columns[n].alignment() {
|
||||
Alignment::Left => { cell.append(&cells[n]); cell.add_spaces(width - cells[n].length); }
|
||||
Alignment::Right => { cell.add_spaces(width - cells[n].length); cell.append(&cells[n]); }
|
||||
}
|
||||
|
||||
cell.add_spaces(1);
|
||||
cell.add_spaces(1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
cell.add_spaces(total_width)
|
||||
}
|
||||
|
||||
let mut filename = String::new();
|
||||
@ -419,40 +519,27 @@ impl<U> Table<U> where U: Users {
|
||||
// necessary to maintain information about the previously-printed
|
||||
// lines, as the output will change based on whether the
|
||||
// *previous* entry was the last in its directory.
|
||||
if show_children {
|
||||
stack.resize(row.depth + 1, TreePart::Edge);
|
||||
stack[row.depth] = if row.last { TreePart::Corner } else { TreePart::Edge };
|
||||
stack.resize(row.depth + 1, TreePart::Edge);
|
||||
stack[row.depth] = if row.last { TreePart::Corner } else { TreePart::Edge };
|
||||
|
||||
for i in 1 .. row.depth + 1 {
|
||||
filename.push_str(&*self.colours.punctuation.paint(stack[i].ascii_art()).to_string());
|
||||
filename_length += 4;
|
||||
}
|
||||
for i in 1 .. row.depth + 1 {
|
||||
filename.push_str(&*self.colours.punctuation.paint(stack[i].ascii_art()).to_string());
|
||||
filename_length += 4;
|
||||
}
|
||||
|
||||
if row.children {
|
||||
stack[row.depth] = if row.last { TreePart::Blank } else { TreePart::Line };
|
||||
}
|
||||
stack[row.depth] = if row.last { TreePart::Blank } else { TreePart::Line };
|
||||
|
||||
// If any tree characters have been printed, then add an extra
|
||||
// space, which makes the output look much better.
|
||||
if row.depth != 0 {
|
||||
filename.push(' ');
|
||||
filename_length += 1;
|
||||
}
|
||||
// If any tree characters have been printed, then add an extra
|
||||
// space, which makes the output look much better.
|
||||
if row.depth != 0 {
|
||||
filename.push(' ');
|
||||
filename_length += 1;
|
||||
}
|
||||
|
||||
// Print the name without worrying about padding.
|
||||
filename.push_str(&*row.name.text);
|
||||
filename_length += row.name.length;
|
||||
|
||||
if xattr {
|
||||
let width = row.attrs.iter().map(|a| a.name().len()).max().unwrap_or(0);
|
||||
for attr in row.attrs.iter() {
|
||||
let name = attr.name();
|
||||
let spaces: String = repeat(" ").take(width - name.len()).collect();
|
||||
filename.push_str(&*format!("\n{}{} {}", name, spaces, attr.size()))
|
||||
}
|
||||
}
|
||||
|
||||
cell.append(&Cell { text: filename, length: filename_length });
|
||||
cells.push(cell);
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ use term_grid as grid;
|
||||
|
||||
use column::{Column, Cell};
|
||||
use dir::Dir;
|
||||
use feature::xattr::FileAttributes;
|
||||
use file::File;
|
||||
use output::details::{Details, Table};
|
||||
use output::grid::Grid;
|
||||
@ -15,6 +16,13 @@ pub struct GridDetails {
|
||||
pub details: Details,
|
||||
}
|
||||
|
||||
fn file_has_xattrs(file: &File) -> bool {
|
||||
match file.path.attributes() {
|
||||
Ok(attrs) => !attrs.is_empty(),
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
impl GridDetails {
|
||||
pub fn view(&self, dir: Option<&Dir>, files: &[File]) {
|
||||
let columns_for_dir = match self.details.columns {
|
||||
@ -23,7 +31,7 @@ impl GridDetails {
|
||||
};
|
||||
|
||||
let mut first_table = Table::with_options(self.details.colours, columns_for_dir.clone());
|
||||
let cells: Vec<_> = files.iter().map(|file| first_table.cells_for_file(file)).collect();
|
||||
let cells: Vec<_> = files.iter().map(|file| first_table.cells_for_file(file, file_has_xattrs(file))).collect();
|
||||
|
||||
let mut last_working_table = self.make_grid(1, &*columns_for_dir, files, cells.clone());
|
||||
|
||||
@ -73,7 +81,7 @@ impl GridDetails {
|
||||
tables[index].add_file_with_cells(row, file, 0, false, false);
|
||||
}
|
||||
|
||||
let columns: Vec<_> = tables.iter().map(|t| t.print_table(false, false)).collect();
|
||||
let columns: Vec<_> = tables.iter().map(|t| t.print_table()).collect();
|
||||
|
||||
let direction = if self.grid.across { grid::Direction::LeftToRight }
|
||||
else { grid::Direction::TopToBottom };
|
||||
|
Loading…
Reference in New Issue
Block a user