diff --git a/src/exa.rs b/src/exa.rs index 061ff02..2434bc0 100644 --- a/src/exa.rs +++ b/src/exa.rs @@ -168,13 +168,13 @@ impl<'w, W: Write + 'w> Exa<'w, W> { /// printing differently... fn print_files(&mut self, dir: Option<&Dir>, files: Vec) -> IOResult<()> { if !files.is_empty() { - let View { ref mode, ref colours, classify } = self.options.view; + let View { ref mode, ref colours, ref style } = self.options.view; match *mode { - Mode::Lines => lines::Render { files, colours, classify }.render(self.writer), - Mode::Grid(ref opts) => grid::Render { files, colours, classify, opts }.render(self.writer), - Mode::Details(ref opts) => details::Render { dir, files, colours, classify, opts, filter: &self.options.filter, recurse: self.options.dir_action.recurse_options() }.render(self.writer), - Mode::GridDetails(ref grid, ref details) => grid_details::Render { dir, files, colours, classify, grid, details, filter: &self.options.filter }.render(self.writer), + Mode::Lines => lines::Render { files, colours, style }.render(self.writer), + Mode::Grid(ref opts) => grid::Render { files, colours, style, opts }.render(self.writer), + Mode::Details(ref opts) => details::Render { dir, files, colours, style, opts, filter: &self.options.filter, recurse: self.options.dir_action.recurse_options() }.render(self.writer), + Mode::GridDetails(ref grid, ref details) => grid_details::Render { dir, files, colours, style, grid, details, filter: &self.options.filter }.render(self.writer), } } else { diff --git a/src/info/filetype.rs b/src/info/filetype.rs index 47af438..608aff7 100644 --- a/src/info/filetype.rs +++ b/src/info/filetype.rs @@ -7,21 +7,24 @@ use fs::File; -impl<'a> File<'a> { +#[derive(Debug)] +pub struct FileExtensions; + +impl FileExtensions { /// An “immediate” file is something that can be run or activated somehow /// in order to kick off the build of a project. It’s usually only present /// in directories full of source code. - pub fn is_immediate(&self) -> bool { - self.name.starts_with("README") || self.name_is_one_of( &[ + pub fn is_immediate(&self, file: &File) -> bool { + file.name.starts_with("README") || file.name_is_one_of( &[ "Makefile", "Cargo.toml", "SConstruct", "CMakeLists.txt", "build.gradle", "Rakefile", "Gruntfile.js", "Gruntfile.coffee", ]) } - pub fn is_image(&self) -> bool { - self.extension_is_one_of( &[ + pub fn is_image(&self, file: &File) -> bool { + file.extension_is_one_of( &[ "png", "jpeg", "jpg", "gif", "bmp", "tiff", "tif", "ppm", "pgm", "pbm", "pnm", "webp", "raw", "arw", "svg", "stl", "eps", "dvi", "ps", "cbr", @@ -29,92 +32,62 @@ impl<'a> File<'a> { ]) } - pub fn is_video(&self) -> bool { - self.extension_is_one_of( &[ + pub fn is_video(&self, file: &File) -> bool { + file.extension_is_one_of( &[ "avi", "flv", "m2v", "mkv", "mov", "mp4", "mpeg", "mpg", "ogm", "ogv", "vob", "wmv", ]) } - pub fn is_music(&self) -> bool { - self.extension_is_one_of( &[ + pub fn is_music(&self, file: &File) -> bool { + file.extension_is_one_of( &[ "aac", "m4a", "mp3", "ogg", "wma", ]) } // Lossless music, rather than any other kind of data... - pub fn is_lossless(&self) -> bool { - self.extension_is_one_of( &[ + pub fn is_lossless(&self, file: &File) -> bool { + file.extension_is_one_of( &[ "alac", "ape", "flac", "wav", ]) } - pub fn is_crypto(&self) -> bool { - self.extension_is_one_of( &[ + pub fn is_crypto(&self, file: &File) -> bool { + file.extension_is_one_of( &[ "asc", "enc", "gpg", "pgp", "sig", "signature", "pfx", "p12", ]) } - pub fn is_document(&self) -> bool { - self.extension_is_one_of( &[ + pub fn is_document(&self, file: &File) -> bool { + file.extension_is_one_of( &[ "djvu", "doc", "docx", "dvi", "eml", "eps", "fotd", "odp", "odt", "pdf", "ppt", "pptx", "rtf", "xls", "xlsx", ]) } - pub fn is_compressed(&self) -> bool { - self.extension_is_one_of( &[ + pub fn is_compressed(&self, file: &File) -> bool { + file.extension_is_one_of( &[ "zip", "tar", "Z", "gz", "bz2", "a", "ar", "7z", "iso", "dmg", "tc", "rar", "par", "tgz", ]) } - pub fn is_temp(&self) -> bool { - self.name.ends_with('~') - || (self.name.starts_with('#') && self.name.ends_with('#')) - || self.extension_is_one_of( &[ "tmp", "swp", "swo", "swn", "bak" ]) + pub fn is_temp(&self, file: &File) -> bool { + file.name.ends_with('~') + || (file.name.starts_with('#') && file.name.ends_with('#')) + || file.extension_is_one_of( &[ "tmp", "swp", "swo", "swn", "bak" ]) } - pub fn is_compiled(&self) -> bool { - if self.extension_is_one_of( &[ "class", "elc", "hi", "o", "pyc" ]) { + pub fn is_compiled(&self, file: &File) -> bool { + if file.extension_is_one_of( &[ "class", "elc", "hi", "o", "pyc" ]) { true } - else if let Some(dir) = self.parent_dir { - self.get_source_files().iter().any(|path| dir.contains(path)) + else if let Some(dir) = file.parent_dir { + file.get_source_files().iter().any(|path| dir.contains(path)) } else { false } } } - - -#[cfg(broken_test)] -mod test { - use file::test::{dummy_stat, new_file}; - - #[test] - fn lowercase() { - let file = new_file(dummy_stat(), "/barracks.wav"); - assert_eq!(FileType::Lossless, file.get_type()) - } - - #[test] - fn uppercase() { - let file = new_file(dummy_stat(), "/BARRACKS.WAV"); - assert_eq!(FileType::Lossless, file.get_type()) - } - - #[test] - fn cargo() { - let file = new_file(dummy_stat(), "/Cargo.toml"); - assert_eq!(FileType::Immediate, file.get_type()) - } - - #[test] - fn not_cargo() { - let file = new_file(dummy_stat(), "/cargo.toml"); - assert_eq!(FileType::Normal, file.get_type()) - } -} diff --git a/src/info/mod.rs b/src/info/mod.rs index ba00019..1fdd535 100644 --- a/src/info/mod.rs +++ b/src/info/mod.rs @@ -1,7 +1,7 @@ -//! The "info" module contains routines that aren't about probing the -//! filesystem nor displaying output to the user, but are internal "business +//! The “info” module contains routines that aren’t about probing the +//! filesystem nor displaying output to the user, but are internal “business //! logic” routines that are performed on a file’s already-read metadata. //! (This counts the file name as metadata.) -mod filetype; +pub mod filetype; mod sources; diff --git a/src/options/view.rs b/src/options/view.rs index 2f1c7fa..03ae3f0 100644 --- a/src/options/view.rs +++ b/src/options/view.rs @@ -2,10 +2,11 @@ use std::env::var_os; use getopts; +use info::filetype::FileExtensions; use output::Colours; use output::{grid, details}; use output::table::{TimeTypes, Environment, SizeFormat, Options as TableOptions}; -use output::file_name::Classify; +use output::file_name::{Classify, FileStyle}; use output::time::TimeFormat; use options::Misfire; use fs::feature::xattr; @@ -16,17 +17,17 @@ use fs::feature::xattr; pub struct View { pub mode: Mode, pub colours: Colours, - pub classify: Classify, + pub style: FileStyle, } impl View { /// Determine which view to use and all of that view’s arguments. pub fn deduce(matches: &getopts::Matches) -> Result { - let mode = Mode::deduce(matches)?; - let colours = Colours::deduce(matches)?; - let classify = Classify::deduce(matches); - Ok(View { mode, colours, classify }) + let mode = Mode::deduce(matches)?; + let colours = Colours::deduce(matches)?; + let style = FileStyle::deduce(matches); + Ok(View { mode, colours, style }) } } @@ -370,6 +371,15 @@ impl Colours { +impl FileStyle { + fn deduce(matches: &getopts::Matches) -> FileStyle { + let classify = Classify::deduce(matches); + let exts = FileExtensions; + FileStyle { classify, exts } + } + +} + impl Classify { fn deduce(matches: &getopts::Matches) -> Classify { if matches.opt_present("classify") { Classify::AddFileIndicators } diff --git a/src/output/details.rs b/src/output/details.rs index 09cf475..8371a34 100644 --- a/src/output/details.rs +++ b/src/output/details.rs @@ -70,7 +70,7 @@ use options::{FileFilter, RecurseOptions}; use output::colours::Colours; use output::cell::TextCell; use output::tree::{TreeTrunk, TreeParams, TreeDepth}; -use output::file_name::{FileName, LinkStyle, Classify}; +use output::file_name::FileStyle; use output::table::{Table, Options as TableOptions, Row as TableRow}; @@ -107,7 +107,7 @@ pub struct Render<'a> { pub dir: Option<&'a Dir>, pub files: Vec>, pub colours: &'a Colours, - pub classify: Classify, + pub style: &'a FileStyle, pub opts: &'a Options, /// Whether to recurse through directories with a tree view, and if so, @@ -233,7 +233,9 @@ impl<'a> Render<'a> { let row = Row { tree: tree_params, cells: egg.table_row, - name: FileName::new(&egg.file, LinkStyle::FullLinkPaths, self.classify, self.colours).paint().promote(), + name: self.style.for_file(&egg.file, self.colours) + .with_link_paths() + .paint().promote(), }; rows.push(row); diff --git a/src/output/file_name.rs b/src/output/file_name.rs index a2b138a..bfb5b31 100644 --- a/src/output/file_name.rs +++ b/src/output/file_name.rs @@ -3,11 +3,76 @@ use std::path::Path; use ansi_term::{ANSIString, Style}; use fs::{File, FileTarget}; +use info::filetype::FileExtensions; use output::Colours; use output::escape; use output::cell::TextCellContents; +/// Basically a file name factory. +#[derive(Debug)] +pub struct FileStyle { + + /// Whether to append file class characters to file names. + pub classify: Classify, + + /// Mapping of file extensions to colours, to highlight regular files. + pub exts: FileExtensions, +} + +impl FileStyle { + + /// Create a new `FileName` that prints the given file’s name, painting it + /// with the remaining arguments. + pub fn for_file<'a, 'dir>(&'a self, file: &'a File<'dir>, colours: &'a Colours) -> FileName<'a, 'dir> { + FileName { + file, colours, + link_style: LinkStyle::JustFilenames, + exts: &self.exts, + classify: self.classify, + target: if file.is_link() { Some(file.link_target()) } + else { None } + } + } +} + + +/// When displaying a file name, there needs to be some way to handle broken +/// links, depending on how long the resulting Cell can be. +#[derive(PartialEq, Debug, Copy, Clone)] +enum LinkStyle { + + /// Just display the file names, but colour them differently if they’re + /// a broken link or can’t be followed. + JustFilenames, + + /// Display all files in their usual style, but follow each link with an + /// arrow pointing to their path, colouring the path differently if it’s + /// a broken link, and doing nothing if it can’t be followed. + FullLinkPaths, +} + + +/// Whether to append file class characters to the file names. +#[derive(PartialEq, Debug, Copy, Clone)] +pub enum Classify { + + /// Just display the file names, without any characters. + JustFilenames, + + /// Add a character after the file name depending on what class of file + /// it is. + AddFileIndicators, +} + +impl Default for Classify { + fn default() -> Classify { + Classify::JustFilenames + } +} + + + /// A **file name** holds all the information necessary to display the name /// of the given file. This is used in all of the views. pub struct FileName<'a, 'dir: 'a> { @@ -26,20 +91,21 @@ pub struct FileName<'a, 'dir: 'a> { /// Whether to append file class characters to file names. classify: Classify, + + /// Mapping of file extensions to colours, to highlight regular files. + exts: &'a FileExtensions, } impl<'a, 'dir> FileName<'a, 'dir> { - /// Create a new `FileName` that prints the given file’s name, painting it - /// with the remaining arguments. - pub fn new(file: &'a File<'dir>, link_style: LinkStyle, classify: Classify, colours: &'a Colours) -> FileName<'a, 'dir> { - let target = if file.is_link() { Some(file.link_target()) } - else { None }; - FileName { file, colours, target, link_style, classify } + /// Sets the flag on this file name to display link targets with an + /// arrow followed by their path. + pub fn with_link_paths(mut self) -> Self { + self.link_style = LinkStyle::FullLinkPaths; + self } - /// Paints the name of the file using the colours, resulting in a vector /// of coloured cells that can be printed to the terminal. /// @@ -73,7 +139,15 @@ impl<'a, 'dir> FileName<'a, 'dir> { } if !target.name.is_empty() { - let target = FileName::new(target, LinkStyle::FullLinkPaths, Classify::JustFilenames, self.colours); + let target = FileName { + file: target, + colours: self.colours, + target: None, + link_style: LinkStyle::FullLinkPaths, + classify: Classify::JustFilenames, + exts: self.exts, + }; + for bit in target.coloured_file_name() { bits.push(bit); } @@ -181,52 +255,18 @@ impl<'a, 'dir> FileName<'a, 'dir> { | 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_immediate() => self.colours.filetypes.immediate, - f if f.is_image() => self.colours.filetypes.image, - f if f.is_video() => self.colours.filetypes.video, - f if f.is_music() => self.colours.filetypes.music, - f if f.is_lossless() => self.colours.filetypes.lossless, - f if f.is_crypto() => self.colours.filetypes.crypto, - f if f.is_document() => self.colours.filetypes.document, - f if f.is_compressed() => self.colours.filetypes.compressed, - f if f.is_temp() => self.colours.filetypes.temp, - f if f.is_compiled() => self.colours.filetypes.compiled, - _ => self.colours.filetypes.normal, + + f if self.exts.is_immediate(f) => self.colours.filetypes.immediate, + f if self.exts.is_image(f) => self.colours.filetypes.image, + f if self.exts.is_video(f) => self.colours.filetypes.video, + f if self.exts.is_music(f) => self.colours.filetypes.music, + f if self.exts.is_lossless(f) => self.colours.filetypes.lossless, + f if self.exts.is_crypto(f) => self.colours.filetypes.crypto, + f if self.exts.is_document(f) => self.colours.filetypes.document, + 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, } } } - - -/// When displaying a file name, there needs to be some way to handle broken -/// links, depending on how long the resulting Cell can be. -#[derive(PartialEq, Debug, Copy, Clone)] -pub enum LinkStyle { - - /// Just display the file names, but colour them differently if they’re - /// a broken link or can’t be followed. - JustFilenames, - - /// Display all files in their usual style, but follow each link with an - /// arrow pointing to their path, colouring the path differently if it’s - /// a broken link, and doing nothing if it can’t be followed. - FullLinkPaths, -} - - -/// Whether to append file class characters to the file names. -#[derive(PartialEq, Debug, Copy, Clone)] -pub enum Classify { - - /// Just display the file names, without any characters. - JustFilenames, - - /// Add a character after the file name depending on what class of file - /// it is. - AddFileIndicators, -} - -impl Default for Classify { - fn default() -> Classify { - Classify::JustFilenames - } -} diff --git a/src/output/grid.rs b/src/output/grid.rs index 7f9637c..a28a409 100644 --- a/src/output/grid.rs +++ b/src/output/grid.rs @@ -4,7 +4,7 @@ use term_grid as tg; use fs::File; use output::colours::Colours; -use output::file_name::{FileName, LinkStyle, Classify}; +use output::file_name::FileStyle; #[derive(PartialEq, Debug, Copy, Clone)] @@ -24,7 +24,7 @@ impl Options { pub struct Render<'a> { pub files: Vec>, pub colours: &'a Colours, - pub classify: Classify, + pub style: &'a FileStyle, pub opts: &'a Options, } @@ -38,7 +38,7 @@ impl<'a> Render<'a> { grid.reserve(self.files.len()); for file in self.files.iter() { - let filename = FileName::new(file, LinkStyle::JustFilenames, self.classify, self.colours).paint(); + let filename = self.style.for_file(file, self.colours).paint(); let width = filename.width(); grid.add(tg::Cell { @@ -52,8 +52,10 @@ impl<'a> Render<'a> { } else { // File names too long for a grid - drop down to just listing them! + // This isn’t *quite* the same as the lines view, which also + // displays full link paths. for file in self.files.iter() { - let name_cell = FileName::new(file, LinkStyle::JustFilenames, self.classify, self.colours).paint(); + let name_cell = self.style.for_file(file, self.colours).paint(); writeln!(w, "{}", name_cell.strings())?; } Ok(()) diff --git a/src/output/grid_details.rs b/src/output/grid_details.rs index 788c3f2..1f1ffc6 100644 --- a/src/output/grid_details.rs +++ b/src/output/grid_details.rs @@ -11,7 +11,7 @@ use output::cell::TextCell; use output::colours::Colours; use output::details::{Options as DetailsOptions, Row as DetailsRow, Render as DetailsRender}; use output::grid::Options as GridOptions; -use output::file_name::{FileName, LinkStyle, Classify}; +use output::file_name::FileStyle; use output::table::{Table, Row as TableRow, Options as TableOptions}; use output::tree::{TreeParams, TreeDepth}; @@ -20,7 +20,7 @@ pub struct Render<'a> { pub dir: Option<&'a Dir>, pub files: Vec>, pub colours: &'a Colours, - pub classify: Classify, + pub style: &'a FileStyle, pub grid: &'a GridOptions, pub details: &'a DetailsOptions, pub filter: &'a FileFilter, @@ -32,7 +32,7 @@ impl<'a> Render<'a> { dir: self.dir.clone(), files: Vec::new(), colours: self.colours, - classify: self.classify, + style: self.style, opts: self.details, recurse: None, filter: self.filter, @@ -52,7 +52,7 @@ impl<'a> Render<'a> { .collect::>(); let file_names = self.files.iter() - .map(|file| FileName::new(file, LinkStyle::JustFilenames, self.classify, self.colours).paint().promote()) + .map(|file| self.style.for_file(file, self.colours).paint().promote()) .collect::>(); let mut last_working_table = self.make_grid(1, options, &file_names, rows.clone(), &drender); diff --git a/src/output/lines.rs b/src/output/lines.rs index 19a86d7..8ea73d2 100644 --- a/src/output/lines.rs +++ b/src/output/lines.rs @@ -4,7 +4,7 @@ use ansi_term::ANSIStrings; use fs::File; -use output::file_name::{FileName, LinkStyle, Classify}; +use output::file_name::{FileName, FileStyle}; use super::colours::Colours; @@ -12,7 +12,7 @@ use super::colours::Colours; pub struct Render<'a> { pub files: Vec>, pub colours: &'a Colours, - pub classify: Classify, + pub style: &'a FileStyle, } impl<'a> Render<'a> { @@ -26,6 +26,6 @@ impl<'a> Render<'a> { } fn render_file<'f>(&self, file: &'f File<'a>) -> FileName<'f, 'a> { - FileName::new(file, LinkStyle::FullLinkPaths, self.classify, self.colours) + self.style.for_file(file, self.colours).with_link_paths() } }