Move all optional features into features module

This module provides feature-specific implementations, and also dummy implementations for when they aren't supported by the system or OS.

Doing it this way limits all the #[cfg(feature)] annotations, as we can now just include the module or not.
This commit is contained in:
Ben S 2015-03-26 00:37:12 +00:00
parent 697e1e66e4
commit 2ffa64cff6
11 changed files with 196 additions and 193 deletions

View File

@ -1,11 +1,9 @@
use std::old_io::{fs, IoResult};
use std::old_path::GenericPath;
use std::old_path::posix::Path;
use file::{File, GREY};
#[cfg(feature="git")] use ansi_term::{ANSIString, ANSIStrings};
#[cfg(feature="git")] use ansi_term::Colour::*;
#[cfg(feature="git")] use git2;
use feature::Git;
use file::{File, GREY};
/// A **Dir** provides a cached list of the file paths in a directory that's
/// being listed.
@ -20,6 +18,7 @@ pub struct Dir {
}
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.
@ -67,107 +66,9 @@ impl Dir {
/// Get a string describing the Git status of the given file.
pub fn git_status(&self, path: &Path, prefix_lookup: bool) -> String {
match (&self.git, prefix_lookup) {
(&Some(ref git), false) => git.status(path),
(&Some(ref git), true) => git.dir_status(path),
(&None, _) => GREY.paint("--").to_string(),
(&Some(ref git), false) => git.status(path),
(&Some(ref git), true) => git.dir_status(path),
(&None, _) => GREY.paint("--").to_string(),
}
}
}
/// Container of Git statuses for all the files in this folder's Git repository.
#[cfg(feature="git")]
struct Git {
statuses: Vec<(Path, git2::Status)>,
}
#[cfg(feature="git")]
impl Git {
/// Discover a Git repository on or above this directory, scanning it for
/// the files' statuses if one is found.
fn scan(path: &Path) -> Result<Git, git2::Error> {
use std::os::unix::ffi::OsStrExt;
use std::ffi::AsOsStr;
// TODO: libgit2-rs uses the new Path module, but exa still uses the
// old_path one, and will have to continue to do so until the new IO
// module gets a bit more developed. So we have to turn Paths into
// old_path::Paths. Yes, this is hacky, but hopefully temporary.
let repo = try!(git2::Repository::discover(path));
let workdir = match repo.workdir() {
Some(w) => Path::new(w.as_os_str().as_bytes()),
None => return Ok(Git { statuses: vec![] }), // bare repo
};
let statuses = try!(repo.statuses(None)).iter()
.map(|e| (workdir.join(e.path_bytes()), e.status()))
.collect();
Ok(Git { statuses: statuses })
}
/// Get the status for the file at the given path, if present.
fn status(&self, path: &Path) -> String {
let status = self.statuses.iter()
.find(|p| &p.0 == path);
match status {
Some(&(_, s)) => ANSIStrings( &[Git::index_status(s), Git::working_tree_status(s) ]).to_string(),
None => GREY.paint("--").to_string(),
}
}
/// Get the combined status for all the files whose paths begin with the
/// path that gets passed in. This is used for getting the status of
/// directories, which don't really have an 'official' status.
fn dir_status(&self, dir: &Path) -> String {
let s = self.statuses.iter()
.filter(|p| dir.is_ancestor_of(&p.0))
.fold(git2::Status::empty(), |a, b| a | b.1);
ANSIStrings( &[Git::index_status(s), Git::working_tree_status(s)] ).to_string()
}
/// The character to display if the file has been modified, but not staged.
fn working_tree_status(status: git2::Status) -> ANSIString<'static> {
match status {
s if s.contains(git2::STATUS_WT_NEW) => Green.paint("A"),
s if s.contains(git2::STATUS_WT_MODIFIED) => Blue.paint("M"),
s if s.contains(git2::STATUS_WT_DELETED) => Red.paint("D"),
s if s.contains(git2::STATUS_WT_RENAMED) => Yellow.paint("R"),
s if s.contains(git2::STATUS_WT_TYPECHANGE) => Purple.paint("T"),
_ => GREY.paint("-"),
}
}
/// The character to display if the file has been modified, and the change
/// has been staged.
fn index_status(status: git2::Status) -> ANSIString<'static> {
match status {
s if s.contains(git2::STATUS_INDEX_NEW) => Green.paint("A"),
s if s.contains(git2::STATUS_INDEX_MODIFIED) => Blue.paint("M"),
s if s.contains(git2::STATUS_INDEX_DELETED) => Red.paint("D"),
s if s.contains(git2::STATUS_INDEX_RENAMED) => Yellow.paint("R"),
s if s.contains(git2::STATUS_INDEX_TYPECHANGE) => Purple.paint("T"),
_ => GREY.paint("-"),
}
}
}
#[cfg(not(feature="git"))]
struct Git;
#[cfg(not(feature="git"))]
impl Git {
fn scan(_: &Path) -> Result<Git, ()> {
// Don't do anything without Git support
Err(())
}
fn status(&self, _: &Path) -> String {
// The Err above means that this should never happen
panic!("Tried to access a Git repo without Git support!");
}
fn dir_status(&self, path: &Path) -> String {
self.status(path)
}
}

