Merge branch 'ls-colors-finally'

This commit is contained in:
Benjamin Sago 2017-09-13 10:12:08 +01:00
commit aa2e3a5d9e
15 changed files with 713 additions and 380 deletions

56
Cargo.lock generated
View File

@ -6,10 +6,10 @@ dependencies = [
"datetime 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
"git2 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
"git2 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)",
"glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)",
"locale 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"natord 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
@ -53,10 +53,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cmake"
version = "0.1.24"
version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)",
"gcc 0.3.53 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -78,7 +78,7 @@ version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"iso8601 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)",
"locale 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"num 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"pad 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
@ -95,7 +95,7 @@ dependencies = [
[[package]]
name = "gcc"
version = "0.3.51"
version = "0.3.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@ -105,12 +105,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "git2"
version = "0.6.6"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)",
"libgit2-sys 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)",
"libgit2-sys 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)",
"url 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -153,17 +153,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libc"
version = "0.2.29"
version = "0.2.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libgit2-sys"
version = "0.6.13"
version = "0.6.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cmake 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)",
"gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)",
"cmake 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
"gcc 0.3.53 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)",
"libz-sys 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -173,8 +173,8 @@ name = "libz-sys"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)",
"gcc 0.3.53 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -184,7 +184,7 @@ name = "locale"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -219,7 +219,7 @@ name = "memchr"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -303,7 +303,7 @@ name = "num_cpus"
version = "1.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -337,7 +337,7 @@ name = "rand"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)",
"magenta 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -382,7 +382,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -392,7 +392,7 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -436,7 +436,7 @@ name = "users"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -474,21 +474,21 @@ dependencies = [
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5"
"checksum byteorder 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "96c8b41881888cc08af32d47ac4edd52bc7fa27fef774be47a92443756451304"
"checksum cmake 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)" = "b8ebbb35d3dc9cd09497168f33de1acb79b265d350ab0ac34133b98f8509af1f"
"checksum cmake 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)" = "0c8a6541a55bcd72d3de4faee2d101a5a66df29790282c7f797082a7228a9b3d"
"checksum conv 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299"
"checksum custom_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9"
"checksum datetime 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "2d425bf1f6bbd57cf833081c1e60ac294fd74e7edd66acc91c3fca2e496bcee9"
"checksum env_logger 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "15abd780e45b3ea4f76b4e9a26ff4843258dd8a3eed2775a0e7368c2e7936c2f"
"checksum gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)" = "120d07f202dcc3f72859422563522b66fe6463a4c513df062874daad05f85f0a"
"checksum gcc 0.3.53 (registry+https://github.com/rust-lang/crates.io-index)" = "e8310f7e9c890398b0e80e301c4f474e9918d2b27fca8f48486ca775fa9ffc5a"
"checksum getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9047cfbd08a437050b363d35ef160452c5fe8ea5187ae0a624708c91581d685"
"checksum git2 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "aa01936ac96555c083c0e8553f672616274408d9d3fc5b8696603fbf63ff43ee"
"checksum git2 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "0c1c0203d653f4140241da0c1375a404f0a397249ec818cd2076c6280c50f6fa"
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
"checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d"
"checksum iso8601 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "11dc464f8c6f17595d191447c9c6559298b2d023d6f846a4a23ac7ea3c46c477"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3b37545ab726dd833ec6420aaba8231c5b320814b9029ad585555d2a03e94fbf"
"checksum libc 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)" = "8a014d9226c2cc402676fbe9ea2e15dd5222cd1dd57f576b5b283178c944a264"
"checksum libgit2-sys 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "0f1641ccb55181967a3e5ee4ae2911c0563492f016383ea67a27886181de088c"
"checksum libc 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)" = "2370ca07ec338939e356443dac2296f581453c35fe1e3a3ed06023c49435f915"
"checksum libgit2-sys 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)" = "c00f6e5bc3fb2b5f87e75e8d0fd4ae6720d55f3ee23d389b7c6cae30f8db8db1"
"checksum libz-sys 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)" = "3fdd64ef8ee652185674455c1d450b83cbc8ad895625d543b5324d923f82e4d8"
"checksum locale 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5fdbe492a9c0238da900a1165c42fc5067161ce292678a6fe80921f30fe307fd"
"checksum log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "880f77541efa6e5cc74e76910c9884d9859683118839d6a1dc3b11e63512565b"

View File

