Browse Source

Merge branch 'cleanup'

Ben S 11 years ago
parent
commit
982352009f
4 changed files with 200 additions and 78 deletions
  1. 1 1
      src/column.rs
  2. 4 1
      src/main.rs
  3. 3 4
      src/options.rs
  4. 192 72
      src/output/details.rs

+ 1 - 1
src/column.rs

@@ -4,7 +4,7 @@ use ansi_term::Style;
 
 use options::{SizeFormat, TimeType};
 
-#[derive(PartialEq, Debug, Copy)]
+#[derive(PartialEq, Debug, Copy, Clone)]
 pub enum Column {
     Permissions,
     FileSize(SizeFormat),

+ 4 - 1
src/main.rs

@@ -1,4 +1,4 @@
-#![feature(collections, core, env, libc, old_io, old_path, plugin, std_misc)]
+#![feature(collections, core, env, io, libc, old_io, old_path, std_misc)]
 
 // Other platforms than macos don't need std_misc but you can't
 // use #[cfg] on features.
@@ -33,6 +33,7 @@ pub mod output;
 pub mod term;
 pub mod xattr;
 
+#[cfg(not(test))]
 struct Exa<'a> {
     count:   usize,
     options: Options,
@@ -40,6 +41,7 @@ struct Exa<'a> {
     files:   Vec<File<'a>>,
 }
 
