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
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)",
"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"

View File

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

View File

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

View File

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

View File

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

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 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);
}

View File

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