@ -1,287 +0,0 @@
use style::Colours;
use options::{flags, Vars, Misfire};
use options::parser::MatchedFlags;
/// Under what circumstances we should display coloured, rather than plain,
/// output to the terminal.
///
/// By default, we want to display the colours when stdout can display them.
/// Turning them on when output is going to, say, a pipe, would make programs
/// such as `grep` or `more` not work properly. So the `Automatic` mode does
/// this check and only displays colours when they can be truly appreciated.
#[derive(PartialEq, Debug)]
enum TerminalColours {
/// Display them even when output isnt going to a terminal.
Always,
/// Display them when output is going to a terminal, but not otherwise.
Automatic,
/// Never display them, even when output is going to a terminal.
Never,
}
impl Default for TerminalColours {
fn default() -> TerminalColours {
TerminalColours::Automatic
}
}
const COLOURS: &[&str] = &["always", "auto", "never"];
impl TerminalColours {
/// Determine which terminal colour conditions to use.
fn deduce(matches: &MatchedFlags) -> Result<TerminalColours, Misfire> {
let word = match matches.get_where(|f| f.matches(&flags::COLOR) || f.matches(&flags::COLOUR))? {
Some(w) => w,
None => return Ok(TerminalColours::default()),
};
if word == "always" {
Ok(TerminalColours::Always)
}
else if word == "auto" || word == "automatic" {
Ok(TerminalColours::Automatic)
}
else if word == "never" {
Ok(TerminalColours::Never)
}
else {
Err(Misfire::bad_argument(&flags::COLOR, word, COLOURS))
}
}
}
impl Colours {
pub fn deduce<V, TW>(matches: &MatchedFlags, vars: &V, widther: TW) -> Result<Colours, Misfire>
where TW: Fn() -> Option<usize>, V: Vars {
use self::TerminalColours::*;
use style::LSColors;
use options::vars;
let tc = TerminalColours::deduce(matches)?;
if tc == Never || (tc == Automatic && widther().is_none()) {
return Ok(Colours::plain());
}
let scale = matches.has_where(|f| f.matches(&flags::COLOR_SCALE) || f.matches(&flags::COLOUR_SCALE))?;
let mut colours = Colours::colourful(scale.is_some());
if let Some(lsc) = vars.get(vars::LS_COLORS) {
let lsc = lsc.to_string_lossy();
LSColors(lsc.as_ref()).each_pair(|pair| colours.set_ls(&pair));
}
if let Some(exa) = vars.get(vars::EXA_COLORS) {
let exa = exa.to_string_lossy();
LSColors(exa.as_ref()).each_pair(|pair| colours.set_exa(&pair));
}
Ok(colours)
}
}
#[cfg(test)]
mod terminal_test {
use super::*;
use std::ffi::OsString;
use options::flags;
use options::parser::{Flag, Arg};
use options::test::parse_for_test;
use options::test::Strictnesses::*;
pub fn os(input: &'static str) -> OsString {
let mut os = OsString::new();
os.push(input);
os
}
static TEST_ARGS: &[&Arg] = &[ &flags::COLOR, &flags::COLOUR ];
macro_rules! test {
($name:ident: $inputs:expr; $stricts:expr => $result:expr) => {
#[test]
fn $name() {
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| TerminalColours::deduce(mf)) {
assert_eq!(result, $result);
}
}
};
($name:ident: $inputs:expr; $stricts:expr => err $result:expr) => {
#[test]
fn $name() {
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| TerminalColours::deduce(mf)) {
assert_eq!(result.unwrap_err(), $result);
}
}
};
}
// Default
test!(empty: []; Both => Ok(TerminalColours::default()));
// --colour
test!(u_always: ["--colour=always"]; Both => Ok(TerminalColours::Always));
test!(u_auto: ["--colour", "auto"]; Both => Ok(TerminalColours::Automatic));
test!(u_never: ["--colour=never"]; Both => Ok(TerminalColours::Never));
// --color
test!(no_u_always: ["--color", "always"]; Both => Ok(TerminalColours::Always));
test!(no_u_auto: ["--color=auto"]; Both => Ok(TerminalColours::Automatic));
test!(no_u_never: ["--color", "never"]; Both => Ok(TerminalColours::Never));
// Errors
test!(no_u_error: ["--color=upstream"]; Both => err Misfire::bad_argument(&flags::COLOR, &os("upstream"), super::COLOURS)); // the error is for --color
test!(u_error: ["--colour=lovers"]; Both => err Misfire::bad_argument(&flags::COLOR, &os("lovers"), super::COLOURS)); // and so is this one!
// Overriding
test!(overridden_1: ["--colour=auto", "--colour=never"]; Last => Ok(TerminalColours::Never));
test!(overridden_2: ["--color=auto", "--colour=never"]; Last => Ok(TerminalColours::Never));
test!(overridden_3: ["--colour=auto", "--color=never"]; Last => Ok(TerminalColours::Never));
test!(overridden_4: ["--color=auto", "--color=never"]; Last => Ok(TerminalColours::Never));
test!(overridden_5: ["--colour=auto", "--colour=never"]; Complain => err Misfire::Duplicate(Flag::Long("colour"), Flag::Long("colour")));
test!(overridden_6: ["--color=auto", "--colour=never"]; Complain => err Misfire::Duplicate(Flag::Long("color"), Flag::Long("colour")));
test!(overridden_7: ["--colour=auto", "--color=never"]; Complain => err Misfire::Duplicate(Flag::Long("colour"), Flag::Long("color")));
test!(overridden_8: ["--color=auto", "--color=never"]; Complain => err Misfire::Duplicate(Flag::Long("color"), Flag::Long("color")));
}
#[cfg(test)]
mod colour_test {
use super::*;
use options::flags;
use options::parser::{Flag, Arg};
use options::test::parse_for_test;
use options::test::Strictnesses::*;
static TEST_ARGS: &[&Arg] = &[ &flags::COLOR, &flags::COLOUR,
&flags::COLOR_SCALE, &flags::COLOUR_SCALE ];
macro_rules! test {
($name:ident: $inputs:expr, $widther:expr; $stricts:expr => $result:expr) => {
#[test]
fn $name() {
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| Colours::deduce(mf, &None, &$widther)) {
assert_eq!(result, $result);
}
}
};
($name:ident: $inputs:expr, $widther:expr; $stricts:expr => err $result:expr) => {
#[test]
fn $name() {
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| Colours::deduce(mf, &None, &$widther)) {
assert_eq!(result.unwrap_err(), $result);
}
}
};
($name:ident: $inputs:expr, $widther:expr; $stricts:expr => like $pat:pat) => {
#[test]
fn $name() {
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| Colours::deduce(mf, &None, &$widther)) {
println!("Testing {:?}", result);
match result {
$pat => assert!(true),
_ => assert!(false),
}
}
}
};
}
test!(width_1: ["--colour", "always"], || Some(80); Both => Ok(Colours::colourful(false)));
test!(width_2: ["--colour", "always"], || None; Both => Ok(Colours::colourful(false)));
test!(width_3: ["--colour", "never"], || Some(80); Both => Ok(Colours::plain()));
test!(width_4: ["--colour", "never"], || None; Both => Ok(Colours::plain()));
test!(width_5: ["--colour", "automatic"], || Some(80); Both => Ok(Colours::colourful(false)));
test!(width_6: ["--colour", "automatic"], || None; Both => Ok(Colours::plain()));
test!(width_7: [], || Some(80); Both => Ok(Colours::colourful(false)));
test!(width_8: [], || None; Both => Ok(Colours::plain()));
test!(scale_1: ["--color=always", "--color-scale", "--colour-scale"], || None; Last => like Ok(Colours { scale: true, .. }));
test!(scale_2: ["--color=always", "--color-scale", ], || None; Last => like Ok(Colours { scale: true, .. }));
test!(scale_3: ["--color=always", "--colour-scale"], || None; Last => like Ok(Colours { scale: true, .. }));
test!(scale_4: ["--color=always", ], || None; Last => like Ok(Colours { scale: false, .. }));
test!(scale_5: ["--color=always", "--color-scale", "--colour-scale"], || None; Complain => err Misfire::Duplicate(Flag::Long("color-scale"), Flag::Long("colour-scale")));
test!(scale_6: ["--color=always", "--color-scale", ], || None; Complain => like Ok(Colours { scale: true, .. }));
test!(scale_7: ["--color=always", "--colour-scale"], || None; Complain => like Ok(Colours { scale: true, .. }));
test!(scale_8: ["--color=always", ], || None; Complain => like Ok(Colours { scale: false, .. }));
}
#[cfg(test)]
mod customs_test {
use std::ffi::OsString;
use super::*;
use options::Vars;
use options::test::parse_for_test;
use options::test::Strictnesses::Both;
use ansi_term::Colour::*;
macro_rules! test {
($name:ident: ls $ls:expr, exa $exa:expr => $resulter:expr) => {
#[test]
fn $name() {
let mut c = Colours::colourful(false);
$resulter(&mut c);
let vars = MockVars { ls: $ls, exa: $exa };
for result in parse_for_test(&[], &[], Both, |mf| Colours::deduce(mf, &vars, || Some(80))) {
assert_eq!(result.as_ref(), Ok(&c));
}
}
};
}
struct MockVars {
ls: &'static str,
exa: &'static str,
}
// Test impl that just returns the value it has.
impl Vars for MockVars {
fn get(&self, name: &'static str) -> Option<OsString> {
use options::vars;
if name == vars::LS_COLORS && !self.ls.is_empty() {
OsString::from(self.ls.clone()).into()
}
else if name == vars::EXA_COLORS && !self.exa.is_empty() {
OsString::from(self.exa.clone()).into()
}
else {
None
}
}
}
test!(ls_di: ls "di=31", exa "" => |c: &mut Colours| { c.filekinds.directory = Red.normal(); }); // Directory
test!(ls_ex: ls "ex=32", exa "" => |c: &mut Colours| { c.filekinds.executable = Green.normal(); }); // Executable file
test!(ls_fi: ls "fi=33", exa "" => |c: &mut Colours| { c.filekinds.normal = Yellow.normal(); }); // Regular file
test!(ls_pi: ls "pi=34", exa "" => |c: &mut Colours| { c.filekinds.pipe = Blue.normal(); }); // FIFO
test!(ls_so: ls "so=35", exa "" => |c: &mut Colours| { c.filekinds.socket = Purple.normal(); }); // Socket
test!(ls_bd: ls "bd=36", exa "" => |c: &mut Colours| { c.filekinds.block_device = Cyan.normal(); }); // Block device
test!(ls_cd: ls "cd=35", exa "" => |c: &mut Colours| { c.filekinds.char_device = Purple.normal(); }); // Character device
test!(ls_ln: ls "ln=34", exa "" => |c: &mut Colours| { c.filekinds.symlink = Blue.normal(); }); // Symlink
test!(ls_or: ls "or=33", exa "" => |c: &mut Colours| { c.broken_arrow = Yellow.normal(); }); // Broken link
test!(ls_mi: ls "mi=32", exa "" => |c: &mut Colours| { c.broken_filename = Green.normal(); }); // Broken link target
}

