summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock79
-rw-r--r--Cargo.toml2
-rw-r--r--src/dir.rs12
-rw-r--r--src/file.rs37
-rw-r--r--src/main.rs186
-rw-r--r--src/options.rs67
-rw-r--r--src/output/details.rs312
7 files changed, 415 insertions, 280 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 9bbcfec..c883b53 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5,7 +5,7 @@ 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.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "getopts 0.2.14 (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)",
@@ -13,8 +13,8 @@ dependencies = [
"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)",
+ "scoped_threadpool 0.1.6 (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.3 (registry+https://github.com/rust-lang/crates.io-index)",
"users 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -30,10 +30,10 @@ dependencies = [
[[package]]
name = "aho-corasick"
-version = "0.3.0"
+version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
- "memchr 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -48,12 +48,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "byteorder"
-version = "0.3.11"
+version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cmake"
-version = "0.1.3"
+version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"gcc 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -69,7 +69,7 @@ dependencies = [
"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)",
- "tz 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tz 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -83,11 +83,8 @@ dependencies = [
[[package]]
name = "getopts"
-version = "0.2.13"
+version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "log 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
-]
[[package]]
name = "git2"
@@ -110,11 +107,11 @@ name = "libgit2-sys"
version = "0.3.2"
source = "git+https://github.com/alexcrichton/git2-rs.git#cbe8e1a65ac9b16bc05137f80673e74c4d36f6e5"
dependencies = [
- "cmake 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cmake 0.1.4 (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)",
+ "openssl-sys 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -131,10 +128,10 @@ name = "libssh2-sys"
version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
- "cmake 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cmake 0.1.4 (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.5 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -158,21 +155,13 @@ dependencies = [
]
[[package]]
-name = "log"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
name = "matches"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "memchr"
-version = "0.1.5"
+version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -188,7 +177,7 @@ name = "num"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
- "rand 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -210,7 +199,7 @@ dependencies = [
[[package]]
name = "openssl-sys"
-version = "0.6.4"
+version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"gcc 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -242,7 +231,7 @@ dependencies = [
[[package]]
name = "rand"
-version = "0.3.10"
+version = "0.3.11"
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)",
@@ -255,8 +244,8 @@ name = "regex"
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.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "aho-corasick 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -279,11 +268,32 @@ version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
+name = "rustc_version"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "scoped_threadpool"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rustc_version 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "semver"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
name = "tempdir"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
- "rand 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -295,16 +305,11 @@ dependencies = [
]
[[package]]
-name = "threadpool"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
name = "tz"
-version = "0.2.0"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
- "byteorder 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "byteorder 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 71dbac9..64df420 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -17,8 +17,8 @@ natord = "1.0.7"
num_cpus = "*"
number_prefix = "0.2.3"
pad = "0.1.1"
+scoped_threadpool = "*"
term_grid = "*"
-threadpool = "*"
unicode-width = "*"
users = "0.4.0"
diff --git a/src/dir.rs b/src/dir.rs
index 414ebd3..78e0ed6 100644
--- a/src/dir.rs
+++ b/src/dir.rs
@@ -14,8 +14,15 @@ use file::{File, fields};
/// check the existence of surrounding files, then highlight themselves
/// accordingly. (See `File#get_source_files`)
pub struct Dir {
+
+ /// A vector of the files that have been read from this directory.
contents: Vec<PathBuf>,
- path: PathBuf,
+
+ /// The path that was read.
+ pub path: PathBuf,
+
+ /// Holds a `Git` object if scanning for Git repositories is switched on,
+ /// and this directory happens to contain one.
git: Option<Git>,
}
@@ -25,7 +32,7 @@ impl Dir {
/// pointed to by the given path. Fails if the directory can't be read, or
/// 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 read_dir(path: &Path, git: bool) -> io::Result<Dir> {
let reader = try!(fs::read_dir(path));
let contents = try!(reader.map(|e| e.map(|e| e.path())).collect());
@@ -71,6 +78,7 @@ impl Dir {
}
+/// Iterator over reading the contents of a directory as `File` objects.
pub struct Files<'dir> {
inner: SliceIter<'dir, PathBuf>,
dir: &'dir Dir,
diff --git a/src/file.rs b/src/file.rs
index be8f960..4e2617d 100644
--- a/src/file.rs
+++ b/src/file.rs
@@ -56,6 +56,7 @@ pub struct File<'dir> {
}
impl<'dir> File<'dir> {
+
/// Create a new `File` object from the given `Path`, inside the given
/// `Dir`, if appropriate.
///
@@ -70,11 +71,11 @@ impl<'dir> File<'dir> {
let filename = path_filename(path);
File {
- path: path.to_path_buf(),
- dir: parent,
- metadata: metadata,
- ext: ext(&filename),
- name: filename.to_string(),
+ path: path.to_path_buf(),
+ dir: parent,
+ metadata: metadata,
+ ext: ext(&filename),
+ name: filename.to_string(),
}
}
@@ -83,8 +84,14 @@ impl<'dir> File<'dir> {
self.metadata.is_dir()
}
- pub fn to_dir(&self) -> io::Result<Dir> {
- Dir::readdir(&*self.path, false)
+ /// If this file is a directory on the filesystem, then clone its
+ /// `PathBuf` for use in one of our own `Dir` objects, and read a list of
+ /// its contents.
+ ///
+ /// Returns an IO error upon failure, but this shouldn't be used to check
+ /// if a `File` is a directory or not! For that, just use `is_directory()`.
+ pub fn to_dir(&self, scan_for_git: bool) -> io::Result<Dir> {
+ Dir::read_dir(&*self.path, scan_for_git)
}
/// Whether this file is a regular file on the filesystem - that is, not a
@@ -178,11 +185,11 @@ impl<'dir> File<'dir> {
// Use plain `metadata` instead of `symlink_metadata` - we *want* to follow links.
if let Ok(metadata) = fs::metadata(&target_path) {
Ok(File {
- path: target_path.to_path_buf(),
- dir: self.dir,
- metadata: metadata,
- ext: ext(&filename),
- name: filename.to_string(),
+ path: target_path.to_path_buf(),
+ dir: self.dir,
+ metadata: metadata,
+ ext: ext(&filename),
+ name: filename.to_string(),
})
}
else {
@@ -282,6 +289,10 @@ impl<'dir> File<'dir> {
}
/// This file's permissions, with flags for each bit.
+ ///
+ /// The extended-attribute '@' character that you see in here is in fact
+ /// added in later, to avoid querying the extended attributes more than
+ /// once. (Yes, it's a little hacky.)
pub fn permissions(&self) -> f::Permissions {
let bits = self.metadata.permissions().mode();
let has_bit = |bit| { bits & bit == bit };
@@ -297,7 +308,6 @@ 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: false, // !self.xattrs.is_empty()
}
}
@@ -423,7 +433,6 @@ pub mod fields {
pub other_read: bool,
pub other_write: bool,
pub other_execute: bool,
- pub attribute: bool,
}
pub struct Links {
diff --git a/src/main.rs b/src/main.rs
index e64f3fb..7e540de 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -11,8 +11,8 @@ extern crate natord;
extern crate num_cpus;
extern crate number_prefix;
extern crate pad;
+extern crate scoped_threadpool;
extern crate term_grid;
-extern crate threadpool;
extern crate unicode_width;
extern crate users;
@@ -21,12 +21,8 @@ extern crate git2;
use std::env;
-use std::fs;
-use std::path::{Component, Path, PathBuf};
+use std::path::{Component, Path};
use std::process;
-use std::sync::mpsc::channel;
-
-use threadpool::ThreadPool;
use dir::Dir;
use file::File;
@@ -43,95 +39,43 @@ mod output;
mod term;
-#[cfg(not(test))]
-struct Exa<'dir> {
- count: usize,
+struct Exa {
options: Options,
- dirs: Vec<PathBuf>,
- files: Vec<File<'dir>>,
}
-#[cfg(not(test))]
-impl<'dir> Exa<'dir> {
- fn new(options: Options) -> Exa<'dir> {
- Exa {
- count: 0,
- options: options,
- dirs: Vec::new(),
- files: Vec::new(),
- }
- }
-
- fn load(&mut self, files: &[String]) {
-
- // Separate the user-supplied paths into directories and files.
- // Files are shown first, and then each directory is expanded
- // and listed second.
- let is_tree = self.options.dir_action.is_tree() || self.options.dir_action.is_as_file();
- let total_files = files.len();
-
-
- // Communication between consumer thread and producer threads
- enum StatResult<'dir> {
- File(File<'dir>),
- Dir(PathBuf),
- Error
- }
-
- let pool = ThreadPool::new(8 * num_cpus::get());
- let (tx, rx) = channel();
+impl Exa {
+ fn run(&mut self, args_file_names: &[String]) {
+ let mut files = Vec::new();
+ let mut dirs = Vec::new();
- for file in files.iter() {
- let tx = tx.clone();
- let file = file.clone();
-
- // Spawn producer thread
- pool.execute(move || {
- let path = Path::new(&*file);
- let _ = tx.send(match fs::metadata(&path) {
- Ok(metadata) => {
- if is_tree || !metadata.is_dir() {
- StatResult::File(File::with_metadata(metadata, &path, None))
- }
- else {
- StatResult::Dir(path.to_path_buf())
+ for file_name in args_file_names.iter() {
+ match File::from_path(Path::new(&file_name), None) {
+ Err(e) => {
+ println!("{}: {}", file_name, e);
+ },
+ Ok(f) => {
+ if f.is_directory() && !self.options.dir_action.treat_dirs_as_files() {
+ match f.to_dir(self.options.should_scan_for_git()) {
+ Ok(d) => dirs.push(d),
+ Err(e) => println!("{}: {}", file_name, e),
}
}
- Err(e) => {
- println!("{}: {}", file, e);
- StatResult::Error
+ else {
+ files.push(f);
}
- });
- });
- }
-
- // Spawn consumer thread
- for result in rx.iter().take(total_files) {
- match result {
- StatResult::File(file) => self.files.push(file),
- StatResult::Dir(path) => self.dirs.push(path),
- StatResult::Error => ()
+ },
}
- self.count += 1;
}
- }
- fn print_files(&self) {
- if !self.files.is_empty() {
- self.print(None, &self.files[..]);
- }
- }
+ let any_files = files.is_empty();
+ self.print_files(None, files);
- fn print_dirs(&mut self) {
- let mut first = self.files.is_empty();
+ let is_only_dir = dirs.len() == 1;
+ self.print_dirs(dirs, any_files, is_only_dir);
+ }
- // Directories are put on a stack rather than just being iterated through,
- // as the vector can change as more directories are added.
- loop {
- let dir_path = match self.dirs.pop() {
- None => break,
- Some(f) => f,
- };
+ fn print_dirs(&self, dir_files: Vec<Dir>, mut first: bool, is_only_dir: bool) {
+ for dir in dir_files {
// Put a gap between directories, or between the list of files and the
// first directory.
@@ -142,68 +86,66 @@ impl<'dir> Exa<'dir> {
print!("\n");
}
- match Dir::readdir(&dir_path, self.options.should_scan_for_git()) {
- Ok(ref dir) => {
- let mut files = Vec::new();
+ if !is_only_dir {
+ println!("{}:", dir.path.display());
+ }
- for file in dir.files() {
- match file {
- Ok(file) => files.push(file),
- Err((path, e)) => println!("[{}: {}]", path.display(), e),
- }
- }
+ let mut children = Vec::new();
+ for file in dir.files() {
+ match file {
+ Ok(file) => children.push(file),
+ Err((path, e)) => println!("[{}: {}]", path.display(), e),
+ }
+ };
+
+ self.options.filter_files(&mut children);
+ self.options.sort_files(&mut children);
- self.options.transform_files(&mut files);
-
- // When recursing, add any directories to the dirs stack
- // backwards: the *last* element of the stack is used each
- // time, so by inserting them backwards, they get displayed in
- // the correct sort order.
- if let Some(recurse_opts) = self.options.dir_action.recurse_options() {
- let depth = dir_path.components().filter(|&c| c != Component::CurDir).count() + 1;
- if !recurse_opts.tree && !recurse_opts.is_too_deep(depth) {
- for dir in files.iter().filter(|f| f.is_directory()).rev() {
- self.dirs.push(dir.path.clone());
- }
+ if let Some(recurse_opts) = self.options.dir_action.recurse_options() {
+ let depth = dir.path.components().filter(|&c| c != Component::CurDir).count() + 1;
+ if !recurse_opts.tree && !recurse_opts.is_too_deep(depth) {
+
+ let mut child_dirs = Vec::new();
+ for child_dir in children.iter().filter(|f| f.is_directory()) {
+ match child_dir.to_dir(false) {
+ Ok(d) => child_dirs.push(d),
+ Err(e) => println!("{}: {}", child_dir.path.display(), e),
}
}
- if self.count > 1 {
- println!("{}:", dir_path.display());
+ self.print_files(Some(&dir), children);
+
+ if !child_dirs.is_empty() {
+ self.print_dirs(child_dirs, false, false);
}
- self.count += 1;
- self.print(Some(dir), &files[..]);
- }
- Err(e) => {
- println!("{}: {}", dir_path.display(), e);
- return;
+ continue;
}
- };
+ }
+
+ self.print_files(Some(&dir), children);
+
}
}
- fn print(&self, dir: Option<&Dir>, files: &[File]) {
+ fn print_files(&self, dir: Option<&Dir>, files: Vec<File>) {
match self.options.view {
- View::Grid(g) => g.view(files),
+ View::Grid(g) => g.view(&files),
View::Details(d) => d.view(dir, files),
- View::GridDetails(gd) => gd.view(dir, files),
- View::Lines(l) => l.view(files),
+ View::GridDetails(gd) => gd.view(dir, &files),
+ View::Lines(l) => l.view(&files),
}
}
}
-#[cfg(not(test))]
fn main() {
let args: Vec<String> = env::args().skip(1).collect();
match Options::getopts(&args) {
Ok((options, paths)) => {
- let mut exa = Exa::new(options);
- exa.load(&paths);
- exa.print_files();
- exa.print_dirs();
+ let mut exa = Exa { options: options };
+ exa.run(&paths);
},
Err(e) => {
println!("{}", e);
diff --git a/src/options.rs b/src/options.rs
index d88eee5..dd76218 100644
--- a/src/options.rs
+++ b/src/options.rs
@@ -107,8 +107,12 @@ impl Options {
}, path_strs))
}
- pub fn transform_files(&self, files: &mut Vec<File>) {
- self.filter.transform_files(files)
+ pub fn sort_files(&self, files: &mut Vec<File>) {
+ self.filter.sort_files(files)
+ }
+
+ pub fn filter_files(&self, files: &mut Vec<File>) {
+ self.filter.filter_files(files)
}
/// Whether the View specified in this set of options includes a Git
@@ -124,7 +128,7 @@ impl Options {
}
-#[derive(PartialEq, Debug, Copy, Clone)]
+#[derive(Default, PartialEq, Debug, Copy, Clone)]
pub struct FileFilter {
list_dirs_first: bool,
reverse: bool,
@@ -133,26 +137,14 @@ pub struct FileFilter {
}
impl FileFilter {
- /// Transform the files (sorting, reversing, filtering) before listing them.
- pub fn transform_files(&self, files: &mut Vec<File>) {
-
+ pub fn filter_files(&self, files: &mut Vec<File>) {
if !self.show_invisibles {
files.retain(|f| !f.is_dotfile());
}
+ }
- match self.sort_field {
- SortField::Unsorted => {},
- SortField::Name => files.sort_by(|a, b| natord::compare(&*a.name, &*b.name)),
- SortField::Size => files.sort_by(|a, b| a.metadata.len().cmp(&b.metadata.len())),
- SortField::FileInode => files.sort_by(|a, b| a.metadata.ino().cmp(&b.metadata.ino())),
- SortField::ModifiedDate => files.sort_by(|a, b| a.metadata.mtime().cmp(&b.metadata.mtime())),
- SortField::AccessedDate => files.sort_by(|a, b| a.metadata.atime().cmp(&b.metadata.atime())),
- SortField::CreatedDate => files.sort_by(|a, b| a.metadata.ctime().cmp(&b.metadata.ctime())),
- SortField::Extension => files.sort_by(|a, b| match a.ext.cmp(&b.ext) {
- cmp::Ordering::Equal => natord::compare(&*a.name, &*b.name),
- order => order,
- }),
- }
+ pub fn sort_files(&self, files: &mut Vec<File>) {
+ files.sort_by(|a, b| self.compare_files(a, b));
if self.reverse {
files.reverse();
@@ -163,6 +155,22 @@ impl FileFilter {
files.sort_by(|a, b| b.is_directory().cmp(&a.is_directory()));
}
}
+
+ pub fn compare_files(&self, a: &File, b: &File) -> cmp::Ordering {
+ match self.sort_field {
+ SortField::Unsorted => cmp::Ordering::Equal,
+ SortField::Name => natord::compare(&*a.name, &*b.name),
+ SortField::Size => a.metadata.len().cmp(&b.metadata.len()),
+ SortField::FileInode => a.metadata.ino().cmp(&b.metadata.ino()),
+ SortField::ModifiedDate => a.metadata.mtime().cmp(&b.metadata.mtime()),
+ SortField::AccessedDate => a.metadata.atime().cmp(&b.metadata.atime()),
+ SortField::CreatedDate => a.metadata.ctime().cmp(&b.metadata.ctime()),
+ SortField::Extension => match a.ext.cmp(&b.ext) {
+ cmp::Ordering::Equal => natord::compare(&*a.name, &*b.name),
+ order => order,
+ },
+ }
+ }
}
/// User-supplied field to sort by.
@@ -280,7 +288,8 @@ impl View {
let details = Details {
columns: Some(try!(Columns::deduce(matches))),
header: matches.opt_present("header"),
- recurse: dir_action.recurse_options().map(|o| (o, filter)),
+ recurse: dir_action.recurse_options(),
+ filter: filter,
xattr: xattr::ENABLED && matches.opt_present("extended"),
colours: if dimensions().is_some() { Colours::colourful() } else { Colours::plain() },
};
@@ -328,7 +337,8 @@ impl View {
let details = Details {
columns: None,
header: false,
- recurse: dir_action.recurse_options().map(|o| (o, filter)),
+ recurse: dir_action.recurse_options(),
+ filter: filter,
xattr: false,
colours: if dimensions().is_some() { Colours::colourful() } else { Colours::plain() },
};
@@ -406,6 +416,7 @@ impl SizeFormat {
}
}
+
#[derive(PartialEq, Debug, Copy, Clone)]
pub enum TimeType {
FileAccessed,
@@ -423,6 +434,7 @@ impl TimeType {
}
}
+
#[derive(PartialEq, Debug, Copy, Clone)]
pub struct TimeTypes {
accessed: bool,
@@ -479,6 +491,7 @@ impl TimeTypes {
}
}
+
/// What to do when encountering a directory?
#[derive(PartialEq, Debug, Copy, Clone)]
pub enum DirAction {
@@ -510,21 +523,16 @@ impl DirAction {
}
}
- pub fn is_as_file(&self) -> bool {
+ pub fn treat_dirs_as_files(&self) -> bool {
match *self {
DirAction::AsFile => true,
- _ => false,
- }
- }
-
- pub fn is_tree(&self) -> bool {
- match *self {
DirAction::Recurse(RecurseOptions { tree, .. }) => tree,
_ => false,
- }
+ }
}
}
+
#[derive(PartialEq, Debug, Copy, Clone)]
pub struct RecurseOptions {
pub tree: bool,
@@ -559,6 +567,7 @@ impl RecurseOptions {
}
}
+
#[derive(PartialEq, Copy, Clone, Debug, Default)]
pub struct Columns {
size_format: SizeFormat,
diff --git a/src/output/details.rs b/src/output/details.rs
index 3052448..f87d0ad 100644
--- a/src/output/details.rs
+++ b/src/output/details.rs
@@ -1,3 +1,116 @@
+//! The **Details** output view displays each file as a row in a table.
+//!
+//! It's used in the following situations:
+//!
+//! - Most commonly, when using the `--long` command-line argument to display the
+//! details of each file, which requires using a table view to hold all the data;
+//! - When using the `--tree` argument, which uses the same table view to display
+//! each file on its own line, with the table providing the tree characters;
+//! - When using both the `--long` and `--grid` arguments, which constructs a
+//! series of tables to fit all the data on the screen.
+//!
+//! You will probably recognise it from the `ls --long` command. It looks like
+//! this:
+//!
+//! .rw-r--r-- 9.6k ben 29 Jun 16:16 Cargo.lock
+//! .rw-r--r-- 547 ben 23 Jun 10:54 Cargo.toml
+//! .rw-r--r-- 1.1k ben 23 Nov 2014 LICENCE
+//! .rw-r--r-- 2.5k ben 21 May 14:38 README.md
+//! .rw-r--r-- 382k ben 8 Jun 21:00 screenshot.png
+//! drwxr-xr-x - ben 29 Jun 14:50 src
+//! drwxr-xr-x - ben 28 Jun 19:53 target
+//!
+//! The table is constructed by creating a `Table` value, which produces a `Row`
+//! value for each file. These rows can contain a vector of `Cell`s, or they can
+//! contain depth information for the tree view, or both. These are described
+//! below.
+//!
+//!
+//! ## Constructing Detail Views
+//!
+//! When using the `--long` command-line argument, the details of each file are
+//! displayed next to its name.
+//!
+//! The table holds a vector of all the column types. For each file and column, a
+//! `Cell` value containing the ANSI-coloured text and Unicode width of each cell
+//! is generated, with the row and column determined by indexing into both arrays.
+//!
+//! The column types vector does not actually include the filename. This is
+//! because the filename is always the rightmost field, and as such, it does not
+//! need to have its width queried or be padded with spaces.
+//!
+//! To illustrate the above:
+//!
+//! ┌─────────────────────────────────────────────────────────────────────────┐
+//! │ columns: [ Permissions, Size, User, Date(Modified) ] │
+//! ├─────────────────────────────────────────────────────────────────────────┤
+//! │ rows: cells: filename: │
+//! │ row 1: [ ".rw-r--r--", "9.6k", "ben", "29 Jun 16:16" ] Cargo.lock │
+//! │ row 2: [ ".rw-r--r--", "547", "ben", "23 Jun 10:54" ] Cargo.toml │
+//! │ row 3: [ "drwxr-xr-x", "-", "ben", "29 Jun 14:50" ] src │
+//! │ row 4: [ "drwxr-xr-x", "-", "ben", "28 Jun 19:53" ] target │
+//! └─────────────────────────────────────────────────────────────────────────┘
+//!
+//! Each column in the table needs to be resized to fit its widest argument. This
+//! means that we must wait until every row has been added to the table before it
+//! can be displayed, in order to make sure that every column is wide enough.
+//!
+//!
+//! ## Constructing Tree Views
+//!
+//! When using the `--tree` argument, instead of a vector of cells, each row has a
+//! `depth` field that indicates how far deep in the tree it is: the top level has
+//! depth 0, its children have depth 1, and *their* children have depth 2, and so
+//! on.
+//!
+//! On top of this, it also has a `last` field that specifies whether this is the
+//! last row of this particular consecutive set of rows. This doesn't affect the
+//! file's information; it's just used to display a different set of Unicode tree
+//! characters! The resulting table looks like this:
+//!
+//! ┌───────┬───────┬───────────────────────┐
+//! │ Depth │ Last │ Output │
+//! ├───────┼───────┼───────────────────────┤
+//! │ 0 │ │ documents │
+//! │ 1 │ false │ ├── this_file.txt │
+//! │ 1 │ false │ ├── that_file.txt │
+//! │ 1 │ false │ ├── features │
+//! │ 2 │ false │ │ ├── feature_1.rs │
+//! │ 2 │ false │ │ ├── feature_2.rs │
+//! │ 2 │ true │ │ └── feature_3.rs │
+//! │ 1 │ true │ └── pictures │
+//! │ 2 │ false │ ├── garden.jpg │
+//! │ 2 │ false │ ├── flowers.jpg │
+//! │ 2 │ false │ ├── library.png │
+//! │ 2 │ true │ └── space.tiff │
+//! └───────┴───────┴───────────────────────┘
+//!
+//! Creating the table like this means that each file has to be tested to see if
+//! it's the last one in the group. This is usually done by putting all the files
+//! in a vector beforehand, getting its length, then comparing the index of each
+//! file to see if it's the last one. (As some files may not be successfully
+//! `stat`ted, we don't know how many files are going to exist in each directory)
+//!
+//! These rows have a `None` value for their vector of cells, instead of a `Some`
+//! vector containing any. It's possible to have *both* a vector of cells and
+//! depth and last flags when the user specifies `--tree` *and* `--long`.
+//!
+//!
+//! ## Extended Attributes and Errors
+//!
+//! Finally, files' extended attributes and any errors that occur while statting
+//! them can also be displayed as their children. It looks like this:
+//!
+//! .rw-r--r-- 0 ben 3 Sep 13:26 forbidden
+//! └── <Permission denied (os error 13)>
+//! .rw-r--r--@ 0 ben 3 Sep 13:26 file_with_xattrs
+//! ├── another_greeting (len 2)
+//! └── greeting (len 5)
+//!
+//! These lines also have `None` cells, and the error string or attribute details
+//! are used in place of the filename.
+
+
use std::error::Error;
use std::io;
use std::path::PathBuf;
@@ -49,7 +162,10 @@ pub struct Details {
/// Whether to recurse through directories with a tree view, and if so,
/// which options to use. This field is only relevant here if the `tree`
/// field of the RecurseOptions is `true`.
- pub recurse: Option<(RecurseOptions, FileFilter)>,
+ pub recurse: Option<RecurseOptions>,
+
+ /// How to sort and filter the files after getting their details.
+ pub filter: FileFilter,
/// Whether to show a header line or not.
pub header: bool,
@@ -63,15 +179,19 @@ pub struct Details {
}
impl Details {
- pub fn view(&self, dir: Option<&Dir>, files: &[File]) {
+
+ /// Print the details of the given vector of files -- all of which will
+ /// have been read from the given directory, if present -- to stdout.
+ pub fn view(&self, dir: Option<&Dir>, files: Vec<File>) {
+
// First, transform the Columns object into a vector of columns for
// the current directory.
-
let columns_for_dir = match self.columns {
Some(cols) => cols.for_dir(dir),
None => Vec::new(),
};
+ // Next, add a header if the user requests it.
let mut table = Table::with_options(self.colours, columns_for_dir);
if self.header { table.add_header() }
@@ -82,78 +202,126 @@ impl Details {
}
}
- /// Adds files to the table - recursively, if the `recurse` option
- /// 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() {
- let mut xattrs = Vec::new();
- let mut errors = Vec::new();
-
- let has_xattrs = match fil