Merge branch 'child-nodes'

This commit is contained in:
Ben S 2015-08-26 12:19:51 +01:00
commit eee49ece04
13 changed files with 563 additions and 487 deletions

View File

@ -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 language: rust
rust: nightly rust: nightly

146
Cargo.lock generated
View File

@ -5,18 +5,27 @@ dependencies = [
"ansi_term 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "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)", "getopts 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
"git2 0.2.13 (git+https://github.com/alexcrichton/git2-rs.git)", "git2 0.3.0 (git+https://github.com/alexcrichton/git2-rs.git)",
"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)",
"locale 0.1.8 (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)", "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)", "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)", "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)", "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)", "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)", "unicode-width 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"users 0.4.2 (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]] [[package]]
@ -24,7 +33,7 @@ name = "aho-corasick"
version = "0.3.0" version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ 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]] [[package]]
@ -42,13 +51,21 @@ name = "byteorder"
version = "0.3.11" version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "datetime" name = "datetime"
version = "0.2.1" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"locale 0.1.8 (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.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)", "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 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)", "regex_macros 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
@ -57,12 +74,16 @@ dependencies = [
[[package]] [[package]]
name = "gcc" name = "gcc"
version = "0.3.9" version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "getopts" name = "getopts"
version = "0.2.11" version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"log 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -70,28 +91,29 @@ dependencies = [
[[package]] [[package]]
name = "git2" name = "git2"
version = "0.2.13" version = "0.3.0"
source = "git+https://github.com/alexcrichton/git2-rs.git#889cf3dd62bcf8406d7c5381699467cdcb79d55e" source = "git+https://github.com/alexcrichton/git2-rs.git#cbe8e1a65ac9b16bc05137f80673e74c4d36f6e5"
dependencies = [ dependencies = [
"bitflags 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libgit2-sys 0.2.18 (git+https://github.com/alexcrichton/git2-rs.git)", "libgit2-sys 0.3.2 (git+https://github.com/alexcrichton/git2-rs.git)",
"url 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "url 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.1.8" version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "libgit2-sys" name = "libgit2-sys"
version = "0.2.18" version = "0.3.2"
source = "git+https://github.com/alexcrichton/git2-rs.git#889cf3dd62bcf8406d7c5381699467cdcb79d55e" source = "git+https://github.com/alexcrichton/git2-rs.git#cbe8e1a65ac9b16bc05137f80673e74c4d36f6e5"
dependencies = [ dependencies = [
"libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "cmake 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libssh2-sys 0.1.26 (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.6 (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)", "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)", "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" version = "2.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ 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]] [[package]]
name = "libssh2-sys" name = "libssh2-sys"
version = "0.1.26" version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "cmake 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libz-sys 0.1.6 (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)", "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)", "pkg-config 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "libz-sys" name = "libz-sys"
version = "0.1.6" version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ 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)", "pkg-config 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -129,8 +153,8 @@ name = "locale"
version = "0.1.8" version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ 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)",
"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]] [[package]]
@ -138,7 +162,7 @@ name = "log"
version = "0.3.1" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ 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]] [[package]]
@ -148,24 +172,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "0.1.3" version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ 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]] [[package]]
name = "natord" name = "natord"
version = "1.0.8" version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "num" name = "num"
version = "0.1.25" version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ 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)",
"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]] [[package]]
@ -173,7 +197,7 @@ name = "num_cpus"
version = "0.2.6" version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ 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]] [[package]]
@ -181,7 +205,7 @@ name = "number_prefix"
version = "0.2.4" version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ 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]] [[package]]
@ -189,8 +213,8 @@ name = "openssl-sys"
version = "0.6.4" version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"gcc 0.3.9 (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.8 (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)", "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)", "pkg-config 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -200,7 +224,7 @@ name = "pad"
version = "0.1.4" version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ 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]] [[package]]
@ -210,7 +234,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "pnacl-build-helper" name = "pnacl-build-helper"
version = "1.4.5" version = "1.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"tempdir 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "tempdir 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
@ -218,10 +242,12 @@ dependencies = [
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.3.8" version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ 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]] [[package]]
@ -230,13 +256,13 @@ version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"aho-corasick 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "memchr 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex-syntax 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.2.0" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
@ -249,7 +275,7 @@ dependencies = [
[[package]] [[package]]
name = "rustc-serialize" name = "rustc-serialize"
version = "0.3.15" version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
@ -257,7 +283,7 @@ name = "tempdir"
version = "0.3.4" version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ 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]] [[package]]
@ -265,7 +291,7 @@ name = "term_grid"
version = "0.1.2" version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ 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]] [[package]]
@ -283,23 +309,33 @@ dependencies = [
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
version = "0.1.2" version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "url" name = "url"
version = "0.2.36" version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "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]] [[package]]
name = "users" name = "users"
version = "0.4.2" version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ 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"

View File

@ -1,6 +1,7 @@
use std::io; use std::io;
use std::fs; use std::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::slice::Iter as SliceIter;
use feature::Git; use feature::Git;
use file::{File, fields}; use file::{File, fields};
@ -22,31 +23,26 @@ impl Dir {
/// Create a new Dir object filled with all the files in the directory /// 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 /// 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> { pub fn readdir(path: &Path, git: bool) -> io::Result<Dir> {
fs::read_dir(path).map(|dir_obj| Dir { let reader = try!(fs::read_dir(path));
contents: dir_obj.map(|entry| entry.unwrap().path()).collect(), let contents = try!(reader.map(|e| e.map(|e| e.path())).collect());
Ok(Dir {
contents: contents,
path: path.to_path_buf(), path: path.to_path_buf(),
git: if git { Git::scan(path).ok() } else { None }, git: if git { Git::scan(path).ok() } else { None },
}) })
} }
/// Produce a vector of File objects from an initialised directory, /// Produce an iterator of IO results of trying to read all the files in
/// printing out an error if any of the Files fail to be created. /// this directory.
/// pub fn files<'dir>(&'dir self) -> Files<'dir> {
/// Passing in `recurse` means that any directories will be scanned for Files {
/// their contents, as well. inner: self.contents.iter(),
pub fn files(&self, recurse: bool) -> Vec<File> { dir: &self,
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),
}
} }
files
} }
/// Whether this directory contains a file with the given path. /// 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)))
}
}

