Merge branch 'ls-colors'

This branch went part of the way towards supporting LS_COLORS in exa. If the variable is set, exa will style certain file kinds, such as sockets or directories or links, according to the style that corresponds with the relevant two-letter code. It doesn’t work with globs for files or extensions yet. That’s coming soon. See #116
This commit is contained in:
Benjamin Sago 2017-08-26 14:56:24 +01:00
commit adfee28fb9
21 changed files with 933 additions and 408 deletions

10
Vagrantfile vendored
View File

@ -159,10 +159,12 @@ Vagrant.configure(2) do |config|
echo ' *) echo "Usage: strict on|off"; return 1 ;; esac; }' >> /home/ubuntu/.bash_profile
echo 'function ls-colors () {' >> /home/ubuntu/.bash_profile
echo ' case "$1" in "on") export LS_COLORS="di=34:ln=35:so=32:pi=33:ex=31:bd=34;46:cd=34;43:su=30;41:sg=30;46:tw=30;42:ow=30;43" ;;' >> /home/ubuntu/.bash_profile
echo ' "off") export LS_COLORS= ;;' >> /home/ubuntu/.bash_profile
echo ' "") [ -n "$LS_COLORS" ] && echo "LS_COLORS=$LS_COLORS" || echo "ls-colors off" ;;' >> /home/ubuntu/.bash_profile
echo ' *) echo "Usage: ls-colors on|off"; return 1 ;; esac; }' >> /home/ubuntu/.bash_profile
echo ' case "$1" in ' >> /home/ubuntu/.bash_profile
echo ' "on") export LS_COLORS="di=34:ln=35:so=32:pi=33:ex=31:bd=34;46:cd=34;43:su=30;41:sg=30;46:tw=30;42:ow=30;43" ;;' >> /home/ubuntu/.bash_profile
echo ' "hacker") export LS_COLORS="di=32:ex=32:fi=32:pi=32:so=32:bd=32:cd=32:ln=32:or=32:mi=32" ;;' >> /home/ubuntu/.bash_profile
echo ' "off") export LS_COLORS= ;;' >> /home/ubuntu/.bash_profile
echo ' "") [ -n "$LS_COLORS" ] && echo "LS_COLORS=$LS_COLORS" || echo "ls-colors off" ;;' >> /home/ubuntu/.bash_profile
echo ' *) echo "Usage: ls-colors on|off"; return 1 ;; esac; }' >> /home/ubuntu/.bash_profile
# Disable last login date in sshd
sed -i '/PrintLastLog yes/c\PrintLastLog no' /etc/ssh/sshd_config

290
src/options/colours.rs Normal file
View File

