2017-08-26 20:40:37 +00:00
|
|
|
|
use style::Colours;
|
2017-08-24 22:38:26 +00:00
|
|
|
|
|
2017-08-25 16:43:36 +00:00
|
|
|
|
use options::{flags, Vars, Misfire};
|
2017-08-24 22:38:26 +00:00
|
|
|
|
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 isn’t 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 {
|
2017-08-25 16:43:36 +00:00
|
|
|
|
pub fn deduce<V, TW>(matches: &MatchedFlags, vars: &V, widther: TW) -> Result<Colours, Misfire>
|
|
|
|
|
where TW: Fn() -> Option<usize>, V: Vars {
|
2017-08-24 22:38:26 +00:00
|
|
|
|
use self::TerminalColours::*;
|
2017-08-26 20:40:37 +00:00
|
|
|
|
use style::LSColors;
|
2017-08-26 20:19:06 +00:00
|
|
|
|
use options::vars;
|
2017-08-24 22:38:26 +00:00
|
|
|
|
|
|
|
|
|
let tc = TerminalColours::deduce(matches)?;
|
2017-08-25 16:43:36 +00:00
|
|
|
|
if tc == Never || (tc == Automatic && widther().is_none()) {
|
|
|
|
|
return Ok(Colours::plain());
|
2017-08-24 22:38:26 +00:00
|
|
|
|
}
|
2017-08-25 16:43:36 +00:00
|
|
|
|
|
|
|
|
|
let scale = matches.has_where(|f| f.matches(&flags::COLOR_SCALE) || f.matches(&flags::COLOUR_SCALE))?;
|
2017-08-26 13:04:49 +00:00
|
|
|
|
let mut colours = Colours::colourful(scale.is_some());
|
2017-08-25 16:43:36 +00:00
|
|
|
|
|
2017-08-26 20:19:06 +00:00
|
|
|
|
if let Some(lsc) = vars.get(vars::LS_COLORS) {
|
2017-08-25 16:43:36 +00:00
|
|
|
|
let lsc = lsc.to_string_lossy();
|
2017-08-26 22:17:07 +00:00
|
|
|
|
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));
|
2017-08-24 22:38:26 +00:00
|
|
|
|
}
|
2017-08-25 16:43:36 +00:00
|
|
|
|
|
2017-08-26 13:04:49 +00:00
|
|
|
|
Ok(colours)
|
2017-08-24 22:38:26 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
2017-08-25 07:53:35 +00:00
|
|
|
|
mod terminal_test {
|
2017-08-24 22:38:26 +00:00
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-25 07:53:35 +00:00
|
|
|
|
static TEST_ARGS: &[&Arg] = &[ &flags::COLOR, &flags::COLOUR ];
|
2017-08-24 22:38:26 +00:00
|
|
|
|
|
|
|
|
|
macro_rules! test {
|
2017-08-25 08:07:28 +00:00
|
|
|
|
($name:ident: $inputs:expr; $stricts:expr => $result:expr) => {
|
2017-08-24 22:38:26 +00:00
|
|
|
|
#[test]
|
|
|
|
|
fn $name() {
|
2017-08-25 08:07:28 +00:00
|
|
|
|
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| TerminalColours::deduce(mf)) {
|
2017-08-24 22:38:26 +00:00
|
|
|
|
assert_eq!(result, $result);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2017-08-25 08:07:28 +00:00
|
|
|
|
($name:ident: $inputs:expr; $stricts:expr => err $result:expr) => {
|
2017-08-24 22:38:26 +00:00
|
|
|
|
#[test]
|
|
|
|
|
fn $name() {
|
2017-08-25 08:07:28 +00:00
|
|
|
|
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| TerminalColours::deduce(mf)) {
|
2017-08-24 22:38:26 +00:00
|
|
|
|
assert_eq!(result.unwrap_err(), $result);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Default
|
2017-08-25 08:07:28 +00:00
|
|
|
|
test!(empty: []; Both => Ok(TerminalColours::default()));
|
2017-08-24 22:38:26 +00:00
|
|
|
|
|
|
|
|
|
// --colour
|
2017-08-25 08:07:28 +00:00
|
|
|
|
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));
|
2017-08-24 22:38:26 +00:00
|
|
|
|
|
|
|
|
|
// --color
|
2017-08-25 08:07:28 +00:00
|
|
|
|
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));
|
2017-08-24 22:38:26 +00:00
|
|
|
|
|
|
|
|
|
// Errors
|
2017-08-25 08:07:28 +00:00
|
|
|
|
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!
|
2017-08-24 22:38:26 +00:00
|
|
|
|
|
|
|
|
|
// Overriding
|
2017-08-25 08:07:28 +00:00
|
|
|
|
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")));
|
2017-08-25 07:53:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[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 {
|
2017-08-25 08:07:28 +00:00
|
|
|
|
($name:ident: $inputs:expr, $widther:expr; $stricts:expr => $result:expr) => {
|
2017-08-25 08:03:47 +00:00
|
|
|
|
#[test]
|
|
|
|
|
fn $name() {
|
2017-08-25 16:43:36 +00:00
|
|
|
|
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| Colours::deduce(mf, &None, &$widther)) {
|
2017-08-25 08:03:47 +00:00
|
|
|
|
assert_eq!(result, $result);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2017-08-25 08:07:28 +00:00
|
|
|
|
($name:ident: $inputs:expr, $widther:expr; $stricts:expr => err $result:expr) => {
|
2017-08-25 07:53:35 +00:00
|
|
|
|
#[test]
|
|
|
|
|
fn $name() {
|
2017-08-25 16:43:36 +00:00
|
|
|
|
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| Colours::deduce(mf, &None, &$widther)) {
|
2017-08-25 07:53:35 +00:00
|
|
|
|
assert_eq!(result.unwrap_err(), $result);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2017-08-25 08:07:28 +00:00
|
|
|
|
($name:ident: $inputs:expr, $widther:expr; $stricts:expr => like $pat:pat) => {
|
2017-08-25 07:53:35 +00:00
|
|
|
|
#[test]
|
|
|
|
|
fn $name() {
|
2017-08-25 16:43:36 +00:00
|
|
|
|
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| Colours::deduce(mf, &None, &$widther)) {
|
2017-08-25 07:53:35 +00:00
|
|
|
|
println!("Testing {:?}", result);
|
|
|
|
|
match result {
|
|
|
|
|
$pat => assert!(true),
|
|
|
|
|
_ => assert!(false),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
2017-08-24 22:38:26 +00:00
|
|
|
|
|
2017-08-25 08:07:28 +00:00
|
|
|
|
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, .. }));
|
2017-08-24 22:38:26 +00:00
|
|
|
|
}
|
2017-08-25 16:43:36 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[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))) {
|
Extract trait above file name colours
This commit meddles about with both the Colours and the FileExtensions.
Even though all the renderable fields were turned into traits, the FileName struct kept on accessing fields directly on the Colours value instead of calling methods on it. It also did the usual amount of colour misappropriation (such as ‘punctuation’ instead of specifying ‘normal_arrow’)
In preparation for when custom file colours are configurable (any day now), the colourise-file-by-kind functionality (links, sockets, or directories) was separated from the colourise-file-by-name functionality (images, videos, archives). The FileStyle struct already allowed for both to be separate; it was only changed so that a type other than FileExtensions could be used instead, as long as it implements the FileColours trait. (I feel like I should re-visit the naming of all these at some point in the future)
The decision to separate the two means that FileExtensions is the one assigning the colours, rather than going through the fields on a Colours value, which have all been removed. This is why a bunch of arbitrary Styles now exist in filetype.rs.
Because the decision on which colourise-file-by-name code to use (currently just the standard extensions, or nothing if we aren’t colourising) is now determined by the Colours type (instead of being derived), it’s possible to get it wrong. And wrong it was! There was a bug where file names were colourised even though the rest of the --long output wasn’t, and this wasn’t caught by the xtests. It is now.
2017-08-26 19:43:47 +00:00
|
|
|
|
assert_eq!(result.as_ref(), Ok(&c));
|
2017-08-25 16:43:36 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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> {
|
2017-08-26 22:17:07 +00:00
|
|
|
|
use options::vars;
|
|
|
|
|
|
|
|
|
|
if name == vars::LS_COLORS && !self.ls.is_empty() {
|
2017-08-25 16:43:36 +00:00
|
|
|
|
OsString::from(self.ls.clone()).into()
|
|
|
|
|
}
|
2017-08-26 22:17:07 +00:00
|
|
|
|
else if name == vars::EXA_COLORS && !self.exa.is_empty() {
|
2017-08-25 16:43:36 +00:00
|
|
|
|
OsString::from(self.exa.clone()).into()
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-26 13:30:33 +00:00
|
|
|
|
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
|
2017-08-25 16:43:36 +00:00
|
|
|
|
}
|