View File

@ -1,13 +1,5 @@
// Extended attribute support // Extended attribute support
pub mod xattr;
#[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;
// Git support // Git support

238
src/feature/xattr.rs Normal file
View 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
)
}
}
}
}

View File

@ -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 }
}

View File

@ -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 }
}

View File

@ -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 }
}

View File

@ -12,7 +12,6 @@ use unicode_width::UnicodeWidthStr;
use dir::Dir; use dir::Dir;
use options::TimeType; use options::TimeType;
use feature::Attribute;
use self::fields as f; use self::fields as f;
@ -27,27 +26,23 @@ use self::fields as f;
pub struct File<'dir> { pub struct File<'dir> {
/// This file's name, as a UTF-8 encoded String. /// 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 /// The file's name's extension, if present, extracted from the name. This
/// is queried a lot, so it's worth being cached. /// 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 /// The path that begat this file. Even though the file's name is
/// extracted, the path needs to be kept around, as certain operations /// extracted, the path needs to be kept around, as certain operations
/// involve looking up the file's absolute location (such as the Git /// involve looking up the file's absolute location (such as the Git
/// status, or searching for compiled files). /// status, or searching for compiled files).
pub path: PathBuf, pub path: PathBuf,
/// A cached `metadata` call for this file. This is queried multiple /// A cached `metadata` call for this file. This is queried multiple
/// times, and is *not* cached by the OS, as it could easily change /// 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 /// between invocations - but exa is so short-lived it's better to just
/// cache it. /// cache it.
pub metadata: fs::Metadata, 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>,
/// A reference to the directory that contains this file, if present. /// 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 /// However, *directories* that get passed in will produce files that
/// contain a reference to it, which is used in certain operations (such /// contain a reference to it, which is used in certain operations (such
/// as looking up a file's Git status). /// as looking up a file's Git status).
pub dir: Option<&'dir Dir>, 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>,
} }
impl<'dir> File<'dir> { impl<'dir> File<'dir> {
@ -70,32 +61,20 @@ impl<'dir> File<'dir> {
/// ///
/// This uses `symlink_metadata` instead of `metadata`, which doesn't /// This uses `symlink_metadata` instead of `metadata`, which doesn't
/// follow symbolic links. /// follow symbolic links.
pub fn from_path(path: &Path, parent: Option<&'dir Dir>, recurse: bool) -> io::Result<File<'dir>> { 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, recurse)) 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. /// 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); 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 { File {
path: path.to_path_buf(), path: path.to_path_buf(),
dir: parent, dir: parent,
metadata: metadata, metadata: metadata,
ext: ext(&filename), ext: ext(&filename),
xattrs: Attribute::llist(path).unwrap_or(Vec::new()),
name: filename.to_string(), name: filename.to_string(),
this: this,
} }
} }
@ -104,6 +83,10 @@ impl<'dir> File<'dir> {
self.metadata.is_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 /// Whether this file is a regular file on the filesystem - that is, not a
/// directory, a link, or anything else treated specially. /// directory, a link, or anything else treated specially.
pub fn is_file(&self) -> bool { pub fn is_file(&self) -> bool {
@ -199,9 +182,7 @@ impl<'dir> File<'dir> {
dir: self.dir, dir: self.dir,
metadata: metadata, metadata: metadata,
ext: ext(&filename), ext: ext(&filename),
xattrs: Attribute::list(&target_path).unwrap_or(Vec::new()),
name: filename.to_string(), name: filename.to_string(),
this: None,
}) })
} }
else { else {
@ -316,7 +297,7 @@ impl<'dir> File<'dir> {
other_read: has_bit(unix::fs::OTHER_READ), other_read: has_bit(unix::fs::OTHER_READ),
other_write: has_bit(unix::fs::OTHER_WRITE), other_write: has_bit(unix::fs::OTHER_WRITE),
other_execute: has_bit(unix::fs::OTHER_EXECUTE), other_execute: has_bit(unix::fs::OTHER_EXECUTE),
attribute: !self.xattrs.is_empty() attribute: false, // !self.xattrs.is_empty()
} }
} }