85
src/feature/git.rs Normal file
View File

@ -0,0 +1,85 @@
use std::old_path::GenericPath;
use std::old_path::posix::Path;
use ansi_term::{ANSIString, ANSIStrings};
use ansi_term::Colour::*;
use git2;
use file::GREY;
/// Container of Git statuses for all the files in this folder's Git repository.
pub struct Git {
statuses: Vec<(Path, git2::Status)>,
}
impl Git {
/// Discover a Git repository on or above this directory, scanning it for
/// the files' statuses if one is found.
pub fn scan(path: &Path) -> Result<Git, git2::Error> {
use std::os::unix::ffi::OsStrExt;
use std::ffi::AsOsStr;
// TODO: libgit2-rs uses the new Path module, but exa still uses the
// old_path one, and will have to continue to do so until the new IO
// module gets a bit more developed. So we have to turn Paths into
// old_path::Paths. Yes, this is hacky, but hopefully temporary.
let new_path = path.as_os_str();
let repo = try!(git2::Repository::discover(new_path));
let workdir = match repo.workdir() {
Some(w) => Path::new(w.as_os_str().as_bytes()),
None => return Ok(Git { statuses: vec![] }), // bare repo
};
let statuses = try!(repo.statuses(None)).iter()
.map(|e| (workdir.join(e.path_bytes()), e.status()))
.collect();
Ok(Git { statuses: statuses })
}
/// Get the status for the file at the given path, if present.
pub fn status(&self, path: &Path) -> String {
let status = self.statuses.iter()
.find(|p| &p.0 == path);
match status {
Some(&(_, s)) => ANSIStrings( &[Git::index_status(s), Git::working_tree_status(s) ]).to_string(),
None => GREY.paint("--").to_string(),
}
}
/// Get the combined status for all the files whose paths begin with the
/// path that gets passed in. This is used for getting the status of
/// directories, which don't really have an 'official' status.
pub fn dir_status(&self, dir: &Path) -> String {
let s = self.statuses.iter()
.filter(|p| dir.is_ancestor_of(&p.0))
.fold(git2::Status::empty(), |a, b| a | b.1);
ANSIStrings( &[Git::index_status(s), Git::working_tree_status(s)] ).to_string()
}
/// The character to display if the file has been modified, but not staged.
fn working_tree_status(status: git2::Status) -> ANSIString<'static> {
match status {
s if s.contains(git2::STATUS_WT_NEW) => Green.paint("A"),
s if s.contains(git2::STATUS_WT_MODIFIED) => Blue.paint("M"),
s if s.contains(git2::STATUS_WT_DELETED) => Red.paint("D"),
s if s.contains(git2::STATUS_WT_RENAMED) => Yellow.paint("R"),
s if s.contains(git2::STATUS_WT_TYPECHANGE) => Purple.paint("T"),
_ => GREY.paint("-"),
}
}
/// The character to display if the file has been modified, and the change
/// has been staged.
fn index_status(status: git2::Status) -> ANSIString<'static> {
match status {
s if s.contains(git2::STATUS_INDEX_NEW) => Green.paint("A"),
s if s.contains(git2::STATUS_INDEX_MODIFIED) => Blue.paint("M"),
s if s.contains(git2::STATUS_INDEX_DELETED) => Red.paint("D"),
s if s.contains(git2::STATUS_INDEX_RENAMED) => Yellow.paint("R"),
s if s.contains(git2::STATUS_INDEX_TYPECHANGE) => Purple.paint("T"),
_ => GREY.paint("-"),
}
}
}

62
src/feature/mod.rs Normal file
View File

