提交 f54bc417 编写于 作者: B Benjamin Sago

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!
......@@ -168,13 +168,13 @@ impl<'w, W: Write + 'w> Exa<'w, W> {
/// printing differently...
fn print_files(&mut self, dir: Option<&Dir>, files: Vec<File>) -> 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 {
......
......@@ -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())
}
}
//! 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;
......@@ -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<View, Misfire> {
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 }
......
......@@ -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<File<'a>>,
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);
......
......@@ -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,
}
}
}
/// 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
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,
}
}
}
......@@ -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<File<'a>>,
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(())
......
......@@ -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<File<'a>>,
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::<Vec<TableRow>>();
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>>();
let mut last_working_table = self.make_grid(1, options, &file_names, rows.clone(), &drender);
......
......@@ -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<File<'a>>,
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()
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册