From a02f37cb45532075281152435d7223e772ae62b5 Mon Sep 17 00:00:00 2001 From: Benjamin Sago Date: Mon, 18 Apr 2016 18:39:32 +0100 Subject: [PATCH] Change views to print to a Writer, not stdout This will mean that we can test exa's output as a whole, without having to rely on process or IO or anything like that. --- src/main.rs | 57 +++++++++++++++++++++++--------------- src/output/details.rs | 12 ++++---- src/output/grid.rs | 9 ++++-- src/output/grid_details.rs | 9 ++++-- src/output/lines.rs | 7 +++-- 5 files changed, 58 insertions(+), 36 deletions(-) diff --git a/src/main.rs b/src/main.rs index 6f9303b..325b04f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,6 +20,7 @@ extern crate zoneinfo_compiled; #[macro_use] extern crate lazy_static; use std::env; +use std::io::{Write, stdout, Result as IOResult}; use std::path::{Component, Path}; use std::process; @@ -33,12 +34,20 @@ mod output; mod term; -struct Exa { +/// The main program wrapper. +struct Exa<'w, W: Write + 'w> { + + /// List of command-line options, having been successfully parsed. options: Options, + + /// The output handle that we write to. When running the program normally, + /// this will be `std::io::Stdout`, but it can accept any struct that’s + /// `Write` so we can write into, say, a vector for testing. + writer: &'w mut W, } -impl Exa { - fn run(&mut self, mut args_file_names: Vec) { +impl<'w, W: Write + 'w> Exa<'w, W> { + fn run(&mut self, mut args_file_names: Vec) -> IOResult<()> { let mut files = Vec::new(); let mut dirs = Vec::new(); @@ -49,13 +58,13 @@ impl Exa { for file_name in args_file_names.iter() { match File::from_path(Path::new(&file_name), None) { Err(e) => { - println!("{}: {}", file_name, e); + try!(writeln!(self.writer, "{}: {}", file_name, e)); }, Ok(f) => { if f.is_directory() && !self.options.dir_action.treat_dirs_as_files() { match f.to_dir(self.options.should_scan_for_git()) { Ok(d) => dirs.push(d), - Err(e) => println!("{}: {}", file_name, e), + Err(e) => try!(writeln!(self.writer, "{}: {}", file_name, e)), } } else { @@ -69,13 +78,13 @@ impl Exa { let is_only_dir = dirs.len() == 1 && no_files; if !no_files { - self.print_files(None, files); + try!(self.print_files(None, files)); } - self.print_dirs(dirs, no_files, is_only_dir); + self.print_dirs(dirs, no_files, is_only_dir) } - fn print_dirs(&self, dir_files: Vec, mut first: bool, is_only_dir: bool) { + fn print_dirs(&mut self, dir_files: Vec, mut first: bool, is_only_dir: bool) -> IOResult<()> { for dir in dir_files { // Put a gap between directories, or between the list of files and the @@ -84,18 +93,18 @@ impl Exa { first = false; } else { - print!("\n"); + try!(write!(self.writer, "\n")); } if !is_only_dir { - println!("{}:", dir.path.display()); + try!(writeln!(self.writer, "{}:", dir.path.display())); } let mut children = Vec::new(); for file in dir.files() { match file { Ok(file) => children.push(file), - Err((path, e)) => println!("[{}: {}]", path.display(), e), + Err((path, e)) => try!(writeln!(self.writer, "[{}: {}]", path.display(), e)), } }; @@ -110,31 +119,32 @@ impl Exa { for child_dir in children.iter().filter(|f| f.is_directory()) { match child_dir.to_dir(false) { Ok(d) => child_dirs.push(d), - Err(e) => println!("{}: {}", child_dir.path.display(), e), + Err(e) => try!(writeln!(self.writer, "{}: {}", child_dir.path.display(), e)), } } - self.print_files(Some(&dir), children); + try!(self.print_files(Some(&dir), children)); if !child_dirs.is_empty() { - self.print_dirs(child_dirs, false, false); + try!(self.print_dirs(child_dirs, false, false)); } continue; } } - self.print_files(Some(&dir), children); - + try!(self.print_files(Some(&dir), children)); } + + Ok(()) } - fn print_files(&self, dir: Option<&Dir>, files: Vec) { + fn print_files(&mut self, dir: Option<&Dir>, files: Vec) -> IOResult<()> { match self.options.view { - View::Grid(g) => g.view(&files), - View::Details(d) => d.view(dir, files), - View::GridDetails(gd) => gd.view(dir, files), - View::Lines(l) => l.view(files), + View::Grid(g) => g.view(&files, self.writer), + View::Details(d) => d.view(dir, files, self.writer), + View::GridDetails(gd) => gd.view(dir, files, self.writer), + View::Lines(l) => l.view(files, self.writer), } } } @@ -145,8 +155,9 @@ fn main() { match Options::getopts(&args) { Ok((options, paths)) => { - let mut exa = Exa { options: options }; - exa.run(paths); + let mut stdout = stdout(); + let mut exa = Exa { options: options, writer: &mut stdout }; + exa.run(paths).expect("IO error"); }, Err(e) => { println!("{}", e); diff --git a/src/output/details.rs b/src/output/details.rs index 0fc4a84..a6ebb65 100644 --- a/src/output/details.rs +++ b/src/output/details.rs @@ -71,7 +71,7 @@ //! are used in place of the filename. -use std::io; +use std::io::{Write, Error as IOError, Result as IOResult}; use std::ops::Add; use std::path::PathBuf; use std::string::ToString; @@ -186,7 +186,7 @@ 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(&self, dir: Option<&Dir>, files: Vec) { + pub fn view(&self, dir: Option<&Dir>, files: Vec, w: &mut W) -> IOResult<()> { // First, transform the Columns object into a vector of columns for // the current directory. @@ -212,8 +212,10 @@ impl Details { // Then add files to the table and print it out. self.add_files_to_table(&mut table, files, 0); for cell in table.print_table() { - println!("{}", cell.strings()); + try!(writeln!(w, "{}", cell.strings())); } + + Ok(()) } /// Adds files to the table, possibly recursively. This is easily @@ -230,7 +232,7 @@ impl Details { struct Egg<'a> { cells: Vec, xattrs: Vec, - errors: Vec<(io::Error, Option)>, + errors: Vec<(IOError, Option)>, dir: Option, file: File<'a>, } @@ -417,7 +419,7 @@ impl<'a, U: Users+Groups+'a> Table<'a, U> { self.rows.push(row); } - fn add_error(&mut self, error: &io::Error, depth: usize, last: bool, path: Option) { + fn add_error(&mut self, error: &IOError, depth: usize, last: bool, path: Option) { let error_message = match path { Some(path) => format!("<{}: {}>", path.display(), error), None => format!("<{}>", error), diff --git a/src/output/grid.rs b/src/output/grid.rs index 5afec32..9daff07 100644 --- a/src/output/grid.rs +++ b/src/output/grid.rs @@ -1,3 +1,5 @@ +use std::io::{Write, Result as IOResult}; + use term_grid as grid; use fs::File; @@ -14,7 +16,7 @@ pub struct Grid { } impl Grid { - pub fn view(&self, files: &[File]) { + pub fn view(&self, files: &[File], w: &mut W) -> IOResult<()> { let direction = if self.across { grid::Direction::LeftToRight } else { grid::Direction::TopToBottom }; @@ -41,13 +43,14 @@ impl Grid { } if let Some(display) = grid.fit_into_width(self.console_width) { - print!("{}", display); + write!(w, "{}", display) } else { // File names too long for a grid - drop down to just listing them! for file in files.iter() { - println!("{}", filename(file, &self.colours, false).strings()); + try!(writeln!(w, "{}", filename(file, &self.colours, false).strings())); } + Ok(()) } } } diff --git a/src/output/grid_details.rs b/src/output/grid_details.rs index 2d348a3..b3a8373 100644 --- a/src/output/grid_details.rs +++ b/src/output/grid_details.rs @@ -1,3 +1,4 @@ +use std::io::{Write, Result as IOResult}; use std::sync::Arc; use ansi_term::ANSIStrings; @@ -26,7 +27,8 @@ fn file_has_xattrs(file: &File) -> bool { } impl GridDetails { - pub fn view(&self, dir: Option<&Dir>, files: Vec) { + pub fn view(&self, dir: Option<&Dir>, files: Vec, w: &mut W) -> IOResult<()> + where W: Write { let columns_for_dir = match self.details.columns { Some(cols) => cols.for_dir(dir), None => Vec::new(), @@ -63,10 +65,11 @@ impl GridDetails { last_working_table = grid; } else { - print!("{}", last_working_table.fit_into_columns(column_count - 1)); - return; + return write!(w, "{}", last_working_table.fit_into_columns(column_count - 1)); } } + + Ok(()) } fn make_table<'a>(&'a self, env: Arc>, columns_for_dir: &'a [Column]) -> Table { diff --git a/src/output/lines.rs b/src/output/lines.rs index 81e51d0..e531930 100644 --- a/src/output/lines.rs +++ b/src/output/lines.rs @@ -1,3 +1,5 @@ +use std::io::{Write, Result as IOResult}; + use ansi_term::ANSIStrings; use fs::File; @@ -13,9 +15,10 @@ pub struct Lines { /// The lines view literally just displays each file, line-by-line. impl Lines { - pub fn view(&self, files: Vec) { + pub fn view(&self, files: Vec, w: &mut W) -> IOResult<()> { for file in files { - println!("{}", ANSIStrings(&filename(&file, &self.colours, true))); + try!(writeln!(w, "{}", ANSIStrings(&filename(&file, &self.colours, true)))); } + Ok(()) } }