Selaa lähdekoodia

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.
Benjamin Sago 9 vuotta sitten
vanhempi
sitoutus
a02f37cb45
5 muutettua tiedostoa jossa 58 lisäystä ja 36 poistoa
  1. 34 23
      src/main.rs
  2. 7 5
      src/output/details.rs
  3. 6 3
      src/output/grid.rs
  4. 6 3
      src/output/grid_details.rs
  5. 5 2
      src/output/lines.rs

+ 34 - 23
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<String>) {
+impl<'w, W: Write + 'w> Exa<'w, W> {
+    fn run(&mut self, mut args_file_names: Vec<String>) -> 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<Dir>, mut first: bool, is_only_dir: bool) {
+    fn print_dirs(&mut self, dir_files: Vec<Dir>, 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<File>) {
+    fn print_files(&mut self, dir: Option<&Dir>, files: Vec<File>) -> 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);

+ 7 - 5
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<File>) {
+    pub fn view<W: Write>(&self, dir: Option<&Dir>, files: Vec<File>, 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<TextCell>,
             xattrs:  Vec<Attribute>,
-            errors:  Vec<(io::Error, Option<PathBuf>)>,
+            errors:  Vec<(IOError, Option<PathBuf>)>,
             dir:     Option<Dir>,
             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<PathBuf>) {
+    fn add_error(&mut self, error: &IOError, depth: usize, last: bool, path: Option<PathBuf>) {
         let error_message = match path {
             Some(path) => format!("<{}: {}>", path.display(), error),
             None       => format!("<{}>", error),

+ 6 - 3
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<W: Write>(&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(())
         }
     }
 }

+ 6 - 3
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<File>) {
+    pub fn view<W>(&self, dir: Option<&Dir>, files: Vec<File>, 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<Environment<UsersCache>>, columns_for_dir: &'a [Column]) -> Table<UsersCache> {

+ 5 - 2
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<File>) {
+    pub fn view<W: Write>(&self, files: Vec<File>, 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(())
     }
 }