Create Render structs from views

Instead of having render methods on the types that are now called Options, create new Render structs (one per view) and execute them. This means that it’s easier to extract methods from them — some of them are pretty long.

Also, remove the GridDetails struct, which got consumed by Mode (mostly)

By introducing another indirection between the structs that command-line options get parsed into and the structs that get rendered, it should be easier to refactor that horrible function in view.rs.
This commit is contained in:
Benjamin Sago 2017-06-26 00:53:48 +01:00
parent 66491cbae3
commit 14144e2ad3
8 changed files with 129 additions and 93 deletions

View File

@ -31,7 +31,7 @@ use ansi_term::{ANSIStrings, Style};
use fs::{Dir, File};
use options::{Options, View, Mode};
pub use options::Misfire;
use output::{escape, lines};
use output::{escape, lines, grid, grid_details, details};
mod fs;
mod info;
@ -168,12 +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, colours, classify } = self.options.view;
let View { ref mode, ref colours, classify } = self.options.view;
match *mode {
Mode::Grid(ref g) => g.view(&files, self.writer, &colours, classify),
Mode::Details(ref d) => d.view(dir, files, self.writer, &colours, classify),
Mode::GridDetails(ref gd) => gd.view(dir, files, self.writer, &colours, classify),
Mode::Lines => lines::view(files, self.writer, &colours, classify),
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 }.render(self.writer),
Mode::GridDetails(ref grid, ref details) => grid_details::Render { dir, files, colours, classify, grid, details }.render(self.writer),
}
}
else {

View File

@ -3,7 +3,7 @@ use std::ffi::OsStr;
use getopts;
use fs::feature::xattr;
use output::{Details, GridDetails};
use output::details;
mod dir_action;
pub use self::dir_action::{DirAction, RecurseOptions};
@ -124,8 +124,8 @@ impl Options {
/// results will end up being displayed.
pub fn should_scan_for_git(&self) -> bool {
match self.view.mode {
Mode::Details(Details { columns: Some(cols), .. }) |
Mode::GridDetails(GridDetails { details: Details { columns: Some(cols), .. }, .. }) => cols.should_scan_for_git(),
Mode::Details(details::Options { columns: Some(cols), .. }) |
Mode::GridDetails(_, details::Options { columns: Some(cols), .. }) => cols.should_scan_for_git(),
_ => false,
}
}

View File

@ -3,7 +3,7 @@ use std::env::var_os;
use getopts;
use output::Colours;
use output::{Grid, Details, GridDetails};
use output::{grid, details};
use output::column::{Columns, TimeTypes, SizeFormat};
use output::file_name::Classify;
use options::{FileFilter, DirAction, Misfire};
@ -33,9 +33,9 @@ impl View {
/// The **mode** is the “type” of output.
#[derive(PartialEq, Debug, Clone)]
pub enum Mode {
Details(Details),
Grid(Grid),
GridDetails(GridDetails),
Grid(grid::Options),
Details(details::Options),
GridDetails(grid::Options, details::Options),
Lines,
}
@ -53,7 +53,7 @@ impl Mode {
Err(Useless("oneline", true, "long"))
}
else {
let details = Details {
let details = details::Options {
columns: Some(Columns::deduce(matches)?),
header: matches.opt_present("header"),
recurse: dir_action.recurse_options(),
@ -97,7 +97,7 @@ impl Mode {
}
}
else if matches.opt_present("tree") {
let details = Details {
let details = details::Options {
columns: None,
header: false,
recurse: dir_action.recurse_options(),
@ -108,7 +108,7 @@ impl Mode {
Ok(Mode::Details(details))
}
else {
let grid = Grid {
let grid = grid::Options {
across: matches.opt_present("across"),
console_width: width,
};
@ -122,7 +122,7 @@ impl Mode {
// fallback to the lines view.
if matches.opt_present("tree") {
let details = Details {
let details = details::Options {
columns: None,
header: false,
recurse: dir_action.recurse_options(),
@ -144,7 +144,7 @@ impl Mode {
if let Mode::Details(details) = view {
let others = other_options_scan()?;
match others {
Mode::Grid(grid) => return Ok(Mode::GridDetails(GridDetails { grid, details })),
Mode::Grid(grid) => return Ok(Mode::GridDetails(grid, details)),
_ => return Ok(others),
};
}

View File

@ -113,7 +113,7 @@ use output::file_name::{FileName, LinkStyle, Classify};
/// Almost all the heavy lifting is done in a Table object, which handles the
/// columns for each row.
#[derive(PartialEq, Debug, Clone, Default)]
pub struct Details {
pub struct Options {
/// A Columns object that says which columns should be included in the
/// output in the general case. Directories themselves can pick which
@ -217,16 +217,22 @@ fn determine_time_zone() -> TZResult<TimeZone> {
TimeZone::from_file("/etc/localtime")
}
impl Details {
/// Print the details of the given vector of files -- all of which will
/// have been read from the given directory, if present -- to stdout.
pub fn view<W: Write>(&self, dir: Option<&Dir>, files: Vec<File>, w: &mut W, colours: &Colours, classify: Classify) -> IOResult<()> {
pub struct Render<'a> {
pub dir: Option<&'a Dir>,
pub files: Vec<File<'a>>,
pub colours: &'a Colours,
pub classify: Classify,
pub opts: &'a Options,
}
impl<'a> Render<'a> {
pub fn render<W: Write>(&self, w: &mut W) -> IOResult<()> {
// First, transform the Columns object into a vector of columns for
// the current directory.
let columns_for_dir = match self.columns {
Some(cols) => cols.for_dir(dir),
let columns_for_dir = match self.opts.columns {
Some(cols) => cols.for_dir(self.dir),
None => Vec::new(),
};
@ -236,18 +242,18 @@ impl Details {
// Build the table to put rows in.
let mut table = Table {
columns: &*columns_for_dir,
colours: colours,
classify: classify,
xattr: self.xattr,
colours: self.colours,
classify: self.classify,
xattr: self.opts.xattr,
env: env,
rows: Vec::new(),
};
// Next, add a header if the user requests it.
if self.header { table.add_header() }
if self.opts.header { table.add_header() }
// Then add files to the table and print it out.
self.add_files_to_table(&mut table, files, 0);
self.add_files_to_table(&mut table, &self.files, 0);
for cell in table.print_table() {
writeln!(w, "{}", cell.strings())?;
}
@ -257,7 +263,7 @@ impl Details {
/// Adds files to the table, possibly recursively. This is easily
/// parallelisable, and uses a pool of threads.
fn add_files_to_table<'dir, U: Users+Groups+Send>(&self, mut table: &mut Table<U>, src: Vec<File<'dir>>, depth: usize) {
fn add_files_to_table<'dir, U: Users+Groups+Send>(&self, mut table: &mut Table<U>, src: &Vec<File<'dir>>, depth: usize) {
use num_cpus;
use scoped_threadpool::Pool;
use std::sync::{Arc, Mutex};
@ -271,12 +277,12 @@ impl Details {
xattrs: Vec<Attribute>,
errors: Vec<(IOError, Option<PathBuf>)>,
dir: Option<Dir>,
file: File<'a>,
file: &'a File<'a>,
}
impl<'a> AsRef<File<'a>> for Egg<'a> {
fn as_ref(&self) -> &File<'a> {
&self.file
self.file
}
}
@ -288,6 +294,8 @@ impl Details {
let file_eggs = file_eggs.clone();
let table = table.clone();
let recurse = self.opts.recurse;
scoped.execute(move || {
let mut errors = Vec::new();
let mut xattrs = Vec::new();
@ -307,7 +315,7 @@ impl Details {
let mut dir = None;
if let Some(r) = self.recurse {
if let Some(r) = recurse {
if file.is_directory() && r.tree && !r.is_too_deep(depth) {
if let Ok(d) = file.to_dir(false) {
dir = Some(d);
@ -321,7 +329,7 @@ impl Details {
}
});
self.filter.sort_files(&mut file_eggs);
self.opts.filter.sort_files(&mut file_eggs);
let num_eggs = file_eggs.len();
for (index, egg) in file_eggs.into_iter().enumerate() {
@ -345,7 +353,7 @@ impl Details {
}
}
self.filter.filter_child_files(&mut files);
self.opts.filter.filter_child_files(&mut files);
if !files.is_empty() {
for xattr in egg.xattrs {
@ -356,7 +364,7 @@ impl Details {
table.add_error(&error, depth + 1, false, path);
}
self.add_files_to_table(table, files, depth + 1);
self.add_files_to_table(table, &files, depth + 1);
continue;
}
}
@ -465,8 +473,8 @@ impl<'a, U: Users+Groups+'a> Table<'a, U> {
self.rows.push(row);
}
pub fn filename(&self, file: File, links: LinkStyle) -> TextCellContents {
FileName::new(&file, links, self.classify, &self.colours).paint()
pub fn filename(&self, file: &File, links: LinkStyle) -> TextCellContents {
FileName::new(file, links, self.classify, &self.colours).paint()
}
pub fn add_file_with_cells(&mut self, cells: Vec<TextCell>, name_cell: TextCell, depth: usize, last: bool) {

View File

@ -1,6 +1,6 @@
use std::io::{Write, Result as IOResult};
use term_grid as grid;
use term_grid as tg;
use fs::File;
use output::colours::Colours;
@ -8,40 +8,52 @@ use output::file_name::{FileName, LinkStyle, Classify};
#[derive(PartialEq, Debug, Copy, Clone)]
pub struct Grid {
pub struct Options {
pub across: bool,
pub console_width: usize,
}
impl Grid {
pub fn view<W: Write>(&self, files: &[File], w: &mut W, colours: &Colours, classify: Classify) -> IOResult<()> {
let direction = if self.across { grid::Direction::LeftToRight }
else { grid::Direction::TopToBottom };
impl Options {
pub fn direction(&self) -> tg::Direction {
if self.across { tg::Direction::LeftToRight }
else { tg::Direction::TopToBottom }
}
}
let mut grid = grid::Grid::new(grid::GridOptions {
direction: direction,
filling: grid::Filling::Spaces(2),
pub struct Render<'a> {
pub files: Vec<File<'a>>,
pub colours: &'a Colours,
pub classify: Classify,
pub opts: &'a Options,
}
impl<'a> Render<'a> {
pub fn render<W: Write>(&self, w: &mut W) -> IOResult<()> {
let mut grid = tg::Grid::new(tg::GridOptions {
direction: self.opts.direction(),
filling: tg::Filling::Spaces(2),
});
grid.reserve(files.len());
grid.reserve(self.files.len());
for file in files.iter() {
let filename = FileName::new(file, LinkStyle::JustFilenames, classify, colours).paint();
for file in self.files.iter() {
let filename = FileName::new(file, LinkStyle::JustFilenames, self.classify, self.colours).paint();
let width = filename.width();
grid.add(grid::Cell {
grid.add(tg::Cell {
contents: filename.strings().to_string(),
width: *width,
});
}
if let Some(display) = grid.fit_into_width(self.console_width) {
if let Some(display) = grid.fit_into_width(self.opts.console_width) {
write!(w, "{}", display)
}
else {
// File names too long for a grid - drop down to just listing them!
for file in files.iter() {
let name_cell = FileName::new(file, LinkStyle::JustFilenames, classify, colours).paint();
for file in self.files.iter() {
let name_cell = FileName::new(file, LinkStyle::JustFilenames, self.classify, self.colours).paint();
writeln!(w, "{}", name_cell.strings())?;
}
Ok(())

View File

@ -11,29 +11,25 @@ use fs::feature::xattr::FileAttributes;
use output::cell::TextCell;
use output::column::Column;
use output::colours::Colours;
use output::details::{Details, Table, Environment};
use output::grid::Grid;
use output::details::{Table, Environment, Options as DetailsOptions};
use output::grid::Options as GridOptions;
use output::file_name::{Classify, LinkStyle};
#[derive(PartialEq, Debug, Clone)]
pub struct GridDetails {
pub grid: Grid,
pub details: Details,
pub struct Render<'a> {
pub dir: Option<&'a Dir>,
pub files: Vec<File<'a>>,
pub colours: &'a Colours,
pub classify: Classify,
pub grid: &'a GridOptions,
pub details: &'a DetailsOptions,
}
fn file_has_xattrs(file: &File) -> bool {
match file.path.attributes() {
Ok(attrs) => !attrs.is_empty(),
Err(_) => false,
}
}
impl<'a> Render<'a> {
pub fn render<W: Write>(&self, w: &mut W) -> IOResult<()> {
impl GridDetails {
pub fn view<W>(&self, dir: Option<&Dir>, files: Vec<File>, w: &mut W, colours: &Colours, classify: Classify) -> IOResult<()>
where W: Write {
let columns_for_dir = match self.details.columns {
Some(cols) => cols.for_dir(dir),
Some(cols) => cols.for_dir(self.dir),
None => Vec::new(),
};
@ -41,23 +37,23 @@ impl GridDetails {
let (cells, file_names) = {
let first_table = self.make_table(env.clone(), &*columns_for_dir, colours, classify);
let first_table = self.make_table(env.clone(), &*columns_for_dir, self.colours, self.classify);
let cells = files.iter()
let cells = self.files.iter()
.map(|file| first_table.cells_for_file(file, file_has_xattrs(file)))
.collect::<Vec<_>>();
let file_names = files.into_iter()
let file_names = self.files.iter()
.map(|file| first_table.filename(file, LinkStyle::JustFilenames).promote())
.collect::<Vec<_>>();
(cells, file_names)
};
let mut last_working_table = self.make_grid(env.clone(), 1, &columns_for_dir, &file_names, cells.clone(), colours, classify);
let mut last_working_table = self.make_grid(env.clone(), 1, &columns_for_dir, &file_names, cells.clone(), self.colours, self.classify);
for column_count in 2.. {
let grid = self.make_grid(env.clone(), column_count, &columns_for_dir, &file_names, cells.clone(), colours, classify);
let grid = self.make_grid(env.clone(), column_count, &columns_for_dir, &file_names, cells.clone(), self.colours, self.classify);
let the_grid_fits = {
let d = grid.fit_into_columns(column_count);
@ -75,7 +71,7 @@ impl GridDetails {
Ok(())
}
fn make_table<'a>(&'a self, env: Arc<Environment<UsersCache>>, columns_for_dir: &'a [Column], colours: &'a Colours, classify: Classify) -> Table<UsersCache> {
fn make_table<'g>(&'g self, env: Arc<Environment<UsersCache>>, columns_for_dir: &'g [Column], colours: &'g Colours, classify: Classify) -> Table<UsersCache> {
let mut table = Table {
columns: columns_for_dir,
colours, classify, env,
@ -87,7 +83,7 @@ impl GridDetails {
table
}
fn make_grid<'a>(&'a self, env: Arc<Environment<UsersCache>>, column_count: usize, columns_for_dir: &'a [Column], file_names: &[TextCell], cells: Vec<Vec<TextCell>>, colours: &'a Colours, classify: Classify) -> grid::Grid {
fn make_grid<'g>(&'g self, env: Arc<Environment<UsersCache>>, column_count: usize, columns_for_dir: &'g [Column], file_names: &[TextCell], cells: Vec<Vec<TextCell>>, colours: &'g Colours, classify: Classify) -> grid::Grid {
let mut tables = Vec::new();
for _ in 0 .. column_count {
tables.push(self.make_table(env.clone(), columns_for_dir, colours, classify));
@ -159,3 +155,11 @@ fn divide_rounding_up(a: usize, b: usize) -> usize {
if a % b != 0 { result += 1; }
result
}
fn file_has_xattrs(file: &File) -> bool {
match file.path.attributes() {
Ok(attrs) => !attrs.is_empty(),
Err(_) => false,
}
}

View File

@ -9,10 +9,23 @@ use super::colours::Colours;
/// The lines view literally just displays each file, line-by-line.
pub fn view<W: Write>(files: Vec<File>, w: &mut W, colours: &Colours, classify: Classify) -> IOResult<()> {
for file in files {
let name_cell = FileName::new(&file, LinkStyle::FullLinkPaths, classify, colours).paint();
writeln!(w, "{}", ANSIStrings(&name_cell))?;
}
Ok(())
pub struct Render<'a> {
pub files: Vec<File<'a>>,
pub colours: &'a Colours,
pub classify: Classify,
}
impl<'a> Render<'a> {
pub fn render<W: Write>(&self, w: &mut W) -> IOResult<()> {
for file in &self.files {
let name_cell = self.render_file(file).paint();
writeln!(w, "{}", ANSIStrings(&name_cell))?;
}
Ok(())
}
fn render_file<'f>(&self, file: &'f File<'a>) -> FileName<'f, 'a> {
FileName::new(file, LinkStyle::FullLinkPaths, self.classify, self.colours)
}
}

View File

@ -1,18 +1,16 @@
pub use self::cell::{TextCell, TextCellContents, DisplayWidth};
pub use self::colours::Colours;
pub use self::details::Details;
pub use self::grid_details::GridDetails;
pub use self::grid::Grid;
pub use self::escape::escape;
mod grid;
pub mod details;
pub mod lines;
mod grid_details;
pub mod column;
pub mod details;
pub mod file_name;
pub mod grid_details;
pub mod grid;
pub mod lines;
mod cell;
mod colours;
mod tree;
pub mod file_name;
mod escape;
mod render;
mod tree;