View File

@ -64,7 +64,7 @@
//! if no aliases are being used!
//!
//! Finally, this isnt just useful when options could override each other.
//! Creating an alias `exal=”exa --long --inode --header”` then invoking `exal
//! Creating an alias `exal="exa --long --inode --header"` then invoking `exal
//! --grid --long` shouldnt complain about `--long` being given twice when
//! its clear what the user wants.
@ -75,7 +75,7 @@ use fs::dir_action::DirAction;
use fs::filter::FileFilter;
use output::{View, Mode, details, grid_details};
mod colours;
mod style;
mod dir_action;
mod filter;
mod view;

528
src/options/style.rs Normal file
View File

@ -0,0 +1,528 @@
use ansi_term::Style;
use glob;
use fs::File;
use options::{flags, Vars, Misfire};
use options::parser::MatchedFlags;
use output::file_name::{FileStyle, Classify};
use style::Colours;
/// Under what circumstances we should display coloured, rather than plain,
/// output to the terminal.
///
/// By default, we want to display the colours when stdout can display them.
/// Turning them on when output is going to, say, a pipe, would make programs
/// such as `grep` or `more` not work properly. So the `Automatic` mode does
/// this check and only displays colours when they can be truly appreciated.
#[derive(PartialEq, Debug)]
enum TerminalColours {
/// Display them even when output isnt going to a terminal.
Always,
/// Display them when output is going to a terminal, but not otherwise.
Automatic,
/// Never display them, even when output is going to a terminal.
Never,
}
impl Default for TerminalColours {
fn default() -> TerminalColours {
TerminalColours::Automatic
}
}
const COLOURS: &[&str] = &["always", "auto", "never"];
impl TerminalColours {
/// Determine which terminal colour conditions to use.
fn deduce(matches: &MatchedFlags) -> Result<TerminalColours, Misfire> {
let word = match matches.get_where(|f| f.matches(&flags::COLOR) || f.matches(&flags::COLOUR))? {
Some(w) => w,
None => return Ok(TerminalColours::default()),
};
if word == "always" {
Ok(TerminalColours::Always)
}
else if word == "auto" || word == "automatic" {
Ok(TerminalColours::Automatic)
}
else if word == "never" {
Ok(TerminalColours::Never)
}
else {
Err(Misfire::bad_argument(&flags::COLOR, word, COLOURS))
}
}
}
/// **Styles**, which is already an overloaded term, is a pair of view option
/// sets that happen to both be affected by `LS_COLORS` and `EXA_COLORS`.
/// Because its better to only iterate through that once, the two are deduced
/// together.
pub struct Styles {
/// The colours to paint user interface elements, like the date column,
/// and file kinds, such as directories.
pub colours: Colours,
/// The colours to paint the names of files that match glob patterns
/// (and the classify option).
pub style: FileStyle,
}
impl Styles {
#[allow(trivial_casts)] // the "as Box<_>" stuff below warns about this for some reason
pub fn deduce<V, TW>(matches: &MatchedFlags, vars: &V, widther: TW) -> Result<Self, Misfire>
where TW: Fn() -> Option<usize>, V: Vars {
use self::TerminalColours::*;
use info::filetype::FileExtensions;
use output::file_name::NoFileColours;
let classify = Classify::deduce(matches)?;
// Before we do anything else, figure out if we need to consider
// custom colours at all
let tc = TerminalColours::deduce(matches)?;
if tc == Never || (tc == Automatic && widther().is_none()) {
return Ok(Styles {
colours: Colours::plain(),
style: FileStyle { classify, exts: Box::new(NoFileColours) },
});
}
// Parse the environment variables into colours and extension mappings
let scale = matches.has_where(|f| f.matches(&flags::COLOR_SCALE) || f.matches(&flags::COLOUR_SCALE))?;
let mut colours = Colours::colourful(scale.is_some());
let (exts, use_default_filetypes) = parse_color_vars(vars, &mut colours);
// Use between 0 and 2 file name highlighters
let exts = match (exts.is_non_empty(), use_default_filetypes) {
(false, false) => Box::new(NoFileColours) as Box<_>,
(false, true) => Box::new(FileExtensions) as Box<_>,
( true, false) => Box::new(exts) as Box<_>,
( true, true) => Box::new((exts, FileExtensions)) as Box<_>,
};
let style = FileStyle { classify, exts };
Ok(Styles { colours, style })
}
}
/// Parse the environment variables into LS_COLORS pairs, putting file glob
/// colours into the `ExtensionMappings` that gets returned, and using the
/// two-character UI codes to modify the mutable `Colours`.
///
/// Also returns if the EXA_COLORS variable should reset the existing file
/// type mappings or not. The `reset` code needs to be the first one.
fn parse_color_vars<V: Vars>(vars: &V, colours: &mut Colours) -> (ExtensionMappings, bool) {
use options::vars;
use style::LSColors;
let mut exts = ExtensionMappings::default();
if let Some(lsc) = vars.get(vars::LS_COLORS) {
let lsc = lsc.to_string_lossy();
LSColors(lsc.as_ref()).each_pair(|pair| {
if !colours.set_ls(&pair) {
match glob::Pattern::new(pair.key) {
Ok(pat) => exts.add(pat, pair.to_style()),
Err(e) => warn!("Couldn't parse glob pattern {:?}: {}", pair.key, e),
}
}
});
}
let mut use_default_filetypes = true;
if let Some(exa) = vars.get(vars::EXA_COLORS) {
let exa = exa.to_string_lossy();
// Is this hacky? Yes.
if exa == "reset" || exa.starts_with("reset:") {
use_default_filetypes = false;
}
LSColors(exa.as_ref()).each_pair(|pair| {
if !colours.set_ls(&pair) && !colours.set_exa(&pair) {
match glob::Pattern::new(pair.key) {
Ok(pat) => exts.add(pat, pair.to_style()),
Err(e) => warn!("Couldn't parse glob pattern {:?}: {}", pair.key, e),
}
};
});
}
(exts, use_default_filetypes)
}
#[derive(PartialEq, Debug, Default)]
struct ExtensionMappings {
mappings: Vec<(glob::Pattern, Style)>
}
// Loop through backwards so that colours specified later in the list override
// colours specified earlier, like we do with options and strict mode
use output::file_name::FileColours;
impl FileColours for ExtensionMappings {
fn colour_file(&self, file: &File) -> Option<Style> {
self.mappings
.iter()
.rev()
.find(|t| t.0.matches(&file.name))
.map (|t| t.1)
}
}
impl ExtensionMappings {
fn is_non_empty(&self) -> bool {
!self.mappings.is_empty()
}
fn add(&mut self, pattern: glob::Pattern, style: Style) {
self.mappings.push((pattern, style))
}
}
impl Classify {
fn deduce(matches: &MatchedFlags) -> Result<Classify, Misfire> {
let flagged = matches.has(&flags::CLASSIFY)?;
Ok(if flagged { Classify::AddFileIndicators }
else { Classify::JustFilenames })
}
}
#[cfg(test)]
mod terminal_test {
use super::*;
use std::ffi::OsString;
use options::flags;
use options::parser::{Flag, Arg};
use options::test::parse_for_test;
use options::test::Strictnesses::*;
pub fn os(input: &'static str) -> OsString {
let mut os = OsString::new();
os.push(input);
os
}
static TEST_ARGS: &[&Arg] = &[ &flags::COLOR, &flags::COLOUR ];
macro_rules! test {
($name:ident: $inputs:expr; $stricts:expr => $result:expr) => {
#[test]
fn $name() {
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| TerminalColours::deduce(mf)) {
assert_eq!(result, $result);
}
}
};
($name:ident: $inputs:expr; $stricts:expr => err $result:expr) => {
#[test]
fn $name() {
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| TerminalColours::deduce(mf)) {
assert_eq!(result.unwrap_err(), $result);
}
}
};
}
// Default
test!(empty: []; Both => Ok(TerminalColours::default()));
// --colour
test!(u_always: ["--colour=always"]; Both => Ok(TerminalColours::Always));
test!(u_auto: ["--colour", "auto"]; Both => Ok(TerminalColours::Automatic));
test!(u_never: ["--colour=never"]; Both => Ok(TerminalColours::Never));
// --color
test!(no_u_always: ["--color", "always"]; Both => Ok(TerminalColours::Always));
test!(no_u_auto: ["--color=auto"]; Both => Ok(TerminalColours::Automatic));
test!(no_u_never: ["--color", "never"]; Both => Ok(TerminalColours::Never));
// Errors
test!(no_u_error: ["--color=upstream"]; Both => err Misfire::bad_argument(&flags::COLOR, &os("upstream"), super::COLOURS)); // the error is for --color
test!(u_error: ["--colour=lovers"]; Both => err Misfire::bad_argument(&flags::COLOR, &os("lovers"), super::COLOURS)); // and so is this one!
// Overriding
test!(overridden_1: ["--colour=auto", "--colour=never"]; Last => Ok(TerminalColours::Never));
test!(overridden_2: ["--color=auto", "--colour=never"]; Last => Ok(TerminalColours::Never));
test!(overridden_3: ["--colour=auto", "--color=never"]; Last => Ok(TerminalColours::Never));
test!(overridden_4: ["--color=auto", "--color=never"]; Last => Ok(TerminalColours::Never));
test!(overridden_5: ["--colour=auto", "--colour=never"]; Complain => err Misfire::Duplicate(Flag::Long("colour"), Flag::Long("colour")));
test!(overridden_6: ["--color=auto", "--colour=never"]; Complain => err Misfire::Duplicate(Flag::Long("color"), Flag::Long("colour")));
test!(overridden_7: ["--colour=auto", "--color=never"]; Complain => err Misfire::Duplicate(Flag::Long("colour"), Flag::Long("color")));
test!(overridden_8: ["--color=auto", "--color=never"]; Complain => err Misfire::Duplicate(Flag::Long("color"), Flag::Long("color")));
}
#[cfg(test)]
mod colour_test {
use super::*;
use options::flags;
use options::parser::{Flag, Arg};
use options::test::parse_for_test;
use options::test::Strictnesses::*;
static TEST_ARGS: &[&Arg] = &[ &flags::COLOR, &flags::COLOUR,
&flags::COLOR_SCALE, &flags::COLOUR_SCALE ];
macro_rules! test {
($name:ident: $inputs:expr, $widther:expr; $stricts:expr => $result:expr) => {
#[test]
fn $name() {
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| Styles::deduce(mf, &None, &$widther).map(|s| s.colours)) {
assert_eq!(result, $result);
}
}
};
($name:ident: $inputs:expr, $widther:expr; $stricts:expr => err $result:expr) => {
#[test]
fn $name() {
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| Styles::deduce(mf, &None, &$widther).map(|s| s.colours)) {
assert_eq!(result.unwrap_err(), $result);
}
}
};
($name:ident: $inputs:expr, $widther:expr; $stricts:expr => like $pat:pat) => {
#[test]
fn $name() {
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| Styles::deduce(mf, &None, &$widther).map(|s| s.colours)) {
println!("Testing {:?}", result);
match result {
$pat => assert!(true),
_ => assert!(false),
}
}
}
};
}
test!(width_1: ["--colour", "always"], || Some(80); Both => Ok(Colours::colourful(false)));
test!(width_2: ["--colour", "always"], || None; Both => Ok(Colours::colourful(false)));
test!(width_3: ["--colour", "never"], || Some(80); Both => Ok(Colours::plain()));
test!(width_4: ["--colour", "never"], || None; Both => Ok(Colours::plain()));
test!(width_5: ["--colour", "automatic"], || Some(80); Both => Ok(Colours::colourful(false)));
test!(width_6: ["--colour", "automatic"], || None; Both => Ok(Colours::plain()));
test!(width_7: [], || Some(80); Both => Ok(Colours::colourful(false)));
test!(width_8: [], || None; Both => Ok(Colours::plain()));
test!(scale_1: ["--color=always", "--color-scale", "--colour-scale"], || None; Last => like Ok(Colours { scale: true, .. }));
test!(scale_2: ["--color=always", "--color-scale", ], || None; Last => like Ok(Colours { scale: true, .. }));
test!(scale_3: ["--color=always", "--colour-scale"], || None; Last => like Ok(Colours { scale: true, .. }));
test!(scale_4: ["--color=always", ], || None; Last => like Ok(Colours { scale: false, .. }));
test!(scale_5: ["--color=always", "--color-scale", "--colour-scale"], || None; Complain => err Misfire::Duplicate(Flag::Long("color-scale"), Flag::Long("colour-scale")));
test!(scale_6: ["--color=always", "--color-scale", ], || None; Complain => like Ok(Colours { scale: true, .. }));
test!(scale_7: ["--color=always", "--colour-scale"], || None; Complain => like Ok(Colours { scale: true, .. }));
test!(scale_8: ["--color=always", ], || None; Complain => like Ok(Colours { scale: false, .. }));
}
#[cfg(test)]
mod customs_test {
use std::ffi::OsString;
use super::*;
use options::Vars;
use ansi_term::Colour::*;
macro_rules! test {
($name:ident: ls $ls:expr, exa $exa:expr => colours $expected:ident -> $process_expected:expr) => {
#[test]
#[allow(unused_mut)]
fn $name() {
let mut $expected = Colours::colourful(false);
$process_expected();
let vars = MockVars { ls: $ls, exa: $exa };
let mut result = Colours::colourful(false);
let (_exts, _reset) = parse_color_vars(&vars, &mut result);
assert_eq!($expected, result);
}
};
($name:ident: ls $ls:expr, exa $exa:expr => exts $mappings:expr) => {
#[test]
fn $name() {
let mappings: Vec<(glob::Pattern, Style)>
= $mappings.into_iter()
.map(|t| (glob::Pattern::new(t.0).unwrap(), t.1))
.collect();
let vars = MockVars { ls: $ls, exa: $exa };
let mut meh = Colours::colourful(false);
let (result, _reset) = parse_color_vars(&vars, &mut meh);
assert_eq!(ExtensionMappings { mappings }, result);
}
};
($name:ident: ls $ls:expr, exa $exa:expr => colours $expected:ident -> $process_expected:expr, exts $mappings:expr) => {
#[test]
#[allow(unused_mut)]
fn $name() {
let mut $expected = Colours::colourful(false);
$process_expected();
let mappings: Vec<(glob::Pattern, Style)>
= $mappings.into_iter()
.map(|t| (glob::Pattern::new(t.0).unwrap(), t.1))
.collect();
let vars = MockVars { ls: $ls, exa: $exa };
let mut meh = Colours::colourful(false);
let (result, _reset) = parse_color_vars(&vars, &mut meh);
assert_eq!(ExtensionMappings { mappings }, result);
assert_eq!($expected, meh);
}
};
}
struct MockVars {
ls: &'static str,
exa: &'static str,
}
// Test impl that just returns the value it has.
impl Vars for MockVars {
fn get(&self, name: &'static str) -> Option<OsString> {
use options::vars;
if name == vars::LS_COLORS && !self.ls.is_empty() {
OsString::from(self.ls.clone()).into()
}
else if name == vars::EXA_COLORS && !self.exa.is_empty() {
OsString::from(self.exa.clone()).into()
}
else {
None
}
}
}
// LS_COLORS can affect all of these colours:
test!(ls_di: ls "di=31", exa "" => colours c -> { c.filekinds.directory = Red.normal(); });
test!(ls_ex: ls "ex=32", exa "" => colours c -> { c.filekinds.executable = Green.normal(); });
test!(ls_fi: ls "fi=33", exa "" => colours c -> { c.filekinds.normal = Yellow.normal(); });
test!(ls_pi: ls "pi=34", exa "" => colours c -> { c.filekinds.pipe = Blue.normal(); });
test!(ls_so: ls "so=35", exa "" => colours c -> { c.filekinds.socket = Purple.normal(); });
test!(ls_bd: ls "bd=36", exa "" => colours c -> { c.filekinds.block_device = Cyan.normal(); });
test!(ls_cd: ls "cd=35", exa "" => colours c -> { c.filekinds.char_device = Purple.normal(); });
test!(ls_ln: ls "ln=34", exa "" => colours c -> { c.filekinds.symlink = Blue.normal(); });
test!(ls_or: ls "or=33", exa "" => colours c -> { c.broken_arrow = Yellow.normal(); });
test!(ls_mi: ls "mi=32", exa "" => colours c -> { c.broken_filename = Green.normal(); });
// EXA_COLORS can affect all those colours too:
test!(exa_di: ls "", exa "di=32" => colours c -> { c.filekinds.directory = Green.normal(); });
test!(exa_ex: ls "", exa "ex=33" => colours c -> { c.filekinds.executable = Yellow.normal(); });
test!(exa_fi: ls "", exa "fi=34" => colours c -> { c.filekinds.normal = Blue.normal(); });
test!(exa_pi: ls "", exa "pi=35" => colours c -> { c.filekinds.pipe = Purple.normal(); });
test!(exa_so: ls "", exa "so=36" => colours c -> { c.filekinds.socket = Cyan.normal(); });
test!(exa_bd: ls "", exa "bd=35" => colours c -> { c.filekinds.block_device = Purple.normal(); });
test!(exa_cd: ls "", exa "cd=34" => colours c -> { c.filekinds.char_device = Blue.normal(); });
test!(exa_ln: ls "", exa "ln=33" => colours c -> { c.filekinds.symlink = Yellow.normal(); });
test!(exa_or: ls "", exa "or=32" => colours c -> { c.broken_arrow = Green.normal(); });
test!(exa_mi: ls "", exa "mi=31" => colours c -> { c.broken_filename = Red.normal(); });
// EXA_COLORS will even override options from LS_COLORS:
test!(ls_exa_di: ls "di=31", exa "di=32" => colours c -> { c.filekinds.directory = Green.normal(); });
test!(ls_exa_ex: ls "ex=32", exa "ex=33" => colours c -> { c.filekinds.executable = Yellow.normal(); });
test!(ls_exa_fi: ls "fi=33", exa "fi=34" => colours c -> { c.filekinds.normal = Blue.normal(); });
// But more importantly, EXA_COLORS has its own, special list of colours:
test!(exa_ur: ls "", exa "ur=38;5;100" => colours c -> { c.perms.user_read = Fixed(100).normal(); });
test!(exa_uw: ls "", exa "uw=38;5;101" => colours c -> { c.perms.user_write = Fixed(101).normal(); });
test!(exa_ux: ls "", exa "ux=38;5;102" => colours c -> { c.perms.user_execute_file = Fixed(102).normal(); });
test!(exa_ue: ls "", exa "ue=38;5;103" => colours c -> { c.perms.user_execute_other = Fixed(103).normal(); });
test!(exa_gr: ls "", exa "gr=38;5;104" => colours c -> { c.perms.group_read = Fixed(104).normal(); });
test!(exa_gw: ls "", exa "gw=38;5;105" => colours c -> { c.perms.group_write = Fixed(105).normal(); });
test!(exa_gx: ls "", exa "gx=38;5;106" => colours c -> { c.perms.group_execute = Fixed(106).normal(); });
test!(exa_tr: ls "", exa "tr=38;5;107" => colours c -> { c.perms.other_read = Fixed(107).normal(); });
test!(exa_tw: ls "", exa "tw=38;5;108" => colours c -> { c.perms.other_write = Fixed(108).normal(); });
test!(exa_tx: ls "", exa "tx=38;5;109" => colours c -> { c.perms.other_execute = Fixed(109).normal(); });
test!(exa_su: ls "", exa "su=38;5;110" => colours c -> { c.perms.special_user_file = Fixed(110).normal(); });
test!(exa_sf: ls "", exa "sf=38;5;111" => colours c -> { c.perms.special_other = Fixed(111).normal(); });
test!(exa_xa: ls "", exa "xa=38;5;112" => colours c -> { c.perms.attribute = Fixed(112).normal(); });
test!(exa_sn: ls "", exa "sn=38;5;113" => colours c -> { c.size.numbers = Fixed(113).normal(); });
test!(exa_sb: ls "", exa "sb=38;5;114" => colours c -> { c.size.unit = Fixed(114).normal(); });
test!(exa_df: ls "", exa "df=38;5;115" => colours c -> { c.size.major = Fixed(115).normal(); });
test!(exa_ds: ls "", exa "ds=38;5;116" => colours c -> { c.size.minor = Fixed(116).normal(); });
test!(exa_uu: ls "", exa "uu=38;5;117" => colours c -> { c.users.user_you = Fixed(117).normal(); });
test!(exa_un: ls "", exa "un=38;5;118" => colours c -> { c.users.user_someone_else = Fixed(118).normal(); });
test!(exa_gu: ls "", exa "gu=38;5;119" => colours c -> { c.users.group_yours = Fixed(119).normal(); });
test!(exa_gn: ls "", exa "gn=38;5;120" => colours c -> { c.users.group_not_yours = Fixed(120).normal(); });
test!(exa_lc: ls "", exa "lc=38;5;121" => colours c -> { c.links.normal = Fixed(121).normal(); });
test!(exa_lm: ls "", exa "lm=38;5;122" => colours c -> { c.links.multi_link_file = Fixed(122).normal(); });
test!(exa_ga: ls "", exa "ga=38;5;123" => colours c -> { c.git.new = Fixed(123).normal(); });
test!(exa_gm: ls "", exa "gm=38;5;124" => colours c -> { c.git.modified = Fixed(124).normal(); });
test!(exa_gd: ls "", exa "gd=38;5;125" => colours c -> { c.git.deleted = Fixed(125).normal(); });
test!(exa_gv: ls "", exa "gv=38;5;126" => colours c -> { c.git.renamed = Fixed(126).normal(); });
test!(exa_gt: ls "", exa "gt=38;5;127" => colours c -> { c.git.typechange = Fixed(127).normal(); });
test!(exa_xx: ls "", exa "xx=38;5;128" => colours c -> { c.punctuation = Fixed(128).normal(); });
test!(exa_da: ls "", exa "da=38;5;129" => colours c -> { c.date = Fixed(129).normal(); });
test!(exa_in: ls "", exa "in=38;5;130" => colours c -> { c.inode = Fixed(130).normal(); });
test!(exa_bl: ls "", exa "bl=38;5;131" => colours c -> { c.blocks = Fixed(131).normal(); });
test!(exa_hd: ls "", exa "hd=38;5;132" => colours c -> { c.header = Fixed(132).normal(); });
test!(exa_lp: ls "", exa "lp=38;5;133" => colours c -> { c.symlink_path = Fixed(133).normal(); });
test!(exa_cc: ls "", exa "cc=38;5;134" => colours c -> { c.control_char = Fixed(134).normal(); });
// All the while, LS_COLORS treats them as filenames:
test!(ls_uu: ls "uu=38;5;117", exa "" => exts [ ("uu", Fixed(117).normal()) ]);
test!(ls_un: ls "un=38;5;118", exa "" => exts [ ("un", Fixed(118).normal()) ]);
test!(ls_gu: ls "gu=38;5;119", exa "" => exts [ ("gu", Fixed(119).normal()) ]);
test!(ls_gn: ls "gn=38;5;120", exa "" => exts [ ("gn", Fixed(120).normal()) ]);
// Just like all other keys:
test!(ls_txt: ls "*.txt=31", exa "" => exts [ ("*.txt", Red.normal()) ]);
test!(ls_mp3: ls "*.mp3=38;5;135", exa "" => exts [ ("*.mp3", Fixed(135).normal()) ]);
test!(ls_mak: ls "Makefile=1;32;4", exa "" => exts [ ("Makefile", Green.bold().underline()) ]);
test!(exa_txt: ls "", exa "*.zip=31" => exts [ ("*.zip", Red.normal()) ]);
test!(exa_mp3: ls "", exa "lev.*=38;5;153" => exts [ ("lev.*", Fixed(153).normal()) ]);
test!(exa_mak: ls "", exa "Cargo.toml=4;32;1" => exts [ ("Cargo.toml", Green.bold().underline()) ]);
// Testing whether a glob from EXA_COLORS overrides a glob from LS_COLORS
// cant be tested here, because theyll both be added to the same vec
// Values get separated by colons:
test!(ls_multi: ls "*.txt=31:*.rtf=32", exa "" => exts [ ("*.txt", Red.normal()), ("*.rtf", Green.normal()) ]);
test!(exa_multi: ls "", exa "*.tmp=37:*.log=37" => exts [ ("*.tmp", White.normal()), ("*.log", White.normal()) ]);
test!(ls_five: ls "1*1=31:2*2=32:3*3=1;33:4*4=34;1:5*5=35;4", exa "" => exts [
("1*1", Red.normal()), ("2*2", Green.normal()), ("3*3", Yellow.bold()), ("4*4", Blue.bold()), ("5*5", Purple.underline())
]);
// Finally, colours get applied right-to-left:
test!(ls_overwrite: ls "pi=31:pi=32:pi=33", exa "" => colours c -> { c.filekinds.pipe = Yellow.normal(); });
test!(exa_overwrite: ls "", exa "da=36:da=35:da=34" => colours c -> { c.date = Blue.normal(); });
}

