Browse Source

Reverse what knows how to render a cell

This commit adds many traits, all named ‘Colours’, to the code. Each one asks for a colour needed to render a cell: the number of links asks for colours for the number and the multi-link-file special case; the file size asks for number, unit, punctuation, and device ID colours, or it can do a scale with its own colours, however it wants.

This is a step towards LS_COLORS compatibility, believe it or not. If a text cell in a column doesn’t depend on Colours to render itself, then the source of the colours is open-ended.

I am glad to have not needed any test changes here.
Benjamin Sago 8 năm trước cách đây
mục cha
commit
9b24649d68

+ 90 - 22
src/output/colours.rs

@@ -1,6 +1,8 @@
 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 {
@@ -202,27 +204,93 @@ impl Colours {
             control_char:     Red.normal(),
         }
     }
+}
 
-    pub fn file_size(&self, size: u64) -> Style {
-        if self.scale {
-            if size < 1024 {
-                self.size.scale_byte
-            }
-            else if size < 1024 * 1024 {
-                self.size.scale_kilo
-            }
-            else if size < 1024 * 1024 * 1024 {
-                self.size.scale_mega
-            }
-            else if size < 1024 * 1024 * 1024 * 1024 {
-                self.size.scale_giga
-            }
-            else {
-                self.size.scale_huge
-            }
-        }
-        else {
-            self.size.numbers
-        }
-    }
+
+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.filetypes.normal }
+	fn directory(&self)  -> Style { self.filetypes.directory }
+	fn pipe(&self)       -> Style { self.filetypes.pipe }
+	fn symlink(&self)    -> Style { self.filetypes.symlink }
+	fn device(&self)     -> Style { self.filetypes.device }
+	fn socket(&self)     -> Style { self.filetypes.socket }
+	fn special(&self)    -> Style { self.filetypes.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
+			}
+			else if size < 1024 * 1024 {
+				self.size.scale_kilo
+			}
+			else if size < 1024 * 1024 * 1024 {
+				self.size.scale_mega
+			}
+			else if size < 1024 * 1024 * 1024 * 1024 {
+				self.size.scale_giga
+			}
+			else {
+				self.size.scale_huge
+			}
+		}
+		else {
+			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 }
+}
+

+ 27 - 14
src/output/render/blocks.rs

@@ -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());
     }
 }

+ 30 - 0
src/output/render/filetype.rs

@@ -0,0 +1,30 @@
+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::CharDevice  => colours.device().paint("c"),
+            f::Type::BlockDevice => colours.device().paint("b"),
+            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 device(&self) -> Style;
+    fn socket(&self) -> Style;
+    fn special(&self) -> Style;
+}

+ 38 - 22
src/output/render/git.rs

@@ -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())
     }
 }

+ 30 - 30
src/output/render/groups.rs

@@ -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)));
     }
 }

+ 5 - 8
src/output/render/inode.rs

@@ -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());
     }
 }

+ 25 - 19
src/output/render/links.rs

@@ -1,35 +1,47 @@
+use ansi_term::Style;
+use locale::Numeric as NumericLocale;
+
 use output::cell::TextCell;
-use output::colours::Colours;
 use fs::fields as f;
 
-use locale;
-
 
 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());
     }
 }

+ 20 - 0
src/output/render/mod.rs

@@ -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;

+ 86 - 79
src/output/render/permissions.rs

@@ -1,16 +1,17 @@
+use ansi_term::{ANSIString, Style};
+
 use fs::fields as f;
-use output::colours::Colours;
 use output::cell::{TextCell, DisplayWidth};
-use ansi_term::{ANSIString, Style};
+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 they’re
@@ -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())
     }
 }

+ 53 - 44
src/output/render/size.rs

@@ -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()))
     }
 }

+ 8 - 9
src/output/render/times.rs

@@ -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)
         }
     }
 }
-

+ 31 - 28
src/output/render/users.rs

@@ -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)));
     }
 }

+ 12 - 12
src/output/table.rs

@@ -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::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::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.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),
         }
     }