diff options
author | Ben S <ogham@bsago.me> | 2015-08-26 12:19:51 +0100 |
---|---|---|
committer | Ben S <ogham@bsago.me> | 2015-08-26 12:19:51 +0100 |
commit | eee49ece041e96673ce536fc1a4f5afb19d43ead (patch) | |
tree | 4a34b5f6e9a03b35bd84d7965cee8975c4c0c2db /src/output | |
parent | d547c3f5d788f202de353938eaaedbb67a3624df (diff) | |
parent | 5f48bfd8b4079d5fa213d3397dabd2a388bf325e (diff) |
Merge branch 'child-nodes'
Diffstat (limited to 'src/output')
-rw-r--r-- | src/output/details.rs | 231 | ||||
-rw-r--r-- | src/output/grid_details.rs | 12 |
2 files changed, 169 insertions, 74 deletions
diff --git a/src/output/details.rs b/src/output/details.rs index f7c50b7..3052448 100644 --- a/src/output/details.rs +++ b/src/output/details.rs @@ -1,10 +1,12 @@ -use std::iter::repeat; +use std::error::Error; +use std::io; +use std::path::PathBuf; use std::string::ToString; use colours::Colours; use column::{Alignment, Column, Cell}; use dir::Dir; -use feature::Attribute; +use feature::xattr::{Attribute, FileAttributes}; use file::fields as f; use file::File; use options::{Columns, FileFilter, RecurseOptions, SizeFormat}; @@ -75,7 +77,7 @@ 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(self.xattr, self.recurse.is_some()) { + for cell in table.print_table() { println!("{}", cell.text); } } @@ -84,26 +86,82 @@ impl Details { /// is present. fn add_files_to_table<U: Users>(&self, table: &mut Table<U>, src: &[File], depth: usize) { for (index, file) in src.iter().enumerate() { - table.add_file(file, depth, index == src.len() - 1, true); + let mut xattrs = Vec::new(); + let mut errors = Vec::new(); + + let has_xattrs = match file.path.attributes() { + Ok(xs) => { + let r = !xs.is_empty(); + if self.xattr { + for xattr in xs { + xattrs.push(xattr); + } + } + r + }, + Err(e) => { + if self.xattr { + errors.push((e, None)); + } + true + }, + }; + + table.add_file(file, depth, index == src.len() - 1, true, has_xattrs); // 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; + if file.is_directory() && r.tree && !r.is_too_deep(depth) { + + // 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. + match file.to_dir() { + Ok(ref dir) => { + let mut files = Vec::new(); + + for file_to_add in dir.files() { + match file_to_add { + Ok(f) => files.push(f), + Err((path, e)) => errors.push((e, Some(path))) + } + } + + filter.transform_files(&mut files); + + if !files.is_empty() { + for xattr in xattrs { + table.add_xattr(xattr, depth + 1, false); + } + + for (error, path) in errors { + table.add_error(&error, depth + 1, false, path); + } + + self.add_files_to_table(table, &files, depth + 1); + continue; + } + }, + Err(e) => { + errors.push((e, None)); + }, + } } + } - // 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 count = xattrs.len(); + for (index, xattr) in xattrs.into_iter().enumerate() { + table.add_xattr(xattr, depth + 1, errors.is_empty() && index == count - 1); + } + + let count = errors.len(); + for (index, (error, path)) in errors.into_iter().enumerate() { + table.add_error(&error, depth + 1, index == count - 1, path); } + } } } @@ -112,26 +170,38 @@ impl Details { struct Row { /// Vector of cells to display. - cells: Vec<Cell>, + /// + /// Most of the rows will be files that have had their metadata + /// successfully queried and displayed in these cells, so this will almost + /// always be `Some`. It will be `None` for a row that's only displaying + /// an attribute or an error. + cells: Option<Vec<Cell>>, + + // Did You Know? + // A Vec<Cell> and an Option<Vec<Cell>> actually have the same byte size! /// This file's name, in coloured output. The name is treated separately /// from the other cells, as it never requires padding. - name: Cell, + name: Cell, /// 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>, + depth: usize, /// Whether this is the last entry in the directory. This flag is used /// when calculating the tree view. - last: bool, + last: bool, +} + +impl Row { - /// Whether this file is a directory and has any children. Also used when - /// calculating the tree view. - children: bool, + /// Gets the 'width' of the indexed column, if present. If not, returns 0. + fn column_width(&self, index: usize) -> usize { + match self.cells { + Some(ref cells) => cells[index].length, + None => 0, + } + } } @@ -191,30 +261,53 @@ impl<U> Table<U> where U: Users { pub fn add_header(&mut self) { let row = Row { depth: 0, - cells: self.columns.iter().map(|c| Cell::paint(self.colours.header, c.header())).collect(), + cells: Some(self.columns.iter().map(|c| Cell::paint(self.colours.header, c.header())).collect()), name: Cell::paint(self.colours.header, "Name"), last: false, - attrs: Vec::new(), - children: false, + }; + + self.rows.push(row); + } + + fn add_error(&mut self, error: &io::Error, depth: usize, last: bool, path: Option<PathBuf>) { + let error_message = match path { + Some(path) => format!("<{}: {}>", path.display(), error), + None => format!("<{}>", error), + }; + + let row = Row { + depth: depth, + cells: None, + name: Cell::paint(self.colours.broken_arrow, &error_message), + last: last, + }; + + self.rows.push(row); + } + + fn add_xattr(&mut self, xattr: Attribute, depth: usize, last: bool) { + let row = Row { + depth: depth, + cells: None, + name: Cell::paint(self.colours.perms.attribute, &format!("{}\t{}", xattr.name, xattr.size)), + last: last, }; self.rows.push(row); } /// Get the cells for the given file, and add the result to the table. - pub fn add_file(&mut self, file: &File, depth: usize, last: bool, links: bool) { - let cells = self.cells_for_file(file); + fn add_file(&mut self, file: &File, depth: usize, last: bool, links: bool, xattrs: bool) { + let cells = self.cells_for_file(file, xattrs); self.add_file_with_cells(cells, file, depth, last, links) } pub fn add_file_with_cells(&mut self, cells: Vec<Cell>, file: &File, depth: usize, last: bool, links: bool) { let row = Row { depth: depth, - cells: cells, + cells: Some(cells), name: Cell { text: filename(file, &self.colours, links), length: file.file_name_width() }, last: last, - attrs: file.xattrs.clone(), - children: file.this.is_some(), }; self.rows.push(row); @@ -222,15 +315,15 @@ impl<U> Table<U> where U: Users { /// Use the list of columns to find which cells should be produced for /// this file, per-column. - pub fn cells_for_file(&mut self, file: &File) -> Vec<Cell> { + pub fn cells_for_file(&mut self, file: &File, xattrs: bool) -> Vec<Cell> { self.columns.clone().iter() - .map(|c| self.display(file, c)) + .map(|c| self.display(file, c, xattrs)) .collect() } - fn display(&mut self, file: &File, column: &Column) -> Cell { + fn display(&mut self, file: &File, column: &Column, xattrs: bool) -> Cell { match *column { - Column::Permissions => self.render_permissions(file.permissions()), + Column::Permissions => self.render_permissions(file.permissions(), xattrs), Column::FileSize(fmt) => self.render_size(file.size(), fmt), Column::Timestamp(t) => self.render_time(file.timestamp(t)), Column::HardLinks => self.render_links(file.links()), @@ -242,7 +335,7 @@ impl<U> Table<U> where U: Users { } } - fn render_permissions(&self, permissions: f::Permissions) -> Cell { + fn render_permissions(&self, permissions: f::Permissions, xattrs: bool) -> Cell { let c = self.colours.perms; let bit = |bit, chr: &'static str, style: Style| { if bit { style.paint(chr) } else { self.colours.punctuation.paint("-") } @@ -272,7 +365,7 @@ impl<U> Table<U> where U: Users { bit(permissions.other_execute, "x", c.other_execute), ]; - if permissions.attribute { + if xattrs { columns.push(c.attribute.paint("@")); } @@ -388,8 +481,8 @@ impl<U> Table<U> where U: Users { Cell::paint(style, &*group_name) } - /// Print the table to standard output, consuming it in the process. - pub fn print_table(&self, xattr: bool, show_children: bool) -> Vec<Cell> { + /// Render the table as a vector of Cells, to be displayed on standard output. + pub fn print_table(&self) -> Vec<Cell> { let mut stack = Vec::new(); let mut cells = Vec::new(); @@ -397,19 +490,26 @@ impl<U> Table<U> where U: Users { // each column, then formatting each cell in that column to be the // width of that one. let column_widths: Vec<usize> = (0 .. self.columns.len()) - .map(|n| self.rows.iter().map(|row| row.cells[n].length).max().unwrap_or(0)) + .map(|n| self.rows.iter().map(|row| row.column_width(n)).max().unwrap_or(0)) .collect(); + let total_width: usize = self.columns.len() + column_widths.iter().sum::<usize>(); + for row in self.rows.iter() { let mut cell = Cell::empty(); - for (n, width) in column_widths.iter().enumerate() { - match self.columns[n].alignment() { - Alignment::Left => { cell.append(&row.cells[n]); cell.add_spaces(width - row.cells[n].length); } - Alignment::Right => { cell.add_spaces(width - row.cells[n].length); cell.append(&row.cells[n]); } - } + if let Some(ref cells) = row.cells { + for (n, width) in column_widths.iter().enumerate() { + match self.columns[n].alignment() { + Alignment::Left => { cell.append(&cells[n]); cell.add_spaces(width - cells[n].length); } + Alignment::Right => { cell.add_spaces(width - cells[n].length); cell.append(&cells[n]); } + } - cell.add_spaces(1); + cell.add_spaces(1); + } + } + else { + cell.add_spaces(total_width) } let mut filename = String::new(); @@ -419,40 +519,27 @@ impl<U> Table<U> where U: Users { // 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 }; + 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 { - filename.push_str(&*self.colours.punctuation.paint(stack[i].ascii_art()).to_string()); - filename_length += 4; - } + for i in 1 .. row.depth + 1 { + filename.push_str(&*self.colours.punctuation.paint(stack[i].ascii_art()).to_string()); + filename_length += 4; + } - if row.children { - stack[row.depth] = if row.last { TreePart::Blank } else { TreePart::Line }; - } + 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 { - filename.push(' '); - filename_length += 1; - } + // If any tree characters have been printed, then add an extra + // space, which makes the output look much better. + if row.depth != 0 { + filename.push(' '); + filename_length += 1; } // Print the name without worrying about padding. filename.push_str(&*row.name.text); filename_length += row.name.length; - 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(); - let spaces: String = repeat(" ").take(width - name.len()).collect(); - filename.push_str(&*format!("\n{}{} {}", name, spaces, attr.size())) - } - } - cell.append(&Cell { text: filename, length: filename_length }); cells.push(cell); } diff --git a/src/output/grid_details.rs b/src/output/grid_details.rs index 4daf85a..bca3c8b 100644 --- a/src/output/grid_details.rs +++ b/src/output/grid_details.rs @@ -5,6 +5,7 @@ use term_grid as grid; use column::{Column, Cell}; use dir::Dir; +use feature::xattr::FileAttributes; use file::File; use output::details::{Details, Table}; use output::grid::Grid; @@ -15,6 +16,13 @@ pub struct GridDetails { pub details: Details, } +fn file_has_xattrs(file: &File) -> bool { + match file.path.attributes() { + Ok(attrs) => !attrs.is_empty(), + Err(_) => false, + } +} + impl GridDetails { pub fn view(&self, dir: Option<&Dir>, files: &[File]) { let columns_for_dir = match self.details.columns { @@ -23,7 +31,7 @@ impl GridDetails { }; let mut first_table = Table::with_options(self.details.colours, columns_for_dir.clone()); - let cells: Vec<_> = files.iter().map(|file| first_table.cells_for_file(file)).collect(); + let cells: Vec<_> = files.iter().map(|file| first_table.cells_for_file(file, file_has_xattrs(file))).collect(); let mut last_working_table = self.make_grid(1, &*columns_for_dir, files, cells.clone()); @@ -73,7 +81,7 @@ impl GridDetails { tables[index].add_file_with_cells(row, file, 0, false, false); } - let columns: Vec<_> = tables.iter().map(|t| t.print_table(false, false)).collect(); + let columns: Vec<_> = tables.iter().map(|t| t.print_table()).collect(); let direction = if self.grid.across { grid::Direction::LeftToRight } else { grid::Direction::TopToBottom }; |