View File

@ -1,9 +1,6 @@
use style::Colours;
use output::{View, Mode, grid, details};
use output::grid_details::{self, RowThreshold};
use output::table::{TimeTypes, Environment, SizeFormat, Columns, Options as TableOptions};
use output::file_name::{Classify, FileStyle, NoFileColours};
use output::time::TimeFormat;
use options::{flags, Misfire, Vars};
@ -16,9 +13,10 @@ impl View {
/// Determine which view to use and all of that views arguments.
pub fn deduce<V: Vars>(matches: &MatchedFlags, vars: &V) -> Result<View, Misfire> {
use options::style::Styles;
let mode = Mode::deduce(matches, vars)?;
let colours = Colours::deduce(matches, vars, || *TERM_WIDTH)?;
let style = FileStyle::deduce(matches, &colours)?;
let Styles { colours, style } = Styles::deduce(matches, vars, || *TERM_WIDTH)?;
Ok(View { mode, colours, style })
}
}
@ -335,30 +333,6 @@ impl TimeTypes {
}
impl FileStyle {
#[allow(trivial_casts)]
fn deduce(matches: &MatchedFlags, colours: &Colours) -> Result<FileStyle, Misfire> {
use info::filetype::FileExtensions;
let classify = Classify::deduce(matches)?;
let exts = if colours.colourful { Box::new(FileExtensions) as Box<_> }
else { Box::new(NoFileColours) as Box<_> };
Ok(FileStyle { classify, exts })
}
}
impl Classify {
fn deduce(matches: &MatchedFlags) -> Result<Classify, Misfire> {
let flagged = matches.has(&flags::CLASSIFY)?;
Ok(if flagged { Classify::AddFileIndicators }
else { Classify::JustFilenames })
}
}
// Gets, then caches, the width of the terminal that exa is running in.
// This gets used multiple times above, with no real guarantee of order,
// so its easier to just cache it the first time it runs.