@ -0,0 +1,290 @@
use output::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 output::lsc::LSColors;
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("LS_COLORS") {
let lsc = lsc.to_string_lossy();
let lsc = LSColors::parse(lsc.as_ref());
if let Some(c) = lsc.get("di") { colours.filekinds.directory = c; }
if let Some(c) = lsc.get("ex") { colours.filekinds.executable = c; }
if let Some(c) = lsc.get("fi") { colours.filekinds.normal = c; }
if let Some(c) = lsc.get("pi") { colours.filekinds.pipe = c; }
if let Some(c) = lsc.get("so") { colours.filekinds.socket = c; }
if let Some(c) = lsc.get("bd") { colours.filekinds.block_device = c; }
if let Some(c) = lsc.get("cd") { colours.filekinds.char_device = c; }
if let Some(c) = lsc.get("ln") { colours.filekinds.symlink = c; }
if let Some(c) = lsc.get("or") { colours.broken_arrow = c; }
if let Some(c) = lsc.get("mi") { colours.broken_filename = c; }
}
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, 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> {
if name == "LS_COLORS" && !self.ls.is_empty() {
OsString::from(self.ls.clone()).into()
}
else if name == "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

@ -75,6 +75,7 @@ use fs::dir_action::DirAction;
use fs::filter::FileFilter;
use output::{View, Mode, details, grid_details};
mod colours;
mod dir_action;
mod filter;
mod view;

View File

@ -11,12 +11,13 @@ use options::parser::MatchedFlags;
use fs::feature::xattr;
use info::filetype::FileExtensions;
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> {
let mode = Mode::deduce(matches, vars)?;
let colours = Colours::deduce(matches)?;
let colours = Colours::deduce(matches, vars, || *TERM_WIDTH)?;
let style = FileStyle::deduce(matches)?;
Ok(View { mode, colours, style })
}
@ -330,77 +331,6 @@ impl TimeTypes {
}
/// 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 {
fn deduce(matches: &MatchedFlags) -> Result<Colours, Misfire> {
use self::TerminalColours::*;
let tc = TerminalColours::deduce(matches)?;
if tc == Always || (tc == Automatic && TERM_WIDTH.is_some()) {
let scale = matches.has(&flags::COLOR_SCALE)? || matches.has(&flags::COLOUR_SCALE)?;
Ok(Colours::colourful(scale))
}
else {
Ok(Colours::plain())
}
}
}
impl FileStyle {
fn deduce(matches: &MatchedFlags) -> Result<FileStyle, Misfire> {
let classify = Classify::deduce(matches)?;
@ -452,7 +382,6 @@ mod test {
static TEST_ARGS: &[&Arg] = &[ &flags::BINARY, &flags::BYTES, &flags::TIME_STYLE,
&flags::TIME, &flags::MODIFIED, &flags::CREATED, &flags::ACCESSED,
&flags::COLOR, &flags::COLOUR,
&flags::HEADER, &flags::GROUP, &flags::INODE, &flags::GIT,
&flags::LINKS, &flags::BLOCKS, &flags::LONG, &flags::LEVEL,
&flags::GRID, &flags::ACROSS, &flags::ONE_LINE ];
@ -611,39 +540,6 @@ mod test {
}
mod colourses {
use super::*;
// Default
test!(empty: TerminalColours <- []; Both => Ok(TerminalColours::default()));
// --colour
test!(u_always: TerminalColours <- ["--colour=always"]; Both => Ok(TerminalColours::Always));
test!(u_auto: TerminalColours <- ["--colour", "auto"]; Both => Ok(TerminalColours::Automatic));
test!(u_never: TerminalColours <- ["--colour=never"]; Both => Ok(TerminalColours::Never));
// --color
test!(no_u_always: TerminalColours <- ["--color", "always"]; Both => Ok(TerminalColours::Always));
test!(no_u_auto: TerminalColours <- ["--color=auto"]; Both => Ok(TerminalColours::Automatic));
test!(no_u_never: TerminalColours <- ["--color", "never"]; Both => Ok(TerminalColours::Never));
// Errors
test!(no_u_error: TerminalColours <- ["--color=upstream"]; Both => err Misfire::bad_argument(&flags::COLOR, &os("upstream"), super::COLOURS)); // the error is for --color
test!(u_error: TerminalColours <- ["--colour=lovers"]; Both => err Misfire::bad_argument(&flags::COLOR, &os("lovers"), super::COLOURS)); // and so is this one!
// Overriding
test!(overridden_1: TerminalColours <- ["--colour=auto", "--colour=never"]; Last => Ok(TerminalColours::Never));
test!(overridden_2: TerminalColours <- ["--color=auto", "--colour=never"]; Last => Ok(TerminalColours::Never));
test!(overridden_3: TerminalColours <- ["--colour=auto", "--color=never"]; Last => Ok(TerminalColours::Never));
test!(overridden_4: TerminalColours <- ["--color=auto", "--color=never"]; Last => Ok(TerminalColours::Never));
test!(overridden_5: TerminalColours <- ["--colour=auto", "--colour=never"]; Complain => err Misfire::Duplicate(Flag::Long("colour"), Flag::Long("colour")));
test!(overridden_6: TerminalColours <- ["--color=auto", "--colour=never"]; Complain => err Misfire::Duplicate(Flag::Long("color"), Flag::Long("colour")));
test!(overridden_7: TerminalColours <- ["--colour=auto", "--color=never"]; Complain => err Misfire::Duplicate(Flag::Long("colour"), Flag::Long("color")));
test!(overridden_8: TerminalColours <- ["--color=auto", "--color=never"]; Complain => err Misfire::Duplicate(Flag::Long("color"), Flag::Long("color")));
}
mod views {
use super::*;
use output::grid::Options as GridOptions;

View File

@ -1,11 +1,14 @@
use ansi_term::Style;
use ansi_term::Colour::{Red, Green, Yellow, Blue, Cyan, Purple, Fixed};
use output::render;
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Colours {
pub scale: bool,
pub filekinds: FileKinds,
pub filetypes: FileTypes,
pub perms: Permissions,
pub size: Size,
@ -25,16 +28,23 @@ pub struct Colours {
pub control_char: Style,
}
// Colours for files depending on their filesystem type.
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct FileTypes {
pub struct FileKinds {
pub normal: Style,
pub directory: Style,
pub symlink: Style,
pub pipe: Style,
pub device: Style,
pub block_device: Style,
pub char_device: Style,
pub socket: Style,
pub special: Style,
pub executable: Style,
}
// Colours for files depending on their name or extension.
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct FileTypes {
pub image: Style,
pub video: Style,
pub music: Style,
@ -115,15 +125,19 @@ impl Colours {
Colours {
scale: scale,
filekinds: FileKinds {
normal: Style::default(),
directory: Blue.bold(),
symlink: Cyan.normal(),
pipe: Yellow.normal(),
block_device: Yellow.bold(),
char_device: Yellow.bold(),
socket: Red.bold(),
special: Yellow.normal(),
executable: Green.bold(),
},
filetypes: FileTypes {
normal: Style::default(),
directory: Blue.bold(),
symlink: Cyan.normal(),
pipe: Yellow.normal(),
device: Yellow.bold(),
socket: Red.bold(),
special: Yellow.normal(),
executable: Green.bold(),
image: Fixed(133).normal(),
video: Fixed(135).normal(),
music: Fixed(92).normal(),
@ -202,8 +216,63 @@ impl Colours {
control_char: Red.normal(),
}
}
}
pub fn file_size(&self, size: u64) -> Style {
impl render::BlocksColours for Colours {
fn block_count(&self) -> Style { self.blocks }
fn no_blocks(&self) -> Style { self.punctuation }
}
impl render::FiletypeColours for Colours {
fn normal(&self) -> Style { self.filekinds.normal }
fn directory(&self) -> Style { self.filekinds.directory }
fn pipe(&self) -> Style { self.filekinds.pipe }
fn symlink(&self) -> Style { self.filekinds.symlink }
fn block_device(&self) -> Style { self.filekinds.block_device }
fn char_device(&self) -> Style { self.filekinds.char_device }
fn socket(&self) -> Style { self.filekinds.socket }
fn special(&self) -> Style { self.filekinds.special }
}
impl render::GitColours for Colours {
fn not_modified(&self) -> Style { self.punctuation }
fn new(&self) -> Style { self.git.new }
fn modified(&self) -> Style { self.git.modified }
fn deleted(&self) -> Style { self.git.deleted }
fn renamed(&self) -> Style { self.git.renamed }
fn type_change(&self) -> Style { self.git.typechange }
}
impl render::GroupColours for Colours {
fn yours(&self) -> Style { self.users.group_yours }
fn not_yours(&self) -> Style { self.users.group_not_yours }
}
impl render::LinksColours for Colours {
fn normal(&self) -> Style { self.links.normal }
fn multi_link_file(&self) -> Style { self.links.multi_link_file }
}
impl render::PermissionsColours for Colours {
fn dash(&self) -> Style { self.punctuation }
fn user_read(&self) -> Style { self.perms.user_read }
fn user_write(&self) -> Style { self.perms.user_write }
fn user_execute_file(&self) -> Style { self.perms.user_execute_file }
fn user_execute_other(&self) -> Style { self.perms.user_execute_other }
fn group_read(&self) -> Style { self.perms.group_read }
fn group_write(&self) -> Style { self.perms.group_write }
fn group_execute(&self) -> Style { self.perms.group_execute }
fn other_read(&self) -> Style { self.perms.other_read }
fn other_write(&self) -> Style { self.perms.other_write }
fn other_execute(&self) -> Style { self.perms.other_execute }
fn special_user_file(&self) -> Style { self.perms.special_user_file }
fn special_other(&self) -> Style { self.perms.special_other }
fn attribute(&self) -> Style { self.perms.attribute }
}
impl render::SizeColours for Colours {
fn size(&self, size: u64) -> Style {
if self.scale {
if size < 1024 {
self.size.scale_byte
@ -225,4 +294,16 @@ impl Colours {
self.size.numbers
}
}
fn unit(&self) -> Style { self.size.unit }
fn no_size(&self) -> Style { self.punctuation }
fn major(&self) -> Style { self.size.major }
fn comma(&self) -> Style { self.punctuation }
fn minor(&self) -> Style { self.size.minor }
}
impl render::UserColours for Colours {
fn you(&self) -> Style { self.users.user_you }
fn someone_else(&self) -> Style { self.users.user_someone_else }
}

View File

@ -64,6 +64,8 @@ use std::io::{Write, Error as IOError, Result as IOResult};
use std::path::PathBuf;
use std::vec::IntoIter as VecIntoIter;
use ansi_term::Style;
use fs::{Dir, File};
use fs::dir_action::RecurseOptions;
use fs::filter::FileFilter;
@ -340,15 +342,15 @@ impl<'a> Render<'a> {
total_width: table.widths().total(),
table: table,
inner: rows.into_iter(),
colours: self.colours,
tree_style: self.colours.punctuation,
}
}
pub fn iterate(&'a self, rows: Vec<Row>) -> Iter<'a> {
pub fn iterate(&'a self, rows: Vec<Row>) -> Iter {
Iter {
tree_trunk: TreeTrunk::default(),
inner: rows.into_iter(),
colours: self.colours,
tree_style: self.colours.punctuation,
}
}
}
@ -374,11 +376,12 @@ pub struct Row {
pub struct TableIter<'a> {
table: Table<'a>,
tree_trunk: TreeTrunk,
total_width: usize,
colours: &'a Colours,
inner: VecIntoIter<Row>,
table: Table<'a>,
total_width: usize,
tree_style: Style,
tree_trunk: TreeTrunk,
}
impl<'a> Iterator for TableIter<'a> {
@ -397,7 +400,7 @@ impl<'a> Iterator for TableIter<'a> {
};
for tree_part in self.tree_trunk.new_row(row.tree) {
cell.push(self.colours.punctuation.paint(tree_part.ascii_art()), 4);
cell.push(self.tree_style.paint(tree_part.ascii_art()), 4);
}
// If any tree characters have been printed, then add an extra
@ -413,13 +416,13 @@ impl<'a> Iterator for TableIter<'a> {
}
pub struct Iter<'a> {
pub struct Iter {
tree_trunk: TreeTrunk,
colours: &'a Colours,
tree_style: Style,
inner: VecIntoIter<Row>,
}
impl<'a> Iterator for Iter<'a> {
impl Iterator for Iter {
type Item = TextCell;
fn next(&mut self) -> Option<Self::Item> {
@ -427,7 +430,7 @@ impl<'a> Iterator for Iter<'a> {
let mut cell = TextCell::default();
for tree_part in self.tree_trunk.new_row(row.tree) {
cell.push(self.colours.punctuation.paint(tree_part.ascii_art()), 4);
cell.push(self.tree_style.paint(tree_part.ascii_art()), 4);
}
// If any tree characters have been printed, then add an extra

View File

@ -247,14 +247,14 @@ impl<'a, 'dir> FileName<'a, 'dir> {
// Otherwise, just apply a bunch of rules in order. For example,
// executable image files should be executable rather than images.
match self.file {
f if f.is_directory() => self.colours.filetypes.directory,
f if f.is_executable_file() => self.colours.filetypes.executable,
f if f.is_link() => self.colours.filetypes.symlink,
f if f.is_pipe() => self.colours.filetypes.pipe,
f if f.is_char_device()
| f.is_block_device() => self.colours.filetypes.device,
f if f.is_socket() => self.colours.filetypes.socket,
f if !f.is_file() => self.colours.filetypes.special,
f if f.is_directory() => self.colours.filekinds.directory,
f if f.is_executable_file() => self.colours.filekinds.executable,
f if f.is_link() => self.colours.filekinds.symlink,
f if f.is_pipe() => self.colours.filekinds.pipe,
f if f.is_block_device() => self.colours.filekinds.block_device,
f if f.is_char_device() => self.colours.filekinds.char_device,
f if f.is_socket() => self.colours.filekinds.socket,
f if !f.is_file() => self.colours.filekinds.special,
f if self.exts.is_immediate(f) => self.colours.filetypes.immediate,
f if self.exts.is_image(f) => self.colours.filetypes.image,
@ -266,7 +266,8 @@ impl<'a, 'dir> FileName<'a, 'dir> {
f if self.exts.is_compressed(f) => self.colours.filetypes.compressed,
f if self.exts.is_temp(f) => self.colours.filetypes.temp,
f if self.exts.is_compiled(f) => self.colours.filetypes.compiled,
_ => self.colours.filetypes.normal,
_ => self.colours.filekinds.normal,
}
}
}

148
src/output/lsc.rs Normal file
View File

@ -0,0 +1,148 @@
#![allow(dead_code)]
use std::collections::HashMap;
use ansi_term::Style;
use ansi_term::Colour::*;
pub struct LSColors<'var> {
contents: HashMap<&'var str, &'var str>
}
impl<'var> LSColors<'var> {
pub fn parse(input: &'var str) -> LSColors<'var> {
let contents = input.split(":")
.flat_map(|mapping| {
let bits = mapping.split("=")
.take(3)
.collect::<Vec<_>>();
if bits.len() != 2 || bits[0].is_empty() || bits[1].is_empty() { None }
else { Some( (bits[0], bits[1]) ) }
}).collect();
LSColors { contents }
}
pub fn get(&self, facet_name: &str) -> Option<Style> {
self.contents.get(facet_name).map(ansi_to_style)
}
}
fn ansi_to_style(ansi: &&str) -> Style {
let mut style = Style::default();
for num in ansi.split(";") {
match num {
// Bold and italic
"1" => style = style.bold(),
"4" => style = style.underline(),
// Foreground colours
"30" => style = style.fg(Black),
"31" => style = style.fg(Red),
"32" => style = style.fg(Green),
"33" => style = style.fg(Yellow),
"34" => style = style.fg(Blue),
"35" => style = style.fg(Purple),
"36" => style = style.fg(Cyan),
"37" => style = style.fg(White),
// Background colours
"40" => style = style.on(Black),
"41" => style = style.on(Red),
"42" => style = style.on(Green),
"43" => style = style.on(Yellow),
"44" => style = style.on(Blue),
"45" => style = style.on(Purple),
"46" => style = style.on(Cyan),
"47" => style = style.on(White),
_ => {/* ignore the error and do nothing */},
}
}
style
}
#[cfg(test)]
mod ansi_test {
use super::*;
use ansi_term::Style;
macro_rules! test {
($name:ident: $input:expr => $result:expr) => {
#[test]
fn $name() {
assert_eq!(ansi_to_style(&$input), $result);
}
};
}
// Styles
test!(bold: "1" => Style::default().bold());
test!(under: "4" => Style::default().underline());
test!(both: "1;4" => Style::default().bold().underline());
test!(fg: "31" => Red.normal());
test!(bg: "43" => Style::default().on(Yellow));
test!(bfg: "31;43" => Red.on(Yellow));
test!(all: "43;31;1;4" => Red.on(Yellow).bold().underline());
test!(again: "1;1;1;1;1" => Style::default().bold());
// Failure cases
test!(empty: "" => Style::default());
test!(semis: ";;;;;;" => Style::default());
test!(nines: "99999999" => Style::default());
test!(word: "GREEN" => Style::default());
}
#[cfg(test)]
mod test {
use super::*;
macro_rules! test {
($name:ident: $input:expr, $facet:expr => $result:expr) => {
#[test]
fn $name() {
let lsc = LSColors::parse($input);
assert_eq!(lsc.get($facet), $result.into());
assert_eq!(lsc.get(""), None);
}
};
}
// Bad parses
test!(empty: "", "di" => None);
test!(jibber: "blah", "di" => None);
test!(equals: "=", "di" => None);
test!(starts: "=di", "di" => None);
test!(ends: "id=", "id" => None);
// Foreground colours
test!(red: "di=31", "di" => Red.normal());
test!(green: "cb=32", "cb" => Green.normal());
test!(blue: "la=34", "la" => Blue.normal());
// Background colours
test!(yellow: "do=43", "do" => Style::default().on(Yellow));
test!(purple: "re=45", "re" => Style::default().on(Purple));
test!(cyan: "mi=46", "mi" => Style::default().on(Cyan));
// Bold and underline
test!(bold: "fa=1", "fa" => Style::default().bold());
test!(under: "so=4", "so" => Style::default().underline());
test!(both: "la=1;4", "la" => Style::default().bold().underline());
// More and many
test!(more_1: "me=43;21;55;34:yu=1;4;1", "me" => Blue.on(Yellow));
test!(more_2: "me=43;21;55;34:yu=1;4;1", "yu" => Style::default().bold().underline());
test!(many_1: "red=31:green=32:blue=34", "red" => Red.normal());
test!(many_2: "red=31:green=32:blue=34", "green" => Green.normal());
test!(many_3: "red=31:green=32:blue=34", "blue" => Blue.normal());
}

View File

@ -3,12 +3,14 @@ use output::file_name::FileStyle;
pub use self::cell::{TextCell, TextCellContents, DisplayWidth};
pub use self::colours::Colours;
pub use self::escape::escape;
pub use self::lsc::LSColors;
pub mod details;
pub mod file_name;
pub mod grid_details;
pub mod grid;
pub mod lines;
pub mod lsc;
pub mod table;
pub mod time;

View File

@ -1,44 +1,57 @@
use ansi_term::Style;
use output::cell::TextCell;
use output::colours::Colours;
use fs::fields as f;
impl f::Blocks {
pub fn render(&self, colours: &Colours) -> TextCell {
pub fn render<C: Colours>(&self, colours: &C) -> TextCell {
match *self {
f::Blocks::Some(ref blk) => TextCell::paint(colours.blocks, blk.to_string()),
f::Blocks::None => TextCell::blank(colours.punctuation),
f::Blocks::Some(ref blk) => TextCell::paint(colours.block_count(), blk.to_string()),
f::Blocks::None => TextCell::blank(colours.no_blocks()),
}
}
}
pub trait Colours {
fn block_count(&self) -> Style;
fn no_blocks(&self) -> Style;
}
#[cfg(test)]
pub mod test {
use output::colours::Colours;
use ansi_term::Style;
use ansi_term::Colour::*;
use super::Colours;
use output::cell::TextCell;
use fs::fields as f;
use ansi_term::Colour::*;
struct TestColours;
impl Colours for TestColours {
fn block_count(&self) -> Style { Red.blink() }
fn no_blocks(&self) -> Style { Green.italic() }
}
#[test]
fn blocklessness() {
let mut colours = Colours::default();
colours.punctuation = Green.italic();
let blox = f::Blocks::None;
let expected = TextCell::blank(Green.italic());
assert_eq!(expected, blox.render(&colours).into());
assert_eq!(expected, blox.render(&TestColours).into());
}
#[test]
fn blockfulity() {
let mut colours = Colours::default();
colours.blocks = Red.blink();
let blox = f::Blocks::Some(3005);
let expected = TextCell::paint_str(Red.blink(), "3005");
assert_eq!(expected, blox.render(&colours).into());
assert_eq!(expected, blox.render(&TestColours).into());
}
}

View File

@ -0,0 +1,31 @@
use ansi_term::{ANSIString, Style};
use fs::fields as f;
impl f::Type {
pub fn render<C: Colours>(&self, colours: &C) -> ANSIString<'static> {
match *self {
f::Type::File => colours.normal().paint("."),
f::Type::Directory => colours.directory().paint("d"),
f::Type::Pipe => colours.pipe().paint("|"),
f::Type::Link => colours.symlink().paint("l"),
f::Type::BlockDevice => colours.block_device().paint("b"),
f::Type::CharDevice => colours.char_device().paint("c"),
f::Type::Socket => colours.socket().paint("s"),
f::Type::Special => colours.special().paint("?"),
}
}
}
pub trait Colours {
fn normal(&self) -> Style;
fn directory(&self) -> Style;
fn pipe(&self) -> Style;
fn symlink(&self) -> Style;
fn block_device(&self) -> Style;
fn char_device(&self) -> Style;
fn socket(&self) -> Style;
fn special(&self) -> Style;
}

View File

@ -1,7 +1,6 @@
use ansi_term::ANSIString;
use ansi_term::{ANSIString, Style};
use output::cell::{TextCell, DisplayWidth};
use output::colours::Colours;
use fs::fields as f;
@ -17,34 +16,55 @@ impl f::Git {
}
}
impl f::GitStatus {
fn render(&self, colours: &Colours) -> ANSIString<'static> {
match *self {
f::GitStatus::NotModified => colours.punctuation.paint("-"),
f::GitStatus::New => colours.git.new.paint("N"),
f::GitStatus::Modified => colours.git.modified.paint("M"),
f::GitStatus::Deleted => colours.git.deleted.paint("D"),
f::GitStatus::Renamed => colours.git.renamed.paint("R"),
f::GitStatus::TypeChange => colours.git.typechange.paint("T"),
f::GitStatus::NotModified => colours.not_modified().paint("-"),
f::GitStatus::New => colours.new().paint("N"),
f::GitStatus::Modified => colours.modified().paint("M"),
f::GitStatus::Deleted => colours.deleted().paint("D"),
f::GitStatus::Renamed => colours.renamed().paint("R"),
f::GitStatus::TypeChange => colours.type_change().paint("T"),
}
}
}
pub trait Colours {
fn not_modified(&self) -> Style;
fn new(&self) -> Style;
fn modified(&self) -> Style;
fn deleted(&self) -> Style;
fn renamed(&self) -> Style;
fn type_change(&self) -> Style;
}
#[cfg(test)]
pub mod test {
use output::colours::Colours;
use super::Colours;
use output::cell::{TextCell, DisplayWidth};
use fs::fields as f;
use ansi_term::Colour::*;
use ansi_term::Style;
struct TestColours;
impl Colours for TestColours {
fn not_modified(&self) -> Style { Fixed(90).normal() }
fn new(&self) -> Style { Fixed(91).normal() }
fn modified(&self) -> Style { Fixed(92).normal() }
fn deleted(&self) -> Style { Fixed(93).normal() }
fn renamed(&self) -> Style { Fixed(94).normal() }
fn type_change(&self) -> Style { Fixed(95).normal() }
}
#[test]
fn git_blank() {
let mut colours = Colours::default();
colours.punctuation = Fixed(44).normal();
let stati = f::Git {
staged: f::GitStatus::NotModified,
unstaged: f::GitStatus::NotModified,
@ -53,21 +73,17 @@ pub mod test {
let expected = TextCell {
width: DisplayWidth::from(2),
contents: vec![
Fixed(44).paint("-"),
Fixed(44).paint("-"),
Fixed(90).paint("-"),
Fixed(90).paint("-"),
].into(),
};
assert_eq!(expected, stati.render(&colours).into())
assert_eq!(expected, stati.render(&TestColours).into())
}
#[test]
fn git_new_changed() {
let mut colours = Colours::default();
colours.git.new = Red.normal();
colours.git.modified = Purple.normal();
let stati = f::Git {
staged: f::GitStatus::New,
unstaged: f::GitStatus::Modified,
@ -76,11 +92,11 @@ pub mod test {
let expected = TextCell {
width: DisplayWidth::from(2),
contents: vec![
Red.paint("N"),
Purple.paint("M"),
Fixed(91).paint("N"),
Fixed(92).paint("M"),
].into(),
};
assert_eq!(expected, stati.render(&colours).into())
assert_eq!(expected, stati.render(&TestColours).into())
}
}

View File

@ -1,15 +1,15 @@
use ansi_term::Style;
use users::{Users, Groups};
use fs::fields as f;
use output::colours::Colours;
use output::cell::TextCell;
impl f::Group {
pub fn render<U: Users+Groups>(&self, colours: &Colours, users: &U) -> TextCell {
pub fn render<C: Colours, U: Users+Groups>(&self, colours: &C, users: &U) -> TextCell {
use users::os::unix::GroupExt;
let mut style = colours.users.group_not_yours;
let mut style = colours.not_yours();
let group = match users.get_group_by_gid(self.0) {
Some(g) => (*g).clone(),
@ -20,7 +20,7 @@ impl f::Group {
if let Some(current_user) = users.get_user_by_uid(current_uid) {
if current_user.primary_group_id() == group.gid()
|| group.members().contains(&current_user.name().to_owned()) {
style = colours.users.group_yours;
style = colours.yours();
}
}
@ -29,63 +29,66 @@ impl f::Group {
}
pub trait Colours {
fn yours(&self) -> Style;
fn not_yours(&self) -> Style;
}
#[cfg(test)]
#[allow(unused_results)]
pub mod test {
use super::Colours;
use fs::fields as f;
use output::cell::TextCell;
use output::colours::Colours;
use users::{User, Group};
use users::mock::MockUsers;
use users::os::unix::GroupExt;
use ansi_term::Colour::*;
use ansi_term::Style;
struct TestColours;
impl Colours for TestColours {
fn yours(&self) -> Style { Fixed(80).normal() }
fn not_yours(&self) -> Style { Fixed(81).normal() }
}
#[test]
fn named() {
let mut colours = Colours::default();
colours.users.group_not_yours = Fixed(101).normal();
let mut users = MockUsers::with_current_uid(1000);
users.add_group(Group::new(100, "folk"));
let group = f::Group(100);
let expected = TextCell::paint_str(Fixed(101).normal(), "folk");
assert_eq!(expected, group.render(&colours, &users))
let expected = TextCell::paint_str(Fixed(81).normal(), "folk");
assert_eq!(expected, group.render(&TestColours, &users))
}
#[test]
fn unnamed() {
let mut colours = Colours::default();
colours.users.group_not_yours = Fixed(87).normal();
let users = MockUsers::with_current_uid(1000);
let group = f::Group(100);
let expected = TextCell::paint_str(Fixed(87).normal(), "100");
assert_eq!(expected, group.render(&colours, &users));
let expected = TextCell::paint_str(Fixed(81).normal(), "100");
assert_eq!(expected, group.render(&TestColours, &users));
}
#[test]
fn primary() {
let mut colours = Colours::default();
colours.users.group_yours = Fixed(64).normal();
let mut users = MockUsers::with_current_uid(2);
users.add_user(User::new(2, "eve", 100));
users.add_group(Group::new(100, "folk"));
let group = f::Group(100);
let expected = TextCell::paint_str(Fixed(64).normal(), "folk");
assert_eq!(expected, group.render(&colours, &users))
let expected = TextCell::paint_str(Fixed(80).normal(), "folk");
assert_eq!(expected, group.render(&TestColours, &users))
}
#[test]
fn secondary() {
let mut colours = Colours::default();
colours.users.group_yours = Fixed(31).normal();
let mut users = MockUsers::with_current_uid(2);
users.add_user(User::new(2, "eve", 666));
@ -93,17 +96,14 @@ pub mod test {
users.add_group(test_group);
let group = f::Group(100);
let expected = TextCell::paint_str(Fixed(31).normal(), "folk");
assert_eq!(expected, group.render(&colours, &users))
let expected = TextCell::paint_str(Fixed(80).normal(), "folk");
assert_eq!(expected, group.render(&TestColours, &users))
}
#[test]
fn overflow() {
let mut colours = Colours::default();
colours.users.group_not_yours = Blue.underline();
let group = f::Group(2_147_483_648);
let expected = TextCell::paint_str(Blue.underline(), "2147483648");
assert_eq!(expected, group.render(&colours, &MockUsers::with_current_uid(0)));
let expected = TextCell::paint_str(Fixed(81).normal(), "2147483648");
assert_eq!(expected, group.render(&TestColours, &MockUsers::with_current_uid(0)));
}
}

View File

@ -1,18 +1,18 @@
use ansi_term::Style;
use output::cell::TextCell;
use output::colours::Colours;
use fs::fields as f;
impl f::Inode {
pub fn render(&self, colours: &Colours) -> TextCell {
TextCell::paint(colours.inode, self.0.to_string())
pub fn render(&self, style: Style) -> TextCell {
TextCell::paint(style, self.0.to_string())
}
}
#[cfg(test)]
pub mod test {
use output::colours::Colours;
use output::cell::TextCell;
use fs::fields as f;
@ -21,11 +21,8 @@ pub mod test {
#[test]
fn blocklessness() {
let mut colours = Colours::default();
colours.inode = Cyan.underline();
let io = f::Inode(1414213);
let expected = TextCell::paint_str(Cyan.underline(), "1414213");
assert_eq!(expected, io.render(&colours).into());
assert_eq!(expected, io.render(Cyan.underline()).into());
}
}

View File

@ -1,35 +1,47 @@
use output::cell::TextCell;
use output::colours::Colours;
use fs::fields as f;
use ansi_term::Style;
use locale::Numeric as NumericLocale;
use locale;
use output::cell::TextCell;
use fs::fields as f;
impl f::Links {
pub fn render(&self, colours: &Colours, numeric: &locale::Numeric) -> TextCell {
let style = if self.multiple { colours.links.multi_link_file }
else { colours.links.normal };
pub fn render<C: Colours>(&self, colours: &C, numeric: &NumericLocale) -> TextCell {
let style = if self.multiple { colours.multi_link_file() }
else { colours.normal() };
TextCell::paint(style, numeric.format_int(self.count))
}
}
pub trait Colours {
fn normal(&self) -> Style;
fn multi_link_file(&self) -> Style;
}
#[cfg(test)]
pub mod test {
use output::colours::Colours;
use super::Colours;
use output::cell::{TextCell, DisplayWidth};
use fs::fields as f;
use ansi_term::Colour::*;
use ansi_term::Style;
use locale;
struct TestColours;
impl Colours for TestColours {
fn normal(&self) -> Style { Blue.normal() }
fn multi_link_file(&self) -> Style { Blue.on(Red) }
}
#[test]
fn regular_file() {
let mut colours = Colours::default();
colours.links.normal = Blue.normal();
let stati = f::Links {
count: 1,
multiple: false,
@ -40,14 +52,11 @@ pub mod test {
contents: vec![ Blue.paint("1") ].into(),
};
assert_eq!(expected, stati.render(&colours, &locale::Numeric::english()).into());
assert_eq!(expected, stati.render(&TestColours, &locale::Numeric::english()).into());
}
#[test]
fn regular_directory() {
let mut colours = Colours::default();
colours.links.normal = Blue.normal();
let stati = f::Links {
count: 3005,
multiple: false,
@ -58,14 +67,11 @@ pub mod test {
contents: vec![ Blue.paint("3,005") ].into(),
};
assert_eq!(expected, stati.render(&colours, &locale::Numeric::english()).into());
assert_eq!(expected, stati.render(&TestColours, &locale::Numeric::english()).into());
}
#[test]
fn popular_file() {
let mut colours = Colours::default();
colours.links.multi_link_file = Blue.on(Red);
let stati = f::Links {
count: 3005,
multiple: true,
@ -76,6 +82,6 @@ pub mod test {
contents: vec![ Blue.on(Red).paint("3,005") ].into(),
};
assert_eq!(expected, stati.render(&colours, &locale::Numeric::english()).into());
assert_eq!(expected, stati.render(&TestColours, &locale::Numeric::english()).into());
}
}

View File

@ -1,9 +1,29 @@
mod blocks;
pub use self::blocks::Colours as BlocksColours;
mod filetype;
pub use self::filetype::Colours as FiletypeColours;
mod git;
pub use self::git::Colours as GitColours;
mod groups;
pub use self::groups::Colours as GroupColours;
mod inode;
// inode uses just one colour
mod links;
pub use self::links::Colours as LinksColours;
mod permissions;
pub use self::permissions::Colours as PermissionsColours;
mod size;
pub use self::size::Colours as SizeColours;
mod times;
// times does too
mod users;
pub use self::users::Colours as UserColours;

View File

@ -1,16 +1,17 @@
use fs::fields as f;
use output::colours::Colours;
use output::cell::{TextCell, DisplayWidth};
use ansi_term::{ANSIString, Style};
use fs::fields as f;
use output::cell::{TextCell, DisplayWidth};
use output::render::FiletypeColours;
impl f::PermissionsPlus {
pub fn render(&self, colours: &Colours) -> TextCell {
pub fn render<C: Colours+FiletypeColours>(&self, colours: &C) -> TextCell {
let mut chars = vec![ self.file_type.render(colours) ];
chars.extend(self.permissions.render(colours, self.file_type.is_regular_file()));
if self.xattrs {
chars.push(colours.perms.attribute.paint("@"));
chars.push(colours.attribute().paint("@"));
}
// As these are all ASCII characters, we can guarantee that theyre
@ -23,87 +24,115 @@ impl f::PermissionsPlus {
}
}
impl f::Permissions {
pub fn render(&self, colours: &Colours, is_regular_file: bool) -> Vec<ANSIString<'static>> {
pub fn render<C: Colours>(&self, colours: &C, is_regular_file: bool) -> Vec<ANSIString<'static>> {
let bit = |bit, chr: &'static str, style: Style| {
if bit { style.paint(chr) } else { colours.punctuation.paint("-") }
if bit { style.paint(chr) } else { colours.dash().paint("-") }
};
vec![
bit(self.user_read, "r", colours.perms.user_read),
bit(self.user_write, "w", colours.perms.user_write),
bit(self.user_read, "r", colours.user_read()),
bit(self.user_write, "w", colours.user_write()),
self.user_execute_bit(colours, is_regular_file),
bit(self.group_read, "r", colours.perms.group_read),
bit(self.group_write, "w", colours.perms.group_write),
bit(self.group_read, "r", colours.group_read()),
bit(self.group_write, "w", colours.group_write()),
self.group_execute_bit(colours),
bit(self.other_read, "r", colours.perms.other_read),
bit(self.other_write, "w", colours.perms.other_write),
bit(self.other_read, "r", colours.other_read()),
bit(self.other_write, "w", colours.other_write()),
self.other_execute_bit(colours)
]
}
fn user_execute_bit(&self, colours: &Colours, is_regular_file: bool) -> ANSIString<'static> {
fn user_execute_bit<C: Colours>(&self, colours: &C, is_regular_file: bool) -> ANSIString<'static> {
match (self.user_execute, self.setuid, is_regular_file) {
(false, false, _) => colours.punctuation.paint("-"),
(true, false, false) => colours.perms.user_execute_other.paint("x"),
(true, false, true) => colours.perms.user_execute_file.paint("x"),
(false, true, _) => colours.perms.special_other.paint("S"),
(true, true, false) => colours.perms.special_other.paint("s"),
(true, true, true) => colours.perms.special_user_file.paint("s"),
(false, false, _) => colours.dash().paint("-"),
(true, false, false) => colours.user_execute_other().paint("x"),
(true, false, true) => colours.user_execute_file().paint("x"),
(false, true, _) => colours.special_other().paint("S"),
(true, true, false) => colours.special_other().paint("s"),
(true, true, true) => colours.special_user_file().paint("s"),
}
}
fn group_execute_bit(&self, colours: &Colours) -> ANSIString<'static> {
fn group_execute_bit<C: Colours>(&self, colours: &C) -> ANSIString<'static> {
match (self.group_execute, self.setgid) {
(false, false) => colours.punctuation.paint("-"),
(true, false) => colours.perms.group_execute.paint("x"),
(false, true) => colours.perms.special_other.paint("S"),
(true, true) => colours.perms.special_other.paint("s"),
(false, false) => colours.dash().paint("-"),
(true, false) => colours.group_execute().paint("x"),
(false, true) => colours.special_other().paint("S"),
(true, true) => colours.special_other().paint("s"),
}
}
fn other_execute_bit(&self, colours: &Colours) -> ANSIString<'static> {
fn other_execute_bit<C: Colours>(&self, colours: &C) -> ANSIString<'static> {
match (self.other_execute, self.sticky) {
(false, false) => colours.punctuation.paint("-"),
(true, false) => colours.perms.other_execute.paint("x"),
(false, true) => colours.perms.special_other.paint("T"),
(true, true) => colours.perms.special_other.paint("t"),
(false, false) => colours.dash().paint("-"),
(true, false) => colours.other_execute().paint("x"),
(false, true) => colours.special_other().paint("T"),
(true, true) => colours.special_other().paint("t"),
}
}
}
impl f::Type {
pub fn render(&self, colours: &Colours) -> ANSIString<'static> {
match *self {
f::Type::File => colours.filetypes.normal.paint("."),
f::Type::Directory => colours.filetypes.directory.paint("d"),
f::Type::Pipe => colours.filetypes.pipe.paint("|"),
f::Type::Link => colours.filetypes.symlink.paint("l"),
f::Type::CharDevice => colours.filetypes.device.paint("c"),
f::Type::BlockDevice => colours.filetypes.device.paint("b"),
f::Type::Socket => colours.filetypes.socket.paint("s"),
f::Type::Special => colours.filetypes.special.paint("?"),
}
}
}
pub trait Colours {
fn dash(&self) -> Style;
fn user_read(&self) -> Style;
fn user_write(&self) -> Style;
fn user_execute_file(&self) -> Style;
fn user_execute_other(&self) -> Style;
fn group_read(&self) -> Style;
fn group_write(&self) -> Style;
fn group_execute(&self) -> Style;
fn other_read(&self) -> Style;
fn other_write(&self) -> Style;
fn other_execute(&self) -> Style;
fn special_user_file(&self) -> Style;
fn special_other(&self) -> Style;
fn attribute(&self) -> Style;
}
#[cfg(test)]
#[allow(unused_results)]
pub mod test {
use output::colours::Colours;
use super::Colours;
use output::cell::TextCellContents;
use fs::fields as f;
use ansi_term::Colour::*;
use ansi_term::Style;
struct TestColours;
impl Colours for TestColours {
fn dash(&self) -> Style { Fixed(11).normal() }
fn user_read(&self) -> Style { Fixed(101).normal() }
fn user_write(&self) -> Style { Fixed(102).normal() }
fn user_execute_file(&self) -> Style { Fixed(103).normal() }
fn user_execute_other(&self) -> Style { Fixed(113).normal() }
fn group_read(&self) -> Style { Fixed(104).normal() }
fn group_write(&self) -> Style { Fixed(105).normal() }
fn group_execute(&self) -> Style { Fixed(106).normal() }
fn other_read(&self) -> Style { Fixed(107).normal() }
fn other_write(&self) -> Style { Fixed(108).normal() }
fn other_execute(&self) -> Style { Fixed(109).normal() }
fn special_user_file(&self) -> Style { Fixed(110).normal() }
fn special_other(&self) -> Style { Fixed(111).normal() }
fn attribute(&self) -> Style { Fixed(112).normal() }
}
#[test]
fn negate() {
let mut colours = Colours::default();
colours.punctuation = Fixed(11).normal();
let bits = f::Permissions {
user_read: false, user_write: false, user_execute: false, setuid: false,
group_read: false, group_write: false, group_execute: false, setgid: false,
@ -116,25 +145,12 @@ pub mod test {
Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(11).paint("-"),
]);
assert_eq!(expected, bits.render(&colours, false).into())
assert_eq!(expected, bits.render(&TestColours, false).into())
}
#[test]
fn affirm() {
let mut colours = Colours::default();
colours.perms.user_read = Fixed(101).normal();
colours.perms.user_write = Fixed(102).normal();
colours.perms.user_execute_file = Fixed(103).normal();
colours.perms.group_read = Fixed(104).normal();
colours.perms.group_write = Fixed(105).normal();
colours.perms.group_execute = Fixed(106).normal();
colours.perms.other_read = Fixed(107).normal();
colours.perms.other_write = Fixed(108).normal();
colours.perms.other_execute = Fixed(109).normal();
let bits = f::Permissions {
user_read: true, user_write: true, user_execute: true, setuid: false,
group_read: true, group_write: true, group_execute: true, setgid: false,
@ -147,17 +163,12 @@ pub mod test {
Fixed(107).paint("r"), Fixed(108).paint("w"), Fixed(109).paint("x"),
]);
assert_eq!(expected, bits.render(&colours, true).into())
assert_eq!(expected, bits.render(&TestColours, true).into())
}
#[test]
fn specials() {
let mut colours = Colours::default();
colours.punctuation = Fixed(11).normal();
colours.perms.special_user_file = Fixed(77).normal();
colours.perms.special_other = Fixed(88).normal();
let bits = f::Permissions {
user_read: false, user_write: false, user_execute: true, setuid: true,
group_read: false, group_write: false, group_execute: true, setgid: true,
@ -165,21 +176,17 @@ pub mod test {
};
let expected = TextCellContents::from(vec![
Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(77).paint("s"),
Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(88).paint("s"),
Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(88).paint("t"),
Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(110).paint("s"),
Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(111).paint("s"),
Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(111).paint("t"),
]);
assert_eq!(expected, bits.render(&colours, true).into())
assert_eq!(expected, bits.render(&TestColours, true).into())
}
#[test]
fn extra_specials() {
let mut colours = Colours::default();
colours.punctuation = Fixed(11).normal();
colours.perms.special_other = Fixed(88).normal();
let bits = f::Permissions {
user_read: false, user_write: false, user_execute: false, setuid: true,
group_read: false, group_write: false, group_execute: false, setgid: true,
@ -187,11 +194,11 @@ pub mod test {
};
let expected = TextCellContents::from(vec![
Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(88).paint("S"),
Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(88).paint("S"),
Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(88).paint("T"),
Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(111).paint("S"),
Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(111).paint("S"),
Fixed(11).paint("-"), Fixed(11).paint("-"), Fixed(111).paint("T"),
]);
assert_eq!(expected, bits.render(&colours, true).into())
assert_eq!(expected, bits.render(&TestColours, true).into())
}
}

View File

@ -1,18 +1,20 @@
use ansi_term::Style;
use locale::Numeric as NumericLocale;
use fs::fields as f;
use output::cell::{TextCell, DisplayWidth};
use output::colours::Colours;
use output::table::SizeFormat;
use locale;
impl f::Size {
pub fn render(&self, colours: &Colours, size_format: SizeFormat, numerics: &locale::Numeric) -> TextCell {
pub fn render<C: Colours>(&self, colours: &C, size_format: SizeFormat, numerics: &NumericLocale) -> TextCell {
use number_prefix::{binary_prefix, decimal_prefix};
use number_prefix::{Prefixed, Standalone, PrefixNames};
let size = match *self {
f::Size::Some(s) => s,
f::Size::None => return TextCell::blank(colours.punctuation),
f::Size::None => return TextCell::blank(colours.no_size()),
f::Size::DeviceIDs(ref ids) => return ids.render(colours),
};
@ -21,12 +23,12 @@ impl f::Size {
SizeFormat::BinaryBytes => binary_prefix(size as f64),
SizeFormat::JustBytes => {
let string = numerics.format_int(size);
return TextCell::paint(colours.file_size(size), string);
return TextCell::paint(colours.size(size), string);
},
};
let (prefix, n) = match result {
Standalone(b) => return TextCell::paint(colours.file_size(b as u64), b.to_string()),
Standalone(b) => return TextCell::paint(colours.size(b as u64), b.to_string()),
Prefixed(p, n) => (p, n)
};
@ -41,114 +43,121 @@ impl f::Size {
TextCell {
width: width,
contents: vec![
colours.file_size(size).paint(number),
colours.size.unit.paint(symbol),
colours.size(size).paint(number),
colours.unit().paint(symbol),
].into(),
}
}
}
impl f::DeviceIDs {
fn render(&self, colours: &Colours) -> TextCell {
fn render<C: Colours>(&self, colours: &C) -> TextCell {
let major = self.major.to_string();
let minor = self.minor.to_string();
TextCell {
width: DisplayWidth::from(major.len() + 1 + minor.len()),
contents: vec![
colours.size.major.paint(major),
colours.punctuation.paint(","),
colours.size.minor.paint(minor),
colours.major().paint(major),
colours.comma().paint(","),
colours.minor().paint(minor),
].into(),
}
}
}
pub trait Colours {
fn size(&self, size: u64) -> Style;
fn unit(&self) -> Style;
fn no_size(&self) -> Style;
fn major(&self) -> Style;
fn comma(&self) -> Style;
fn minor(&self) -> Style;
}
#[cfg(test)]
pub mod test {
use output::colours::Colours;
use super::Colours;
use output::cell::{TextCell, DisplayWidth};
use output::table::SizeFormat;
use fs::fields as f;
use locale;
use locale::Numeric as NumericLocale;
use ansi_term::Colour::*;
use ansi_term::Style;
struct TestColours;
impl Colours for TestColours {
fn size(&self, _size: u64) -> Style { Fixed(66).normal() }
fn unit(&self) -> Style { Fixed(77).bold() }
fn no_size(&self) -> Style { Black.italic() }
fn major(&self) -> Style { Blue.on(Red) }
fn comma(&self) -> Style { Green.italic() }
fn minor(&self) -> Style { Cyan.on(Yellow) }
}
#[test]
fn directory() {
let mut colours = Colours::default();
colours.punctuation = Green.italic();
let directory = f::Size::None;
let expected = TextCell::blank(Green.italic());
assert_eq!(expected, directory.render(&colours, SizeFormat::JustBytes, &locale::Numeric::english()))
let expected = TextCell::blank(Black.italic());
assert_eq!(expected, directory.render(&TestColours, SizeFormat::JustBytes, &NumericLocale::english()))
}
#[test]
fn file_decimal() {
let mut colours = Colours::default();
colours.size.numbers = Blue.on(Red);
colours.size.unit = Yellow.bold();
let directory = f::Size::Some(2_100_000);
let expected = TextCell {
width: DisplayWidth::from(4),
contents: vec![
Blue.on(Red).paint("2.1"),
Yellow.bold().paint("M"),
Fixed(66).paint("2.1"),
Fixed(77).bold().paint("M"),
].into(),
};
assert_eq!(expected, directory.render(&colours, SizeFormat::DecimalBytes, &locale::Numeric::english()))
assert_eq!(expected, directory.render(&TestColours, SizeFormat::DecimalBytes, &NumericLocale::english()))
}
#[test]
fn file_binary() {
let mut colours = Colours::default();
colours.size.numbers = Blue.on(Red);
colours.size.unit = Yellow.bold();
let directory = f::Size::Some(1_048_576);
let expected = TextCell {
width: DisplayWidth::from(5),
contents: vec![
Blue.on(Red).paint("1.0"),
Yellow.bold().paint("Mi"),
Fixed(66).paint("1.0"),
Fixed(77).bold().paint("Mi"),
].into(),
};
assert_eq!(expected, directory.render(&colours, SizeFormat::BinaryBytes, &locale::Numeric::english()))
assert_eq!(expected, directory.render(&TestColours, SizeFormat::BinaryBytes, &NumericLocale::english()))
}
#[test]
fn file_bytes() {
let mut colours = Colours::default();
colours.size.numbers = Blue.on(Red);
let directory = f::Size::Some(1048576);
let expected = TextCell {
width: DisplayWidth::from(9),
contents: vec![
Blue.on(Red).paint("1,048,576"),
Fixed(66).paint("1,048,576"),
].into(),
};
assert_eq!(expected, directory.render(&colours, SizeFormat::JustBytes, &locale::Numeric::english()))
assert_eq!(expected, directory.render(&TestColours, SizeFormat::JustBytes, &NumericLocale::english()))
}
#[test]
fn device_ids() {
let mut colours = Colours::default();
colours.size.major = Blue.on(Red);
colours.punctuation = Green.italic();
colours.size.minor = Cyan.on(Yellow);
let directory = f::Size::DeviceIDs(f::DeviceIDs { major: 10, minor: 80 });
let expected = TextCell {
width: DisplayWidth::from(5),
@ -159,6 +168,6 @@ pub mod test {
].into(),
};
assert_eq!(expected, directory.render(&colours, SizeFormat::JustBytes, &locale::Numeric::english()))
assert_eq!(expected, directory.render(&TestColours, SizeFormat::JustBytes, &NumericLocale::english()))
}
}

View File

@ -1,24 +1,23 @@
use datetime::TimeZone;
use ansi_term::Style;
use fs::fields as f;
use output::cell::TextCell;
use output::colours::Colours;
use output::time::TimeFormat;
impl f::Time {
pub fn render(self, colours: &Colours,
tz: &Option<TimeZone>,
style: &TimeFormat) -> TextCell {
pub fn render(self, style: Style,
tz: &Option<TimeZone>,
format: &TimeFormat) -> TextCell {
if let Some(ref tz) = *tz {
let datestamp = style.format_zoned(self, tz);
TextCell::paint(colours.date, datestamp)
let datestamp = format.format_zoned(self, tz);
TextCell::paint(style, datestamp)
}
else {
let datestamp = style.format_local(self);
TextCell::paint(colours.date, datestamp)
let datestamp = format.format_local(self);
TextCell::paint(style, datestamp)
}
}
}

View File

@ -1,89 +1,92 @@
use ansi_term::Style;
use users::Users;
use fs::fields as f;
use output::colours::Colours;
use output::cell::TextCell;
impl f::User {
pub fn render(&self, colours: &Colours, users: &Users) -> TextCell {
pub fn render<C: Colours, U: Users>(&self, colours: &C, users: &U) -> TextCell {
let user_name = match users.get_user_by_uid(self.0) {
Some(user) => user.name().to_owned(),
None => self.0.to_string(),
};
let style = if users.get_current_uid() == self.0 { colours.users.user_you }
else { colours.users.user_someone_else };
let style = if users.get_current_uid() == self.0 { colours.you() }
else { colours.someone_else() };
TextCell::paint(style, user_name)
}
}
pub trait Colours {
fn you(&self) -> Style;
fn someone_else(&self) -> Style;
}
#[cfg(test)]
#[allow(unused_results)]
pub mod test {
use super::Colours;
use fs::fields as f;
use output::cell::TextCell;
use output::colours::Colours;
use users::User;
use users::mock::MockUsers;
use ansi_term::Colour::*;
use ansi_term::Style;
struct TestColours;
impl Colours for TestColours {
fn you(&self) -> Style { Red.bold() }
fn someone_else(&self) -> Style { Blue.underline() }
}
#[test]
fn named() {
let mut colours = Colours::default();
colours.users.user_you = Red.bold();
let mut users = MockUsers::with_current_uid(1000);
users.add_user(User::new(1000, "enoch", 100));
let user = f::User(1000);
let expected = TextCell::paint_str(Red.bold(), "enoch");
assert_eq!(expected, user.render(&colours, &users))
assert_eq!(expected, user.render(&TestColours, &users))
}
#[test]
fn unnamed() {
let mut colours = Colours::default();
colours.users.user_you = Cyan.bold();
let users = MockUsers::with_current_uid(1000);
let user = f::User(1000);
let expected = TextCell::paint_str(Cyan.bold(), "1000");
assert_eq!(expected, user.render(&colours, &users));
let expected = TextCell::paint_str(Red.bold(), "1000");
assert_eq!(expected, user.render(&TestColours, &users));
}
#[test]
fn different_named() {
let mut colours = Colours::default();
colours.users.user_someone_else = Green.bold();
let mut users = MockUsers::with_current_uid(0);
users.add_user(User::new(1000, "enoch", 100));
let user = f::User(1000);
let expected = TextCell::paint_str(Green.bold(), "enoch");
assert_eq!(expected, user.render(&colours, &users));
let expected = TextCell::paint_str(Blue.underline(), "enoch");
assert_eq!(expected, user.render(&TestColours, &users));
}
#[test]
fn different_unnamed() {
let mut colours = Colours::default();
colours.users.user_someone_else = Red.normal();
let user = f::User(1000);
let expected = TextCell::paint_str(Red.normal(), "1000");
assert_eq!(expected, user.render(&colours, &MockUsers::with_current_uid(0)));
let expected = TextCell::paint_str(Blue.underline(), "1000");
assert_eq!(expected, user.render(&TestColours, &MockUsers::with_current_uid(0)));
}
#[test]
fn overflow() {
let mut colours = Colours::default();
colours.users.user_someone_else = Blue.underline();
let user = f::User(2_147_483_648);
let expected = TextCell::paint_str(Blue.underline(), "2147483648");
assert_eq!(expected, user.render(&colours, &MockUsers::with_current_uid(0)));
assert_eq!(expected, user.render(&TestColours, &MockUsers::with_current_uid(0)));
}
}

View File

@ -342,18 +342,18 @@ impl<'a, 'f> Table<'a> {
use output::table::TimeType::*;
match *column {
Column::Permissions => self.permissions_plus(file, xattrs).render(&self.colours),
Column::FileSize => file.size().render(&self.colours, self.size_format, &self.env.numeric),
Column::HardLinks => file.links().render(&self.colours, &self.env.numeric),
Column::Inode => file.inode().render(&self.colours),
Column::Blocks => file.blocks().render(&self.colours),
Column::User => file.user().render(&self.colours, &*self.env.lock_users()),
Column::Group => file.group().render(&self.colours, &*self.env.lock_users()),
Column::GitStatus => file.git_status().render(&self.colours),
Column::Permissions => self.permissions_plus(file, xattrs).render(self.colours),
Column::FileSize => file.size().render(self.colours, self.size_format, &self.env.numeric),
Column::HardLinks => file.links().render(self.colours, &self.env.numeric),
Column::Inode => file.inode().render(self.colours.inode),
Column::Blocks => file.blocks().render(self.colours),
Column::User => file.user().render(self.colours, &*self.env.lock_users()),
Column::Group => file.group().render(self.colours, &*self.env.lock_users()),
Column::GitStatus => file.git_status().render(self.colours),
Column::Timestamp(Modified) => file.modified_time().render(&self.colours, &self.env.tz, &self.time_format),
Column::Timestamp(Created) => file.created_time().render( &self.colours, &self.env.tz, &self.time_format),
Column::Timestamp(Accessed) => file.accessed_time().render(&self.colours, &self.env.tz, &self.time_format),
Column::Timestamp(Modified) => file.modified_time().render(self.colours.date, &self.env.tz, &self.time_format),
Column::Timestamp(Created) => file.created_time() .render(self.colours.date, &self.env.tz, &self.time_format),
Column::Timestamp(Accessed) => file.accessed_time().render(self.colours.date, &self.env.tz, &self.time_format),
}
}