Merge branch 'file-namers'

This creates a new type that holds file extensions in preparation for #116. It doesn’t do anything yet, but it will!
This commit is contained in:
Benjamin Sago 2017-07-10 14:04:43 +01:00
commit f54bc41792
9 changed files with 164 additions and 137 deletions

View File

@ -168,13 +168,13 @@ impl<'w, W: Write + 'w> Exa<'w, W> {
/// printing differently... /// printing differently...
fn print_files(&mut self, dir: Option<&Dir>, files: Vec<File>) -> IOResult<()> { fn print_files(&mut self, dir: Option<&Dir>, files: Vec<File>) -> IOResult<()> {
if !files.is_empty() { 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 { match *mode {
Mode::Lines => lines::Render { files, colours, classify }.render(self.writer), Mode::Lines => lines::Render { files, colours, style }.render(self.writer),
Mode::Grid(ref opts) => grid::Render { files, colours, classify, opts }.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, classify, opts, filter: &self.options.filter, recurse: self.options.dir_action.recurse_options() }.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, classify, grid, details, filter: &self.options.filter }.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 { else {

View File

@ -7,21 +7,24 @@
use fs::File; 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 /// An “immediate” file is something that can be run or activated somehow
/// in order to kick off the build of a project. Its usually only present /// in order to kick off the build of a project. Its usually only present
/// in directories full of source code. /// in directories full of source code.
pub fn is_immediate(&self) -> bool { pub fn is_immediate(&self, file: &File) -> bool {
self.name.starts_with("README") || self.name_is_one_of( &[ file.name.starts_with("README") || file.name_is_one_of( &[
"Makefile", "Cargo.toml", "SConstruct", "CMakeLists.txt", "Makefile", "Cargo.toml", "SConstruct", "CMakeLists.txt",
"build.gradle", "Rakefile", "Gruntfile.js", "build.gradle", "Rakefile", "Gruntfile.js",
"Gruntfile.coffee", "Gruntfile.coffee",
]) ])
} }
pub fn is_image(&self) -> bool { pub fn is_image(&self, file: &File) -> bool {
self.extension_is_one_of( &[ file.extension_is_one_of( &[
"png", "jpeg", "jpg", "gif", "bmp", "tiff", "tif", "png", "jpeg", "jpg", "gif", "bmp", "tiff", "tif",
"ppm", "pgm", "pbm", "pnm", "webp", "raw", "arw", "ppm", "pgm", "pbm", "pnm", "webp", "raw", "arw",
"svg", "stl", "eps", "dvi", "ps", "cbr", "svg", "stl", "eps", "dvi", "ps", "cbr",
@ -29,92 +32,62 @@ impl<'a> File<'a> {
]) ])
} }
pub fn is_video(&self) -> bool { pub fn is_video(&self, file: &File) -> bool {
self.extension_is_one_of( &[ file.extension_is_one_of( &[
"avi", "flv", "m2v", "mkv", "mov", "mp4", "mpeg", "avi", "flv", "m2v", "mkv", "mov", "mp4", "mpeg",
"mpg", "ogm", "ogv", "vob", "wmv", "mpg", "ogm", "ogv", "vob", "wmv",
]) ])
} }
pub fn is_music(&self) -> bool { pub fn is_music(&self, file: &File) -> bool {
self.extension_is_one_of( &[ file.extension_is_one_of( &[
"aac", "m4a", "mp3", "ogg", "wma", "aac", "m4a", "mp3", "ogg", "wma",
]) ])
} }
// Lossless music, rather than any other kind of data... // Lossless music, rather than any other kind of data...
pub fn is_lossless(&self) -> bool { pub fn is_lossless(&self, file: &File) -> bool {
self.extension_is_one_of( &[ file.extension_is_one_of( &[
"alac", "ape", "flac", "wav", "alac", "ape", "flac", "wav",
]) ])
} }
pub fn is_crypto(&self) -> bool { pub fn is_crypto(&self, file: &File) -> bool {
self.extension_is_one_of( &[ file.extension_is_one_of( &[
"asc", "enc", "gpg", "pgp", "sig", "signature", "pfx", "p12", "asc", "enc", "gpg", "pgp", "sig", "signature", "pfx", "p12",
]) ])
} }
pub fn is_document(&self) -> bool { pub fn is_document(&self, file: &File) -> bool {
self.extension_is_one_of( &[ file.extension_is_one_of( &[
"djvu", "doc", "docx", "dvi", "eml", "eps", "fotd", "djvu", "doc", "docx", "dvi", "eml", "eps", "fotd",
"odp", "odt", "pdf", "ppt", "pptx", "rtf", "odp", "odt", "pdf", "ppt", "pptx", "rtf",
"xls", "xlsx", "xls", "xlsx",
]) ])
} }
pub fn is_compressed(&self) -> bool { pub fn is_compressed(&self, file: &File) -> bool {
self.extension_is_one_of( &[ file.extension_is_one_of( &[
"zip", "tar", "Z", "gz", "bz2", "a", "ar", "7z", "zip", "tar", "Z", "gz", "bz2", "a", "ar", "7z",
"iso", "dmg", "tc", "rar", "par", "tgz", "iso", "dmg", "tc", "rar", "par", "tgz",
]) ])
} }
pub fn is_temp(&self) -> bool { pub fn is_temp(&self, file: &File) -> bool {
self.name.ends_with('~') file.name.ends_with('~')
|| (self.name.starts_with('#') && self.name.ends_with('#')) || (file.name.starts_with('#') && file.name.ends_with('#'))
|| self.extension_is_one_of( &[ "tmp", "swp", "swo", "swn", "bak" ]) || file.extension_is_one_of( &[ "tmp", "swp", "swo", "swn", "bak" ])
} }
pub fn is_compiled(&self) -> bool { pub fn is_compiled(&self, file: &File) -> bool {
if self.extension_is_one_of( &[ "class", "elc", "hi", "o", "pyc" ]) { if file.extension_is_one_of( &[ "class", "elc", "hi", "o", "pyc" ]) {
true true
} }
else if let Some(dir) = self.parent_dir { else if let Some(dir) = file.parent_dir {
self.get_source_files().iter().any(|path| dir.contains(path)) file.get_source_files().iter().any(|path| dir.contains(path))
} }
else { else {
false 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())
}
}

View File

@ -1,7 +1,7 @@
//! The "info" module contains routines that aren't about probing the //! The “info” module contains routines that arent about probing the
//! filesystem nor displaying output to the user, but are internal "business //! filesystem nor displaying output to the user, but are internal business
//! logic” routines that are performed on a files already-read metadata. //! logic” routines that are performed on a files already-read metadata.
//! (This counts the file name as metadata.) //! (This counts the file name as metadata.)
mod filetype; pub mod filetype;
mod sources; mod sources;

View File

@ -2,10 +2,11 @@ use std::env::var_os;
use getopts; use getopts;
use info::filetype::FileExtensions;
use output::Colours; use output::Colours;
use output::{grid, details}; use output::{grid, details};
use output::table::{TimeTypes, Environment, SizeFormat, Options as TableOptions}; 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 output::time::TimeFormat;
use options::Misfire; use options::Misfire;
use fs::feature::xattr; use fs::feature::xattr;
@ -16,17 +17,17 @@ use fs::feature::xattr;
pub struct View { pub struct View {
pub mode: Mode, pub mode: Mode,
pub colours: Colours, pub colours: Colours,
pub classify: Classify, pub style: FileStyle,
} }
impl View { impl View {
/// Determine which view to use and all of that views arguments. /// Determine which view to use and all of that views arguments.
pub fn deduce(matches: &getopts::Matches) -> Result<View, Misfire> { pub fn deduce(matches: &getopts::Matches) -> Result<View, Misfire> {
let mode = Mode::deduce(matches)?; let mode = Mode::deduce(matches)?;
let colours = Colours::deduce(matches)?; let colours = Colours::deduce(matches)?;
let classify = Classify::deduce(matches); let style = FileStyle::deduce(matches);
Ok(View { mode, colours, classify }) 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 { impl Classify {
fn deduce(matches: &getopts::Matches) -> Classify { fn deduce(matches: &getopts::Matches) -> Classify {
if matches.opt_present("classify") { Classify::AddFileIndicators } if matches.opt_present("classify") { Classify::AddFileIndicators }

View File

@ -70,7 +70,7 @@ use options::{FileFilter, RecurseOptions};
use output::colours::Colours; use output::colours::Colours;
use output::cell::TextCell; use output::cell::TextCell;
use output::tree::{TreeTrunk, TreeParams, TreeDepth}; 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}; use output::table::{Table, Options as TableOptions, Row as TableRow};
@ -107,7 +107,7 @@ pub struct Render<'a> {
pub dir: Option<&'a Dir>, pub dir: Option<&'a Dir>,
pub files: Vec<File<'a>>, pub files: Vec<File<'a>>,
pub colours: &'a Colours, pub colours: &'a Colours,
pub classify: Classify, pub style: &'a FileStyle,
pub opts: &'a Options, pub opts: &'a Options,
/// Whether to recurse through directories with a tree view, and if so, /// Whether to recurse through directories with a tree view, and if so,
@ -233,7 +233,9 @@ impl<'a> Render<'a> {
let row = Row { let row = Row {
tree: tree_params, tree: tree_params,
cells: egg.table_row, 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); rows.push(row);

View File

@ -3,11 +3,76 @@ use std::path::Path;
use ansi_term::{ANSIString, Style}; use ansi_term::{ANSIString, Style};
use fs::{File, FileTarget}; use fs::{File, FileTarget};
use info::filetype::FileExtensions;
use output::Colours; use output::Colours;
use output::escape; use output::escape;
use output::cell::TextCellContents; 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 files 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 theyre
/// a broken link or cant 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 its
/// a broken link, and doing nothing if it cant 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 /// A **file name** holds all the information necessary to display the name
/// of the given file. This is used in all of the views. /// of the given file. This is used in all of the views.
pub struct FileName<'a, 'dir: 'a> { 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. /// Whether to append file class characters to file names.
classify: Classify, classify: Classify,
/// Mapping of file extensions to colours, to highlight regular files.
exts: &'a FileExtensions,
} }
impl<'a, 'dir> FileName<'a, 'dir> { impl<'a, 'dir> FileName<'a, 'dir> {
/// Create a new `FileName` that prints the given files name, painting it /// Sets the flag on this file name to display link targets with an
/// with the remaining arguments. /// arrow followed by their path.
pub fn new(file: &'a File<'dir>, link_style: LinkStyle, classify: Classify, colours: &'a Colours) -> FileName<'a, 'dir> { pub fn with_link_paths(mut self) -> Self {
let target = if file.is_link() { Some(file.link_target()) } self.link_style = LinkStyle::FullLinkPaths;
else { None }; self
FileName { file, colours, target, link_style, classify }
} }
/// Paints the name of the file using the colours, resulting in a vector /// Paints the name of the file using the colours, resulting in a vector
/// of coloured cells that can be printed to the terminal. /// 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() { 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() { for bit in target.coloured_file_name() {
bits.push(bit); bits.push(bit);
} }
@ -181,52 +255,18 @@ impl<'a, 'dir> FileName<'a, 'dir> {
| f.is_block_device() => self.colours.filetypes.device, | f.is_block_device() => self.colours.filetypes.device,
f if f.is_socket() => self.colours.filetypes.socket, f if f.is_socket() => self.colours.filetypes.socket,
f if !f.is_file() => self.colours.filetypes.special, 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 self.exts.is_immediate(f) => self.colours.filetypes.immediate,
f if f.is_video() => self.colours.filetypes.video, f if self.exts.is_image(f) => self.colours.filetypes.image,
f if f.is_music() => self.colours.filetypes.music, f if self.exts.is_video(f) => self.colours.filetypes.video,
f if f.is_lossless() => self.colours.filetypes.lossless, f if self.exts.is_music(f) => self.colours.filetypes.music,
f if f.is_crypto() => self.colours.filetypes.crypto, f if self.exts.is_lossless(f) => self.colours.filetypes.lossless,
f if f.is_document() => self.colours.filetypes.document, f if self.exts.is_crypto(f) => self.colours.filetypes.crypto,
f if f.is_compressed() => self.colours.filetypes.compressed, f if self.exts.is_document(f) => self.colours.filetypes.document,
f if f.is_temp() => self.colours.filetypes.temp, f if self.exts.is_compressed(f) => self.colours.filetypes.compressed,
f if f.is_compiled() => self.colours.filetypes.compiled, f if self.exts.is_temp(f) => self.colours.filetypes.temp,
_ => self.colours.filetypes.normal, 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 theyre
/// a broken link or cant 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 its
/// a broken link, and doing nothing if it cant 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
}
}

View File

@ -4,7 +4,7 @@ use term_grid as tg;
use fs::File; use fs::File;
use output::colours::Colours; use output::colours::Colours;
use output::file_name::{FileName, LinkStyle, Classify}; use output::file_name::FileStyle;
#[derive(PartialEq, Debug, Copy, Clone)] #[derive(PartialEq, Debug, Copy, Clone)]
@ -24,7 +24,7 @@ impl Options {
pub struct Render<'a> { pub struct Render<'a> {
pub files: Vec<File<'a>>, pub files: Vec<File<'a>>,
pub colours: &'a Colours, pub colours: &'a Colours,
pub classify: Classify, pub style: &'a FileStyle,
pub opts: &'a Options, pub opts: &'a Options,
} }
@ -38,7 +38,7 @@ impl<'a> Render<'a> {
grid.reserve(self.files.len()); grid.reserve(self.files.len());
for file in self.files.iter() { 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(); let width = filename.width();
grid.add(tg::Cell { grid.add(tg::Cell {
@ -52,8 +52,10 @@ impl<'a> Render<'a> {
} }
else { else {
// File names too long for a grid - drop down to just listing them! // File names too long for a grid - drop down to just listing them!
// This isnt *quite* the same as the lines view, which also
// displays full link paths.
for file in self.files.iter() { 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())?; writeln!(w, "{}", name_cell.strings())?;
} }
Ok(()) Ok(())

View File

@ -11,7 +11,7 @@ use output::cell::TextCell;
use output::colours::Colours; use output::colours::Colours;
use output::details::{Options as DetailsOptions, Row as DetailsRow, Render as DetailsRender}; use output::details::{Options as DetailsOptions, Row as DetailsRow, Render as DetailsRender};
use output::grid::Options as GridOptions; 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::table::{Table, Row as TableRow, Options as TableOptions};
use output::tree::{TreeParams, TreeDepth}; use output::tree::{TreeParams, TreeDepth};
@ -20,7 +20,7 @@ pub struct Render<'a> {
pub dir: Option<&'a Dir>, pub dir: Option<&'a Dir>,
pub files: Vec<File<'a>>, pub files: Vec<File<'a>>,
pub colours: &'a Colours, pub colours: &'a Colours,
pub classify: Classify, pub style: &'a FileStyle,
pub grid: &'a GridOptions, pub grid: &'a GridOptions,
pub details: &'a DetailsOptions, pub details: &'a DetailsOptions,
pub filter: &'a FileFilter, pub filter: &'a FileFilter,
@ -32,7 +32,7 @@ impl<'a> Render<'a> {
dir: self.dir.clone(), dir: self.dir.clone(),
files: Vec::new(), files: Vec::new(),
colours: self.colours, colours: self.colours,
classify: self.classify, style: self.style,
opts: self.details, opts: self.details,
recurse: None, recurse: None,
filter: self.filter, filter: self.filter,
@ -52,7 +52,7 @@ impl<'a> Render<'a> {
.collect::<Vec<TableRow>>(); .collect::<Vec<TableRow>>();
let file_names = self.files.iter() 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::<Vec<TextCell>>(); .collect::<Vec<TextCell>>();
let mut last_working_table = self.make_grid(1, options, &file_names, rows.clone(), &drender); let mut last_working_table = self.make_grid(1, options, &file_names, rows.clone(), &drender);

View File

@ -4,7 +4,7 @@ use ansi_term::ANSIStrings;
use fs::File; use fs::File;
use output::file_name::{FileName, LinkStyle, Classify}; use output::file_name::{FileName, FileStyle};
use super::colours::Colours; use super::colours::Colours;
@ -12,7 +12,7 @@ use super::colours::Colours;
pub struct Render<'a> { pub struct Render<'a> {
pub files: Vec<File<'a>>, pub files: Vec<File<'a>>,
pub colours: &'a Colours, pub colours: &'a Colours,
pub classify: Classify, pub style: &'a FileStyle,
} }
impl<'a> Render<'a> { impl<'a> Render<'a> {
@ -26,6 +26,6 @@ impl<'a> Render<'a> {
} }
fn render_file<'f>(&self, file: &'f File<'a>) -> FileName<'f, '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()
} }
} }