View File

@ -285,3 +285,13 @@ impl FileColours for NoFileColours {
fn colour_file(&self, _file: &File) -> Option<Style> { None }
}
// When getting the colour of a file from a *pair* of colourisers, try the
// first one then try the second one. This lets the user provide their own
// file type associations, while falling back to the default set if not set
// explicitly.
impl<A, B> FileColours for (A, B)
where A: FileColours, B: FileColours {
fn colour_file(&self, file: &File) -> Option<Style> {
self.0.colour_file(file).or_else(|| self.1.colour_file(file))
}
}

View File

@ -195,7 +195,7 @@ impl Colours {
impl Colours {
pub fn set_ls(&mut self, pair: &Pair) {
pub fn set_ls(&mut self, pair: &Pair) -> bool {
match pair.key {
"di" => self.filekinds.directory = pair.to_style(),
"ex" => self.filekinds.executable = pair.to_style(),
@ -207,23 +207,13 @@ impl Colours {
"ln" => self.filekinds.symlink = pair.to_style(),
"or" => self.broken_arrow = pair.to_style(),
"mi" => self.broken_filename = pair.to_style(),
_ => {/* dont change anything */},
_ => return false,
}
true
}
pub fn set_exa(&mut self, pair: &Pair) {
pub fn set_exa(&mut self, pair: &Pair) -> bool {
match pair.key {
"di" => self.filekinds.directory = pair.to_style(),
"ex" => self.filekinds.executable = pair.to_style(),
"fi" => self.filekinds.normal = pair.to_style(),
"pi" => self.filekinds.pipe = pair.to_style(),
"so" => self.filekinds.socket = pair.to_style(),
"bd" => self.filekinds.block_device = pair.to_style(),
"cd" => self.filekinds.char_device = pair.to_style(),
"ln" => self.filekinds.symlink = pair.to_style(),
"or" => self.broken_arrow = pair.to_style(),
"mi" => self.broken_filename = pair.to_style(),
"ur" => self.perms.user_read = pair.to_style(),
"uw" => self.perms.user_write = pair.to_style(),
"ux" => self.perms.user_execute_file = pair.to_style(),
@ -265,8 +255,9 @@ impl Colours {
"lp" => self.symlink_path = pair.to_style(),
"cc" => self.control_char = pair.to_style(),
_ => {/* still dont change anything */},
_ => return false,
}
true
}
}

View File

@ -1,6 +1,6 @@
use std::ops::FnMut;
use ansi_term::Style;
use ansi_term::{Colour, Style};
use ansi_term::Colour::*;
@ -25,11 +25,30 @@ pub struct Pair<'var> {
pub value: &'var str,
}
use std::iter::Peekable;
fn parse_into_high_colour<'a, I>(iter: &mut Peekable<I>) -> Option<Colour>
where I: Iterator<Item=&'a str> {
match iter.peek() {
Some(&"5") => {
let _5 = iter.next();
if let Some(byte) = iter.next() {
if let Ok(num) = byte.parse() {
return Some(Fixed(num));
}
}
}
_ => {},
}
None
}
impl<'var> Pair<'var> {
pub fn to_style(&self) -> Style {
let mut style = Style::default();
let mut iter = self.value.split(";").peekable();
for num in self.value.split(";") {
while let Some(num) = iter.next() {
match num {
// Bold and italic
@ -45,6 +64,7 @@ impl<'var> Pair<'var> {
"35" => style = style.fg(Purple),
"36" => style = style.fg(Cyan),
"37" => style = style.fg(White),
"38" => if let Some(c) = parse_into_high_colour(&mut iter) { style = style.fg(c) },
// Background colours
"40" => style = style.on(Black),
@ -55,6 +75,8 @@ impl<'var> Pair<'var> {
"45" => style = style.on(Purple),
"46" => style = style.on(Cyan),
"47" => style = style.on(White),
"48" => if let Some(c) = parse_into_high_colour(&mut iter) { style = style.on(c) },
_ => {/* ignore the error and do nothing */},
}
}
@ -93,6 +115,16 @@ mod ansi_test {
test!(semis: ";;;;;;" => Style::default());
test!(nines: "99999999" => Style::default());
test!(word: "GREEN" => Style::default());
// Higher colours
test!(hifg: "38;5;149" => Fixed(149).normal());
test!(hibg: "48;5;1" => Style::default().on(Fixed(1)));
test!(hibo: "48;5;1;1" => Style::default().on(Fixed(1)).bold());
test!(hiund: "4;48;5;1" => Style::default().on(Fixed(1)).underline());
test!(fgbg: "38;5;121;48;5;212" => Fixed(121).on(Fixed(212)));
test!(bgfg: "48;5;121;38;5;212" => Fixed(212).on(Fixed(121)));
test!(toohi: "48;5;999" => Style::default());
}

View File

@ -158,17 +158,17 @@ env LANG=ja_JP.UTF-8 $exa $testcases/dates -l | diff -q - $results/dates_jp ||
# These directories are created in the VM users home directory (the default
# location) when a Cargo build is done.
(cd; mkdir -p target/debug/build
$exa -1d target target/debug target/debug/build | diff -q - $results/dir_paths) || exit 1
$exa -1d . .. / | diff -q - $results/dirs || exit 1
$exa -1d target target/debug target/debug/build | diff -q - $results/dir_paths) || exit 1
$exa -1d . .. / | diff -q - $results/dirs || exit 1
# Links
COLUMNS=80 $exa $testcases/links 2>&1 | diff -q - $results/links || exit 1
$exa $testcases/links -1 2>&1 | diff -q - $results/links_1 || exit 1
$exa $testcases/links -T 2>&1 | diff -q - $results/links_T || exit 1
$exa $testcases/links -T@ 2>&1 | diff -q - $results/links_T@ || exit 1
$exa /proc/1/root -T 2>&1 | diff -q - $results/proc_1_root || exit 1
$exa /proc/1/root -T@ 2>&1 | diff -q - $results/proc_1_root_@ || exit 1
COLUMNS=80 $exa $testcases/links 2>&1 | diff -q - $results/links || exit 1
$exa $testcases/links -1 2>&1 | diff -q - $results/links_1 || exit 1
$exa $testcases/links -T 2>&1 | diff -q - $results/links_T || exit 1
$exa $testcases/links -T@ 2>&1 | diff -q - $results/links_T@ || exit 1
$exa /proc/1/root -T 2>&1 | diff -q - $results/proc_1_root || exit 1
$exa /proc/1/root -T@ 2>&1 | diff -q - $results/proc_1_root_@ || exit 1
# Thereve been bugs where the target file wasnt printed properly when the
# symlink file was specified on the command-line directly.
@ -230,14 +230,35 @@ $exa $testcases/hiddens -l -a 2>&1 | diff -q - $results/hiddens_la || exit 1
$exa $testcases/hiddens -l -aa 2>&1 | diff -q - $results/hiddens_laa || exit 1
# Themes
LS_COLORS="bd=31:cd=32:pi=34" $exa -1 $testcases/specials 2>&1 | diff -q - $results/themed_specials || exit 1
EXA_COLORS="bd=31:cd=32:pi=34" $exa -1 $testcases/specials 2>&1 | diff -q - $results/themed_specials || exit 1
LS_COLORS="*.deb=1;37:*.tar.*=1;37" $exa -1 $testcases/file-names-exts/compressed.* 2>&1 | diff -q - $results/themed_compresseds || exit 1
EXA_COLORS="*.deb=1;37:*.tar.*=1;37" $exa -1 $testcases/file-names-exts/compressed.* 2>&1 | diff -q - $results/themed_compresseds || exit 1
EXA_COLORS="*.deb=1;37" LS_COLORS="*.tar.*=1;37" $exa -1 $testcases/file-names-exts/compressed.* 2>&1 | diff -q - $results/themed_compresseds || exit 1
LS_COLORS="reset:*.deb=1;37:*.tar.*=1;37" $exa -1 $testcases/file-names-exts/compressed.* 2>&1 | diff -q - $results/themed_compresseds || exit 1
EXA_COLORS="reset:*.deb=1;37:*.tar.*=1;37" $exa -1 $testcases/file-names-exts/compressed.* 2>&1 | diff -q - $results/themed_compresseds_r || exit 1
EXA_COLORS="or=32:mi=32;1;4:cc=34;1:ln=34:lp=36;4:xx=32" $exa -1 $testcases/file-names/links 2>&1 | diff -q - $results/themed_links || exit 1
# EXA_COLORS overrides LS_COLORS
LS_COLORS="bd=32:cd=34:pi=31" EXA_COLORS="bd=31:cd=32:pi=34" $exa -1 $testcases/specials 2>&1 | diff -q - $results/themed_specials || exit 1
EXA_COLORS="di=38;5;195:fi=38;5;250:xx=38;5;237:ur=38;5;194:uw=38;5;193:ux=38;5;192:gr=38;5;191:gw=38;5;190:gx=38;5;118:tr=38;5;119:tw=38;5;120:tx=38;5;121:su=38;5;51:sf=38;5;50:sn=38;5;49:un=38;5;46:da=38;5;47:ex=38;5;48" \
$exa --long $testcases/permissions 2>&1 | diff -q - $results/themed_long || exit 1
EXA_COLORS="reset" $exa $testcases/file-names-exts -1 2>&1 | diff -q - $results/themed_un || exit 1
# Errors
$exa --binary 2>&1 | diff -q - $results/error_useless || exit 1
$exa --ternary 2>&1 | diff -q - $results/error_long || exit 1
$exa -4 2>&1 | diff -q - $results/error_short || exit 1
$exa --time 2>&1 | diff -q - $results/error_value || exit 1
$exa --long=time 2>&1 | diff -q - $results/error_overvalued || exit 1
$exa -l --long 2>&1 | diff -q - $results/error_duplicate || exit 1
$exa -ll 2>&1 | diff -q - $results/error_twice || exit 1
$exa --binary 2>&1 | diff -q - $results/error_useless || exit 1
$exa --ternary 2>&1 | diff -q - $results/error_long || exit 1
$exa -4 2>&1 | diff -q - $results/error_short || exit 1
$exa --time 2>&1 | diff -q - $results/error_value || exit 1
$exa --long=time 2>&1 | diff -q - $results/error_overvalued || exit 1
$exa -l --long 2>&1 | diff -q - $results/error_duplicate || exit 1
$exa -ll 2>&1 | diff -q - $results/error_twice || exit 1
# Debug mode
@ -246,8 +267,8 @@ EXA_DEBUG="1" $exa $testcases/attributes/dirs/no-xattrs_empty -lh 2>&1 | tail -n
# And finally...
$exa --help | diff -q - $results/help || exit 1
$exa --help --long | diff -q - $results/help_long || exit 1
$exa --help | diff -q - $results/help || exit 1
$exa --help --long | diff -q - $results/help_long || exit 1
echo "All the tests passed!"

View File

@ -0,0 +1,5 @@
/testcases/file-names-exts/compressed.deb
/testcases/file-names-exts/compressed.tar.gz
/testcases/file-names-exts/compressed.tar.xz
/testcases/file-names-exts/compressed.tgz
/testcases/file-names-exts/compressed.txz

View File

@ -0,0 +1,5 @@
/testcases/file-names-exts/compressed.deb
/testcases/file-names-exts/compressed.tar.gz
/testcases/file-names-exts/compressed.tar.xz
/testcases/file-names-exts/compressed.tgz
/testcases/file-names-exts/compressed.txz

3
xtests/themed_links Normal file
View File

@ -0,0 +1,3 @@
another: [\n] -> /testcases/file-names/new-line-dir: [\n]/another: [\n]
broken -> /testcases/file-names/new-line-dir: [\n]/broken
subfile -> /testcases/file-names/new-line-dir: [\n]/subfile

22
xtests/themed_long Normal file
View File

@ -0,0 +1,22 @@
.--------- 0 cassowary  1 Jan 12:34 000
.--------x 0 cassowary  1 Jan 12:34 001
.-------w- 0 cassowary  1 Jan 12:34 002
.------r-- 0 cassowary  1 Jan 12:34 004
.-----x--- 0 cassowary  1 Jan 12:34 010
.----w---- 0 cassowary  1 Jan 12:34 020
.---r----- 0 cassowary  1 Jan 12:34 040
.--x------ 0 cassowary  1 Jan 12:34 100
.-w------- 0 cassowary  1 Jan 12:34 200
.r-------- 0 cassowary  1 Jan 12:34 400
.rw-r--r-- 0 cassowary  1 Jan 12:34 644
.rwxr-xr-x 0 cassowary  1 Jan 12:34 755
.rwxrwxrwx 0 cassowary  1 Jan 12:34 777
.--------T 0 cassowary  1 Jan 12:34 1000
.--------t 0 cassowary  1 Jan 12:34 1001
.-----S--- 0 cassowary  1 Jan 12:34 2000
.-----s--- 0 cassowary  1 Jan 12:34 2010
.--S------ 0 cassowary  1 Jan 12:34 4000
.--s------ 0 cassowary  1 Jan 12:34 4100
.rwSrwSrwT 0 cassowary  1 Jan 12:34 7666
.rwsrwsrwt 0 cassowary  1 Jan 12:34 7777
d--------- - cassowary  1 Jan 12:34 forbidden-directory

3
xtests/themed_specials Normal file
View File

@ -0,0 +1,3 @@
block-device
char-device
named-pipe

26
xtests/themed_un Normal file
View File

@ -0,0 +1,26 @@
#SAVEFILE#
backup~
compiled.class
compiled.coffee
compiled.js
compiled.o
compressed.deb
compressed.tar.gz
compressed.tar.xz
compressed.tgz
compressed.txz
COMPRESSED.ZIP
crypto.asc
crypto.signature
document.pdf
DOCUMENT.XLSX
file.tmp
IMAGE.PNG
image.svg
lossless.flac
lossless.wav
Makefile
music.mp3
MUSIC.OGG
VIDEO.AVI
video.wmv