View File

@ -1,3 +1,4 @@
#![feature(iter_arith)]
#![feature(convert, fs_mode)] #![feature(convert, fs_mode)]
#![feature(slice_splits, vec_resize)] #![feature(slice_splits, vec_resize)]
@ -89,11 +90,8 @@ impl<'dir> Exa<'dir> {
let path = Path::new(&*file); let path = Path::new(&*file);
let _ = tx.send(match fs::metadata(&path) { let _ = tx.send(match fs::metadata(&path) {
Ok(metadata) => { Ok(metadata) => {
if !metadata.is_dir() { if is_tree || !metadata.is_dir() {
StatResult::File(File::with_metadata(metadata, &path, None, false)) StatResult::File(File::with_metadata(metadata, &path, None))
}
else if is_tree {
StatResult::File(File::with_metadata(metadata, &path, None, true))
} }
else { else {
StatResult::Dir(path.to_path_buf()) 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()) { match Dir::readdir(&dir_path, self.options.should_scan_for_git()) {
Ok(ref dir) => { 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); self.options.transform_files(&mut files);
// When recursing, add any directories to the dirs stack // When recursing, add any directories to the dirs stack

View File

@ -11,7 +11,7 @@ use colours::Colours;
use column::Column; use column::Column;
use column::Column::*; use column::Column::*;
use dir::Dir; use dir::Dir;
use feature::Attribute; use feature::xattr;
use file::File; use file::File;
use output::{Grid, Details, GridDetails, Lines}; use output::{Grid, Details, GridDetails, Lines};
use term::dimensions; use term::dimensions;
@ -62,7 +62,7 @@ impl Options {
opts.optflag("", "git", "show git status"); 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"); 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))), columns: Some(try!(Columns::deduce(matches))),
header: matches.opt_present("header"), header: matches.opt_present("header"),
recurse: dir_action.recurse_options().map(|o| (o, filter)), 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() }, 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") { else if matches.opt_present("level") && !matches.opt_present("recurse") && !matches.opt_present("tree") {
Err(Useless2("level", "recurse", "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")) Err(Useless("extended", false, "long"))
} }
else { else {
@ -640,7 +640,7 @@ impl Columns {
mod test { mod test {
use super::Options; use super::Options;
use super::Misfire; use super::Misfire;
use feature::Attribute; use feature::xattr;
fn is_helpful<T>(misfire: Result<T, Misfire>) -> bool { fn is_helpful<T>(misfire: Result<T, Misfire>) -> bool {
match misfire { match misfire {
@ -742,7 +742,7 @@ mod test {
#[test] #[test]
fn extended_without_long() { fn extended_without_long() {
if Attribute::feature_implemented() { if xattr::ENABLED {
let opts = Options::getopts(&[ "--extended".to_string() ]); let opts = Options::getopts(&[ "--extended".to_string() ]);
assert_eq!(opts.unwrap_err(), Misfire::Useless("extended", false, "long")) assert_eq!(opts.unwrap_err(), Misfire::Useless("extended", false, "long"))
} }

View File

@ -1,10 +1,12 @@
use std::iter::repeat; use std::error::Error;
use std::io;
use std::path::PathBuf;
use std::string::ToString; use std::string::ToString;
use colours::Colours; use colours::Colours;
use column::{Alignment, Column, Cell}; use column::{Alignment, Column, Cell};
use dir::Dir; use dir::Dir;
use feature::Attribute; use feature::xattr::{Attribute, FileAttributes};
use file::fields as f; use file::fields as f;
use file::File; use file::File;
use options::{Columns, FileFilter, RecurseOptions, SizeFormat}; use options::{Columns, FileFilter, RecurseOptions, SizeFormat};
@ -75,7 +77,7 @@ impl Details {
// Then add files to the table and print it out. // Then add files to the table and print it out.
self.add_files_to_table(&mut table, files, 0); 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); println!("{}", cell.text);
} }
} }
@ -84,26 +86,82 @@ impl Details {
/// is present. /// is present.
fn add_files_to_table<U: Users>(&self, table: &mut Table<U>, src: &[File], depth: usize) { fn add_files_to_table<U: Users>(&self, table: &mut Table<U>, src: &[File], depth: usize) {
for (index, file) in src.iter().enumerate() { 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 // There are two types of recursion that exa supports: a tree
// view, which is dealt with here, and multiple listings, which is // view, which is dealt with here, and multiple listings, which is
// dealt with in the main module. So only actually recurse if we // dealt with in the main module. So only actually recurse if we
// are in tree mode - the other case will be dealt with elsewhere. // are in tree mode - the other case will be dealt with elsewhere.
if let Some((r, filter)) = self.recurse { if let Some((r, filter)) = self.recurse {
if r.tree == false || r.is_too_deep(depth) { if file.is_directory() && r.tree && !r.is_too_deep(depth) {
continue;
}
// Use the filter to remove unwanted files *before* expanding // Use the filter to remove unwanted files *before* expanding
// them, so we don't examine any directories that wouldn't // them, so we don't examine any directories that wouldn't
// have their contents listed anyway. // have their contents listed anyway.
if let Some(ref dir) = file.this { match file.to_dir() {
let mut files = dir.files(true); Ok(ref dir) => {
filter.transform_files(&mut files); let mut files = Vec::new();
self.add_files_to_table(table, &files, depth + 1);
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 { struct Row {
/// Vector of cells to display. /// 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 /// This file's name, in coloured output. The name is treated separately
/// from the other cells, as it never requires padding. /// from the other cells, as it never requires padding.
name: Cell, name: Cell,
/// How many directories deep into the tree structure this is. Directories /// How many directories deep into the tree structure this is. Directories
/// on top have depth 0. /// on top have depth 0.
depth: usize, depth: usize,
/// Vector of this file's extended attributes, if that feature is active.
attrs: Vec<Attribute>,
/// Whether this is the last entry in the directory. This flag is used /// Whether this is the last entry in the directory. This flag is used
/// when calculating the tree view. /// when calculating the tree view.
last: bool, last: bool,
}
/// Whether this file is a directory and has any children. Also used when impl Row {
/// calculating the tree view.
children: bool, /// 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) { pub fn add_header(&mut self) {
let row = Row { let row = Row {
depth: 0, 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"), name: Cell::paint(self.colours.header, "Name"),
last: false, 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); self.rows.push(row);
} }
/// Get the cells for the given file, and add the result to the table. /// 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) { fn add_file(&mut self, file: &File, depth: usize, last: bool, links: bool, xattrs: bool) {
let cells = self.cells_for_file(file); let cells = self.cells_for_file(file, xattrs);
self.add_file_with_cells(cells, file, depth, last, links) 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) { pub fn add_file_with_cells(&mut self, cells: Vec<Cell>, file: &File, depth: usize, last: bool, links: bool) {
let row = Row { let row = Row {
depth: depth, depth: depth,
cells: cells, cells: Some(cells),
name: Cell { text: filename(file, &self.colours, links), length: file.file_name_width() }, name: Cell { text: filename(file, &self.colours, links), length: file.file_name_width() },
last: last, last: last,
attrs: file.xattrs.clone(),
children: file.this.is_some(),
}; };
self.rows.push(row); 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 /// Use the list of columns to find which cells should be produced for
/// this file, per-column. /// 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() self.columns.clone().iter()
.map(|c| self.display(file, c)) .map(|c| self.display(file, c, xattrs))
.collect() .collect()
} }
fn display(&mut self, file: &File, column: &Column) -> Cell { fn display(&mut self, file: &File, column: &Column, xattrs: bool) -> Cell {
match *column { 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::FileSize(fmt) => self.render_size(file.size(), fmt),
Column::Timestamp(t) => self.render_time(file.timestamp(t)), Column::Timestamp(t) => self.render_time(file.timestamp(t)),
Column::HardLinks => self.render_links(file.links()), 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 c = self.colours.perms;
let bit = |bit, chr: &'static str, style: Style| { let bit = |bit, chr: &'static str, style: Style| {
if bit { style.paint(chr) } else { self.colours.punctuation.paint("-") } 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), bit(permissions.other_execute, "x", c.other_execute),
]; ];
if permissions.attribute { if xattrs {
columns.push(c.attribute.paint("@")); columns.push(c.attribute.paint("@"));
} }
@ -388,8 +481,8 @@ impl<U> Table<U> where U: Users {
Cell::paint(style, &*group_name) Cell::paint(style, &*group_name)
} }
/// Print the table to standard output, consuming it in the process. /// Render the table as a vector of Cells, to be displayed on standard output.
pub fn print_table(&self, xattr: bool, show_children: bool) -> Vec<Cell> { pub fn print_table(&self) -> Vec<Cell> {
let mut stack = Vec::new(); let mut stack = Vec::new();
let mut cells = 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 // each column, then formatting each cell in that column to be the
// width of that one. // width of that one.
let column_widths: Vec<usize> = (0 .. self.columns.len()) 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(); .collect();
let total_width: usize = self.columns.len() + column_widths.iter().sum::<usize>();
for row in self.rows.iter() { for row in self.rows.iter() {
let mut cell = Cell::empty(); let mut cell = Cell::empty();
for (n, width) in column_widths.iter().enumerate() { if let Some(ref cells) = row.cells {
match self.columns[n].alignment() { for (n, width) in column_widths.iter().enumerate() {
Alignment::Left => { cell.append(&row.cells[n]); cell.add_spaces(width - row.cells[n].length); } match self.columns[n].alignment() {
Alignment::Right => { cell.add_spaces(width - row.cells[n].length); cell.append(&row.cells[n]); } 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(); let mut filename = String::new();
@ -419,40 +519,27 @@ impl<U> Table<U> where U: Users {
// necessary to maintain information about the previously-printed // necessary to maintain information about the previously-printed
// lines, as the output will change based on whether the // lines, as the output will change based on whether the
// *previous* entry was the last in its directory. // *previous* entry was the last in its directory.
if show_children { stack.resize(row.depth + 1, TreePart::Edge);
stack.resize(row.depth + 1, TreePart::Edge); stack[row.depth] = if row.last { TreePart::Corner } else { TreePart::Edge };
stack[row.depth] = if row.last { TreePart::Corner } else { TreePart::Edge };
for i in 1 .. row.depth + 1 { for i in 1 .. row.depth + 1 {
filename.push_str(&*self.colours.punctuation.paint(stack[i].ascii_art()).to_string()); filename.push_str(&*self.colours.punctuation.paint(stack[i].ascii_art()).to_string());
filename_length += 4; 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 // If any tree characters have been printed, then add an extra
// space, which makes the output look much better. // space, which makes the output look much better.
if row.depth != 0 { if row.depth != 0 {
filename.push(' '); filename.push(' ');
filename_length += 1; filename_length += 1;
}
} }
// Print the name without worrying about padding. // Print the name without worrying about padding.
filename.push_str(&*row.name.text); filename.push_str(&*row.name.text);
filename_length += row.name.length; 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 }); cell.append(&Cell { text: filename, length: filename_length });
cells.push(cell); cells.push(cell);
} }

View File

@ -5,6 +5,7 @@ use term_grid as grid;
use column::{Column, Cell}; use column::{Column, Cell};
use dir::Dir; use dir::Dir;
use feature::xattr::FileAttributes;
use file::File; use file::File;
use output::details::{Details, Table}; use output::details::{Details, Table};
use output::grid::Grid; use output::grid::Grid;
@ -15,6 +16,13 @@ pub struct GridDetails {
pub details: Details, pub details: Details,
} }
fn file_has_xattrs(file: &File) -> bool {
match file.path.attributes() {
Ok(attrs) => !attrs.is_empty(),
Err(_) => false,
}
}
impl GridDetails { impl GridDetails {
pub fn view(&self, dir: Option<&Dir>, files: &[File]) { pub fn view(&self, dir: Option<&Dir>, files: &[File]) {
let columns_for_dir = match self.details.columns { 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 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()); 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); 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 } let direction = if self.grid.across { grid::Direction::LeftToRight }
else { grid::Direction::TopToBottom }; else { grid::Direction::TopToBottom };