+#[cfg(not(test))]
 impl<'a> Exa<'a> {
     fn new(options: Options) -> Exa<'a> {
         Exa {
@@ -145,6 +147,7 @@ impl<'a> Exa<'a> {
     }
 }
 
+#[cfg(not(test))]
 fn main() {
     let args: Vec<String> = env::args().collect();
 

+ 3 - 4
src/options.rs

@@ -233,9 +233,8 @@ impl View {
                 let details = Details {
                         columns: try!(Columns::deduce(matches)),
                         header: matches.opt_present("header"),
-                        recurse: dir_action.recurse_options(),
+                        recurse: dir_action.recurse_options().map(|o| (o, filter)),
                         xattr: xattr::feature_implemented() && matches.opt_present("extended"),
-                        filter: filter,
                 };
 
                 Ok(View::Details(details))
@@ -298,7 +297,7 @@ impl View {
     }
 }
 
-#[derive(PartialEq, Debug, Copy)]
+#[derive(PartialEq, Debug, Copy, Clone)]
 pub enum SizeFormat {
     DecimalBytes,
     BinaryBytes,
@@ -319,7 +318,7 @@ impl SizeFormat {
     }
 }
 
-#[derive(PartialEq, Debug, Copy)]
+#[derive(PartialEq, Debug, Copy, Clone)]
 pub enum TimeType {
     FileAccessed,
     FileModified,

+ 192 - 72
src/output/details.rs

@@ -8,75 +8,205 @@ use users::OSUsers;
 use locale;
 use ansi_term::Style::Plain;
 
+/// With the **Details** view, the output gets formatted into columns, with
+/// each `Column` object showing some piece of information about the file,
+/// such as its size, or its permissions.
+///
+/// To do this, the results have to be written to a table, instead of
+/// displaying each file immediately. Then, the width of each column can be
+/// calculated based on the individual results, and the fields are padded
+/// during output.
+///
+/// Almost all the heavy lifting is done in a Table object, which handles the
+/// columns for each row.
 #[derive(PartialEq, Debug, Copy)]
 pub struct Details {
+
+    /// A Columns object that says which columns should be included in the
+    /// output in the general case. Directories themselves can pick which
+    /// columns are *added* to this list, such as the Git column.
     pub columns: Columns,
+
+    /// Whether to recurse through directories with a tree view, and if so,
+    /// which options to use. This field is only relevant here if the `tree`
+    /// field of the RecurseOptions is `true`.
+    pub recurse: Option<(RecurseOptions, FileFilter)>,
+
+    /// Whether to show a header line or not.
     pub header: bool,
-    pub recurse: Option<RecurseOptions>,
+
+    /// Whether to show each file's extended attributes.
     pub xattr: bool,
-    pub filter: FileFilter,
 }
 
 impl Details {
-
     pub fn view(&self, dir: Option<&Dir>, files: &[File]) {
-        // The output gets formatted into columns, which looks nicer. To
-        // do this, we have to write the results into a table, instead of
-        // displaying each file immediately, then calculating the maximum
-        // width of each column based on the length of the results and
-        // padding the fields during output.
-
-        let columns = self.columns.for_dir(dir);
-        let locale = UserLocale::new();
-        let mut cache = OSUsers::empty_cache();
-        let mut table = Vec::new();
-        self.get_files(&columns[..], &mut cache, &locale, &mut table, files, 0);
-
-        if self.header {
-            let row = Row {
-                depth: 0,
-                cells: columns.iter().map(|c| Cell::paint(Plain.underline(), c.header())).collect(),
-                name: Plain.underline().paint("Name").to_string(),
-                last: false,
-                attrs: Vec::new(),
-                children: false,
-            };
-
-            table.insert(0, row);
+        // First, transform the Columns object into a vector of columns for
+        // the current directory.
+        let mut table = Table::with_columns(self.columns.for_dir(dir));
+        if self.header { table.add_header() }
+
+        // Then add files to the table and print it out.
+        self.add_files_to_table(&mut table, files, 0);
+        table.print_table(self.xattr, self.recurse.is_some());
+    }
+
+    /// Adds files to the table - recursively, if the `recurse` option
+    /// is present.
+    fn add_files_to_table(&self, table: &mut Table, src: &[File], depth: usize) {
+        for (index, file) in src.iter().enumerate() {
+            table.add_file(file, depth, index == src.len() - 1);
+
+            // There are two types of recursion that exa supports: a tree
+            // view, which is dealt with here, and multiple listings, which is
+            // dealt with in the main module. So only actually recurse if we
+            // are in tree mode - the other case will be dealt with elsewhere.
+            if let Some((r, filter)) = self.recurse {
+                if r.tree == false || r.is_too_deep(depth) {
+                    continue;
+                }
+
+                // Use the filter to remove unwanted files *before* expanding
+                // them, so we don't examine any directories that wouldn't
+                // have their contents listed anyway.
+                if let Some(ref dir) = file.this {
+                    let mut files = dir.files(true);
+                    filter.transform_files(&mut files);
+                    self.add_files_to_table(table, &files, depth + 1);
+                }
+            }
         }
+    }
+}
 
-        let column_widths: Vec<usize> = range(0, columns.len())
-            .map(|n| table.iter().map(|row| row.cells[n].length).max().unwrap_or(0))
-            .collect();
+struct Row {
+
+    /// Vector of cells to display.
+    cells:    Vec<Cell>,
+
+    /// This file's name, in coloured output. The name is treated separately
+    /// from the other cells, as it never requires padding.
+    name:     String,
+
+    /// How many directories deep into the tree structure this is. Directories
+    /// on top have depth 0.
+    depth:    usize,
+
+    /// Vector of this file's extended attributes, if that feature is active.
+    attrs:    Vec<Attribute>,
+
+    /// Whether this is the last entry in the directory. This flag is used
+    /// when calculating the tree view.
+    last:     bool,
+
+    /// Whether this file is a directory and has any children. Also used when
+    /// calculating the tree view.
+    children: bool,
+}
 
+/// A **Table** object gets built up by the view as it lists files and
+/// directories.
+struct Table {
+    columns: Vec<Column>,
+    users:   OSUsers,
+    locale:  UserLocale,
+    rows:    Vec<Row>,
+}
+
+impl Table {
+    /// Create a new, empty Table object, setting the caching fields to their
+    /// empty states.
+    fn with_columns(columns: Vec<Column>) -> Table {
+        Table {
+            columns: columns,
+            users: OSUsers::empty_cache(),
+            locale: UserLocale::new(),
+            rows: Vec::new(),
+        }
+    }
+
+    /// Add a dummy "header" row to the table, which contains the names of all
+    /// the columns, underlined. This has dummy data for the cases that aren't
+    /// actually used, such as the depth or list of attributes.
+    fn add_header(&mut self) {
+        let row = Row {
+            depth:    0,
+            cells:    self.columns.iter().map(|c| Cell::paint(Plain.underline(), c.header())).collect(),
+            name:     Plain.underline().paint("Name").to_string(),
+            last:     false,
+            attrs:    Vec::new(),
+            children: false,
+        };
+
+        self.rows.push(row);
+    }
+
+    /// Use the list of columns to find which cells should be produced for
+    /// this file, per-column.
+    fn cells_for_file(&mut self, file: &File) -> Vec<Cell> {
+        self.columns.clone().iter()
+                    .map(|c| file.display(c, &mut self.users, &self.locale))
+                    .collect()
+    }
+
+    /// Get the cells for the given file, and add the result to the table.
+    fn add_file(&mut self, file: &File, depth: usize, last: bool) {
+        let row = Row {
+            depth:    depth,
+            cells:    self.cells_for_file(file),
+            name:     file.file_name_view(),
+            last:     last,
+            attrs:    file.xattrs.clone(),
+            children: file.this.is_some(),
+        };
+
+        self.rows.push(row)
+    }
+
+    /// Print the table to standard output, consuming it in the process.
+    fn print_table(self, xattr: bool, show_children: bool) {
         let mut stack = Vec::new();
 
-        for row in table {
-            for (num, column) in columns.iter().enumerate() {
-                let padding = column_widths[num] - row.cells[num].length;
-                print!("{} ", column.alignment().pad_string(&row.cells[num].text, padding));
+        // Work out the list of column widths by finding the longest cell for
+        // each column, then formatting each cell in that column to be the
+        // width of that one.
+        let column_widths: Vec<usize> = range(0, self.columns.len())
+            .map(|n| self.rows.iter().map(|row| row.cells[n].length).max().unwrap_or(0))
+            .collect();
+
+        for row in self.rows.into_iter() {
+            for (n, width) in column_widths.iter().enumerate() {
+                let padding = width - row.cells[n].length;
+                print!("{} ", self.columns[n].alignment().pad_string(&row.cells[n].text, padding));
             }
 
-            if self.recurse.is_some() {
-                stack.resize(row.depth  + 1, "├──");
-                stack[row.depth] = if row.last { "└──" } else { "├──" };
+            // A stack tracks which tree characters should be printed. It's
+            // necessary to maintain information about the previously-printed
+            // lines, as the output will change based on whether the
+            // *previous* entry was the last in its directory.
+            if show_children {
+                stack.resize(row.depth + 1, TreePart::Edge);
+                stack[row.depth] = if row.last { TreePart::Corner } else { TreePart::Edge };
 
                 for i in 1 .. row.depth + 1 {
-                    print!("{}", GREY.paint(stack[i]));
+                    print!("{}", GREY.paint(stack[i].ascii_art()));
                 }
 
                 if row.children {
-                    stack[row.depth] = if row.last { "   " } else { "│  " };
+                    stack[row.depth] = if row.last { TreePart::Blank } else { TreePart::Line };
                 }
 
+                // If any tree characters have been printed, then add an extra
+                // space, which makes the output look much better.
                 if row.depth != 0 {
                     print!(" ");
                 }
             }
 
+            // Print the name without worrying about padding.
             print!("{}\n", row.name);
 
-            if self.xattr {
+            if xattr {
                 let width = row.attrs.iter().map(|a| a.name().len()).max().unwrap_or(0);
                 for attr in row.attrs.iter() {
                     let name = attr.name();
@@ -88,61 +218,51 @@ impl Details {
             }
         }
     }
+}
 
-    fn get_files(&self, columns: &[Column], cache: &mut OSUsers, locale: &UserLocale, dest: &mut Vec<Row>, src: &[File], depth: usize) {
-        for (index, file) in src.iter().enumerate() {
+#[derive(PartialEq, Debug, Clone)]
+enum TreePart {
 
-            let row = Row {
-                depth: depth,
-                cells: columns.iter().map(|c| file.display(c, cache, locale)).collect(),
-                name:  file.file_name_view(),
-                last:  index == src.len() - 1,
-                attrs: file.xattrs.clone(),
-                children: file.this.is_some(),
-            };
+    /// Rightmost column, *not* the last in the directory.
+    Edge,
 
-            dest.push(row);
+    /// Not the rightmost column, and the directory has not finished yet.
+    Line,
 
-            if let Some(r) = self.recurse {
-                if r.tree == false || r.is_too_deep(depth) {
-                    continue;
-                }
+    /// Rightmost column, and the last in the directory.
+    Corner,
 
-                if let Some(ref dir) = file.this {
-                    let mut files = dir.files(true);
-                    self.filter.transform_files(&mut files);
-                    self.get_files(columns, cache, locale, dest, &files, depth + 1);
-                }
-            }
-        }
-    }
+    /// Not the rightmost column, and the directory *has* finished.
+    Blank,
 }
 
-struct Row {
-    pub depth: usize,
-    pub cells: Vec<Cell>,
-    pub name: String,
-    pub last: bool,
-    pub attrs: Vec<Attribute>,
-    pub children: bool,
+impl TreePart {
+    fn ascii_art(&self) -> &'static str {
+        match *self {
+            TreePart::Edge   => "├──",
+            TreePart::Line   => "│  ",
+            TreePart::Corner => "└──",
+            TreePart::Blank  => "   ",
+        }
+    }
 }
 
 pub struct UserLocale {
-    pub time: locale::Time,
+    pub time:    locale::Time,
     pub numeric: locale::Numeric,
 }
 
 impl UserLocale {
     pub fn new() -> UserLocale {
         UserLocale {
-            time: locale::Time::load_user_locale().unwrap_or_else(|_| locale::Time::english()),
+            time:    locale::Time::load_user_locale().unwrap_or_else(|_| locale::Time::english()),
             numeric: locale::Numeric::load_user_locale().unwrap_or_else(|_| locale::Numeric::english()),
         }
     }
 
     pub fn default() -> UserLocale {
         UserLocale {
-            time: locale::Time::english(),
+            time:    locale::Time::english(),
             numeric: locale::Numeric::english(),
         }
     }