@ -0,0 +1,62 @@
// 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")))] use std::old_io as io;
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
#[derive(Clone)]
pub struct Attribute;
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
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 `stat`
pub fn list(_: &Path) -> io::IoResult<Vec<Attribute>> {
Ok(Vec::new())
}
/// Lists the extended attributes. Does not follow symlinks like `lstat`
pub fn llist(_: &Path) -> io::IoResult<Vec<Attribute>> {
Ok(Vec::new())
}
pub fn feature_implemented() -> bool { false }
}
// Git support
#[cfg(feature="git")] mod git;
#[cfg(feature="git")] pub use self::git::Git;
#[cfg(not(feature="git"))] pub struct Git;
#[cfg(not(feature="git"))] use std::old_path::posix::Path;
#[cfg(not(feature="git"))]
impl Git {
pub fn scan(_: &Path) -> Result<Git, ()> {
Err(())
}
pub fn status(&self, _: &Path) -> String {
panic!("Tried to access a Git repo without Git support!");
}
pub fn dir_status(&self, path: &Path) -> String {
self.status(path)
}
}

View File

@ -41,7 +41,7 @@ pub struct Attribute {
impl Attribute {
/// Lists the extended attribute of `path`.
/// Does follow symlinks by default.
pub fn list(path: &Path, flags: &[ListFlags]) -> io::IoResult<Vec<Attribute>> {
pub fn list_attrs(path: &Path, flags: &[ListFlags]) -> io::IoResult<Vec<Attribute>> {
let mut c_flags: c_int = 0;
for &flag in flags.iter() {
c_flags |= flag as c_int
@ -112,19 +112,20 @@ impl Attribute {
pub fn size(&self) -> usize {
self.size
}
/// Lists the extended attributes.
/// Follows symlinks like `stat`
pub fn list(path: &Path) -> io::IoResult<Vec<Attribute>> {
Attribute::list_attrs(path, &[])
}
/// Lists the extended attributes.
/// Does not follow symlinks like `lstat`
pub fn llist(path: &Path) -> io::IoResult<Vec<Attribute>> {
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 }
}
/// Lists the extended attributes.
/// Follows symlinks like `stat`
pub fn list(path: &Path) -> io::IoResult<Vec<Attribute>> {
Attribute::list(path, &[])
}
/// Lists the extended attributes.
/// Does not follow symlinks like `lstat`
pub fn llist(path: &Path) -> io::IoResult<Vec<Attribute>> {
Attribute::list(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

@ -36,7 +36,7 @@ pub struct Attribute {
impl Attribute {
/// Lists the extended attribute of `path`.
/// Does follow symlinks by default.
pub fn list(path: &Path, do_follow: FollowSymlinks) -> io::IoResult<Vec<Attribute>> {
pub fn list_attrs(path: &Path, do_follow: FollowSymlinks) -> io::IoResult<Vec<Attribute>> {
let (listxattr, getxattr) = match do_follow {
FollowSymlinks::Yes => (listxattr, getxattr),
FollowSymlinks::No => (llistxattr, lgetxattr),
@ -103,19 +103,19 @@ impl Attribute {
pub fn size(&self) -> usize {
self.size
}
}
/// Lists the extended attributes.
/// Follows symlinks like `stat`
pub fn list(path: &Path) -> io::IoResult<Vec<Attribute>> {
Attribute::list(path, FollowSymlinks::Yes)
}
/// Lists the extended attributes.
/// Does not follow symlinks like `lstat`
pub fn llist(path: &Path) -> io::IoResult<Vec<Attribute>> {
Attribute::list(path, FollowSymlinks::No)
}
/// Lists the extended attributes.
/// Follows symlinks like `stat`
pub fn list(path: &Path) -> io::IoResult<Vec<Attribute>> {
Attribute::list_attrs(path, FollowSymlinks::Yes)
}
/// Lists the extended attributes.
/// Does not follow symlinks like `lstat`
pub fn llist(path: &Path) -> io::IoResult<Vec<Attribute>> {
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 }
/// Returns true if the extended attribute feature is implemented on this platform.
#[inline(always)]
pub fn feature_implemented() -> bool { true }
}

View File

@ -31,11 +31,10 @@ use column::Column::*;
use dir::Dir;
use filetype::HasType;
use options::{SizeFormat, TimeType};
use xattr;
use xattr::Attribute;
use feature::Attribute;
/// This grey value is directly in between white and black, so it's guaranteed
/// to show up on either backgrounded terminal.
/// to show up on either backg"#160909"rounded terminal.
pub static GREY: Colour = Fixed(244);
/// A **File** is a wrapper around one of Rust's Path objects, along with
@ -83,7 +82,7 @@ impl<'a> File<'a> {
dir: parent,
stat: stat,
ext: ext(&filename),
xattrs: xattr::llist(path).unwrap_or(Vec::new()),
xattrs: Attribute::llist(path).unwrap_or(Vec::new()),
name: filename.to_string(),
this: this,
}
@ -227,7 +226,7 @@ impl<'a> File<'a> {
dir: self.dir,
stat: stat,
ext: ext(&filename),
xattrs: xattr::list(target_path).unwrap_or(Vec::new()),
xattrs: Attribute::list(target_path).unwrap_or(Vec::new()),
name: filename.to_string(),
this: None,
})

View File

@ -33,12 +33,12 @@ use output::lines_view;
pub mod column;
pub mod dir;
pub mod feature;
pub mod file;
pub mod filetype;
pub mod options;
pub mod output;
pub mod term;
pub mod xattr;
#[cfg(not(test))]
struct Exa<'a> {

View File

@ -2,9 +2,9 @@ use dir::Dir;
use file::File;
use column::Column;
use column::Column::*;
use feature::Attribute;
use output::{Grid, Details};
use term::dimensions;
use xattr;
use std::cmp::Ordering;
use std::fmt;
@ -76,7 +76,7 @@ impl Options {
opts.optflag("", "git", "show git status");
}
if xattr::feature_implemented() {
if Attribute::feature_implemented() {
opts.optflag("@", "extended", "display extended attribute keys and sizes in long (-l) output");
}
@ -255,7 +255,7 @@ impl View {
columns: try!(Columns::deduce(matches)),
header: matches.opt_present("header"),
recurse: dir_action.recurse_options().map(|o| (o, filter)),
xattr: xattr::feature_implemented() && matches.opt_present("extended"),
xattr: Attribute::feature_implemented() && matches.opt_present("extended"),
};
Ok(View::Details(details))
@ -294,7 +294,7 @@ impl View {
else if matches.opt_present("level") && !matches.opt_present("recurse") {
Err(Misfire::Useless2("level", "recurse", "tree"))
}
else if xattr::feature_implemented() && matches.opt_present("extended") {
else if Attribute::feature_implemented() && matches.opt_present("extended") {
Err(Misfire::Useless("extended", false, "long"))
}
else if matches.opt_present("oneline") {
@ -572,7 +572,7 @@ mod test {
use super::Options;
use super::Misfire;
use super::Misfire::*;
use xattr;
use feature::Attribute;
fn is_helpful<T>(misfire: Result<T, Misfire>) -> bool {
match misfire {
@ -674,7 +674,7 @@ mod test {
#[test]
fn extended_without_long() {
if xattr::feature_implemented() {
if Attribute::feature_implemented() {
let opts = Options::getopts(&[ "--extended".to_string() ]);
assert_eq!(opts.unwrap_err(), Misfire::Useless("extended", false, "long"))
}

View File

@ -1,5 +1,5 @@
use column::{Alignment, Column, Cell};
use xattr::Attribute;
use feature::Attribute;
use dir::Dir;
use file::{File, GREY};
use options::{Columns, FileFilter, RecurseOptions};

View File

@ -1,13 +0,0 @@
//! Extended attribute support
#[cfg(target_os = "macos")]
mod xattr_darwin;
#[cfg(target_os = "macos")]
pub use self::xattr_darwin::*;
#[cfg(target_os = "linux")]
mod xattr_linux;
#[cfg(target_os = "linux")]
pub use self::xattr_linux::*;
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
mod xattr_other;
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
pub use self::xattr_other::*;

View File

@ -1,32 +0,0 @@
//! Extended attribute support for other os
use std::old_io as io;
/// Extended attribute
#[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 `stat`
pub fn list(_: &Path) -> io::IoResult<Vec<Attribute>> {
Ok(Vec::new())
}
/// Lists the extended attributes. Does not follow symlinks like `lstat`
pub fn llist(_: &Path) -> io::IoResult<Vec<Attribute>> {
Ok(Vec::new())
}
/// Returns true if the extended attribute feature is implemented on this platform.
#[inline(always)]
pub fn feature_implemented() -> bool { false }