From 7f980935c5641b39735b2fb7c55ecefd1115c2ce Mon Sep 17 00:00:00 2001 From: Benjamin Sago Date: Sat, 16 Jan 2016 11:56:37 -1000 Subject: [PATCH] Use Mutex lock on only the users columns This makes use of a change in the `users` crate to change which parts of exa's code are accessed under a `Mutex`. The change is that the methods on `Users` can now take just `&self`, instead of `&mut self`. This has a knock-on effect in exa, as many methods now don't need to take a mutable `&self`, meaning that the Mutex can be moved to only containing the users information instead of having to be queried for *every column*. This means that threading should now be a lot faster, as fewer parts have to be executed on a single thread. The main change to facilitate this is that `Table`'s structure has changed: everything environmental that gets loaded at the beginning is now in an `Environment` struct, which can be mocked out if necessary, as one of `Table`'s fields. (They were kind of in a variety of places before.) Casualties include having to make some of the test code more verbose, as it explicitly takes the columns and environment as references rather than values, and those both need to be put on the stack beforehand. Also, all the colours are now hidden behind an `opts` field, so a lot of the rendering code is more verbose too (but not greatly so). --- src/output/details.rs | 385 ++++++++++++++++++++++--------------- src/output/grid_details.rs | 46 +++-- 2 files changed, 261 insertions(+), 170 deletions(-) diff --git a/src/output/details.rs b/src/output/details.rs index 1d59acd..d699b81 100644 --- a/src/output/details.rs +++ b/src/output/details.rs @@ -76,6 +76,7 @@ use std::io; use std::ops::Add; use std::path::PathBuf; use std::string::ToString; +use std::sync::{Arc, Mutex}; use ansi_term::Style; @@ -85,8 +86,7 @@ use datetime::zoned::TimeZone; use locale; -use users::{OSUsers, Users}; -use users::mock::MockUsers; +use users::{OSUsers, Users, Groups}; use dir::Dir; use feature::xattr::{Attribute, FileAttributes}; @@ -111,7 +111,7 @@ use super::filename; /// /// Almost all the heavy lifting is done in a Table object, which handles the /// columns for each row. -#[derive(PartialEq, Debug, Copy, Clone)] +#[derive(PartialEq, Debug, Copy, Clone, Default)] pub struct Details { /// A Columns object that says which columns should be included in the @@ -138,6 +138,42 @@ pub struct Details { pub colours: Colours, } +/// The **environment** struct contains any data that could change between +/// running instances of exa, depending on the user's computer's configuration. +/// +/// Any environment field should be able to be mocked up for test runs. +pub struct Environment { + + /// The year of the current time. This gets used to determine which date + /// format to use. + current_year: i64, + + /// Localisation rules for formatting numbers. + numeric: locale::Numeric, + + /// Localisation rules for formatting timestamps. + time: locale::Time, + + /// The computer's current time zone. This gets used to determine how to + /// offset files' timestamps. + tz: TimeZone, + + /// Mapping cache of user IDs to usernames. + users: Mutex, +} + +impl Default for Environment { + fn default() -> Self { + Environment { + current_year: LocalDateTime::now().year(), + numeric: locale::Numeric::load_user_locale().unwrap_or_else(|_| locale::Numeric::english()), + time: locale::Time::load_user_locale().unwrap_or_else(|_| locale::Time::english()), + tz: TimeZone::localtime().unwrap(), + users: Mutex::new(OSUsers::empty_cache()), + } + } +} + impl Details { /// Print the details of the given vector of files -- all of which will @@ -151,8 +187,18 @@ impl Details { None => Vec::new(), }; + // Then, retrieve various environment variables. + let env = Arc::new(Environment::::default()); + + // Build the table to put rows in. + let mut table = Table { + columns: &*columns_for_dir, + opts: &self, + env: env, + rows: Vec::new(), + }; + // Next, add a header if the user requests it. - let mut table = Table::with_options(self.colours, columns_for_dir); if self.header { table.add_header() } // Then add files to the table and print it out. @@ -164,7 +210,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+Send>(&self, mut table: &mut Table, src: Vec>, depth: usize) { + fn add_files_to_table<'dir, U: Users+Groups+Send>(&self, mut table: &mut Table, src: Vec>, depth: usize) { use num_cpus; use scoped_threadpool::Pool; use std::sync::{Arc, Mutex}; @@ -182,7 +228,7 @@ impl Details { pool.scoped(|scoped| { let file_eggs = Arc::new(Mutex::new(&mut file_eggs)); - let table = Arc::new(Mutex::new(&mut table)); + let table = Arc::new(&mut table); for file in src { let file_eggs = file_eggs.clone(); @@ -207,7 +253,7 @@ impl Details { }, }; - let cells = table.lock().unwrap().cells_for_file(&file, !xattrs.is_empty()); + let cells = table.cells_for_file(&file, !xattrs.is_empty()); let mut dir = None; @@ -292,7 +338,7 @@ impl Details { } -struct Row { +pub struct Row { /// Vector of cells to display. /// @@ -330,53 +376,15 @@ impl Row { /// A **Table** object gets built up by the view as it lists files and /// directories. -pub struct Table { - columns: Vec, - rows: Vec, +pub struct Table<'a, U: Users+Groups+'a> { + pub rows: Vec, - time: locale::Time, - numeric: locale::Numeric, - tz: TimeZone, - users: U, - colours: Colours, - current_year: i64, + pub columns: &'a [Column], + pub opts: &'a Details, + pub env: Arc>, } -impl Default for Table { - fn default() -> Table { - Table { - columns: Columns::default().for_dir(None), - rows: Vec::new(), - time: locale::Time::english(), - numeric: locale::Numeric::english(), - tz: TimeZone::localtime().unwrap(), - users: MockUsers::with_current_uid(0), - colours: Colours::default(), - current_year: 1234, - } - } -} - -impl Table { - - /// Create a new, empty Table object, setting the caching fields to their - /// empty states. - pub fn with_options(colours: Colours, columns: Vec) -> Table { - Table { - columns: columns, - rows: Vec::new(), - - time: locale::Time::load_user_locale().unwrap_or_else(|_| locale::Time::english()), - numeric: locale::Numeric::load_user_locale().unwrap_or_else(|_| locale::Numeric::english()), - tz: TimeZone::localtime().unwrap(), - users: OSUsers::empty_cache(), - colours: colours, - current_year: LocalDateTime::now().year(), - } - } -} - -impl Table where U: Users { +impl<'a, U: Users+Groups+'a> Table<'a, U> { /// 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 @@ -384,8 +392,8 @@ impl Table where U: Users { pub fn add_header(&mut self) { let row = Row { depth: 0, - cells: Some(self.columns.iter().map(|c| TextCell::paint_str(self.colours.header, c.header())).collect()), - name: TextCell::paint_str(self.colours.header, "Name"), + cells: Some(self.columns.iter().map(|c| TextCell::paint_str(self.opts.colours.header, c.header())).collect()), + name: TextCell::paint_str(self.opts.colours.header, "Name"), last: false, }; @@ -401,7 +409,7 @@ impl Table where U: Users { let row = Row { depth: depth, cells: None, - name: TextCell::paint(self.colours.broken_arrow, error_message), + name: TextCell::paint(self.opts.colours.broken_arrow, error_message), last: last, }; @@ -412,7 +420,7 @@ impl Table where U: Users { let row = Row { depth: depth, cells: None, - name: TextCell::paint(self.colours.perms.attribute, format!("{} (len {})", xattr.name, xattr.size)), + name: TextCell::paint(self.opts.colours.perms.attribute, format!("{} (len {})", xattr.name, xattr.size)), last: last, }; @@ -423,7 +431,7 @@ impl Table where U: Users { let width = DisplayWidth::from(&*file.name); TextCell { - contents: filename(file, &self.colours, links), + contents: filename(file, &self.opts.colours, links), width: width, } } @@ -441,13 +449,13 @@ impl Table 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, xattrs: bool) -> Vec { + pub fn cells_for_file(&self, file: &File, xattrs: bool) -> Vec { self.columns.clone().iter() .map(|c| self.display(file, c, xattrs)) .collect() } - fn display(&mut self, file: &File, column: &Column, xattrs: bool) -> TextCell { + fn display(&self, file: &File, column: &Column, xattrs: bool) -> TextCell { use output::column::TimeType::*; match *column { @@ -466,37 +474,39 @@ impl Table where U: Users { } fn render_permissions(&self, permissions: f::Permissions, xattrs: bool) -> TextCell { - let c = self.colours.perms; + let perms = self.opts.colours.perms; + let types = self.opts.colours.filetypes; + let bit = |bit, chr: &'static str, style: Style| { - if bit { style.paint(chr) } else { self.colours.punctuation.paint("-") } + if bit { style.paint(chr) } else { self.opts.colours.punctuation.paint("-") } }; let file_type = match permissions.file_type { - f::Type::File => self.colours.filetypes.normal.paint("."), - f::Type::Directory => self.colours.filetypes.directory.paint("d"), - f::Type::Pipe => self.colours.filetypes.special.paint("|"), - f::Type::Link => self.colours.filetypes.symlink.paint("l"), - f::Type::Special => self.colours.filetypes.special.paint("?"), + f::Type::File => types.normal.paint("."), + f::Type::Directory => types.directory.paint("d"), + f::Type::Pipe => types.special.paint("|"), + f::Type::Link => types.symlink.paint("l"), + f::Type::Special => types.special.paint("?"), }; - let x_colour = if let f::Type::File = permissions.file_type { c.user_execute_file } - else { c.user_execute_other }; + let x_colour = if let f::Type::File = permissions.file_type { perms.user_execute_file } + else { perms.user_execute_other }; let mut chars = vec![ file_type, - bit(permissions.user_read, "r", c.user_read), - bit(permissions.user_write, "w", c.user_write), + bit(permissions.user_read, "r", perms.user_read), + bit(permissions.user_write, "w", perms.user_write), bit(permissions.user_execute, "x", x_colour), - bit(permissions.group_read, "r", c.group_read), - bit(permissions.group_write, "w", c.group_write), - bit(permissions.group_execute, "x", c.group_execute), - bit(permissions.other_read, "r", c.other_read), - bit(permissions.other_write, "w", c.other_write), - bit(permissions.other_execute, "x", c.other_execute), + bit(permissions.group_read, "r", perms.group_read), + bit(permissions.group_write, "w", perms.group_write), + bit(permissions.group_execute, "x", perms.group_execute), + bit(permissions.other_read, "r", perms.other_read), + bit(permissions.other_write, "w", perms.other_write), + bit(permissions.other_execute, "x", perms.other_execute), ]; if xattrs { - chars.push(c.attribute.paint("@")); + chars.push(perms.attribute.paint("@")); } // As these are all ASCII characters, we can guarantee that they’re @@ -511,21 +521,21 @@ impl Table where U: Users { } fn render_links(&self, links: f::Links) -> TextCell { - let style = if links.multiple { self.colours.links.multi_link_file } - else { self.colours.links.normal }; + let style = if links.multiple { self.opts.colours.links.multi_link_file } + else { self.opts.colours.links.normal }; - TextCell::paint(style, self.numeric.format_int(links.count)) + TextCell::paint(style, self.env.numeric.format_int(links.count)) } fn render_blocks(&self, blocks: f::Blocks) -> TextCell { match blocks { - f::Blocks::Some(blk) => TextCell::paint(self.colours.blocks, blk.to_string()), - f::Blocks::None => TextCell::blank(self.colours.punctuation), + f::Blocks::Some(blk) => TextCell::paint(self.opts.colours.blocks, blk.to_string()), + f::Blocks::None => TextCell::blank(self.opts.colours.punctuation), } } fn render_inode(&self, inode: f::Inode) -> TextCell { - TextCell::paint(self.colours.inode, inode.0.to_string()) + TextCell::paint(self.opts.colours.inode, inode.0.to_string()) } fn render_size(&self, size: f::Size, size_format: SizeFormat) -> TextCell { @@ -534,26 +544,26 @@ impl Table where U: Users { let size = match size { f::Size::Some(s) => s, - f::Size::None => return TextCell::blank(self.colours.punctuation), + f::Size::None => return TextCell::blank(self.opts.colours.punctuation), }; let result = match size_format { SizeFormat::DecimalBytes => decimal_prefix(size as f64), SizeFormat::BinaryBytes => binary_prefix(size as f64), SizeFormat::JustBytes => { - let string = self.numeric.format_int(size); - return TextCell::paint(self.colours.size.numbers, string); + let string = self.env.numeric.format_int(size); + return TextCell::paint(self.opts.colours.size.numbers, string); }, }; let (prefix, n) = match result { - Standalone(b) => return TextCell::paint(self.colours.size.numbers, b.to_string()), + Standalone(b) => return TextCell::paint(self.opts.colours.size.numbers, b.to_string()), Prefixed(p, n) => (p, n) }; let symbol = prefix.symbol(); - let number = if n < 10f64 { self.numeric.format_float(n, 1) } - else { self.numeric.format_int(n as isize) }; + let number = if n < 10f64 { self.env.numeric.format_float(n, 1) } + else { self.env.numeric.format_int(n as isize) }; // The numbers and symbols are guaranteed to be written in ASCII, so // we can skip the display width calculation. @@ -562,34 +572,34 @@ impl Table where U: Users { TextCell { width: width, contents: vec![ - self.colours.size.numbers.paint(number), - self.colours.size.unit.paint(symbol), + self.opts.colours.size.numbers.paint(number), + self.opts.colours.size.unit.paint(symbol), ].into(), } } #[allow(trivial_numeric_casts)] fn render_time(&self, timestamp: f::Time) -> TextCell { - let date = self.tz.at(LocalDateTime::at(timestamp.0 as i64)); + let date = self.env.tz.at(LocalDateTime::at(timestamp.0 as i64)); - let datestamp = if date.year() == self.current_year { - DATE_AND_TIME.format(&date, &self.time) + let datestamp = if date.year() == self.env.current_year { + DATE_AND_TIME.format(&date, &self.env.time) } else { - DATE_AND_YEAR.format(&date, &self.time) + DATE_AND_YEAR.format(&date, &self.env.time) }; - TextCell::paint(self.colours.date, datestamp) + TextCell::paint(self.opts.colours.date, datestamp) } fn render_git_status(&self, git: f::Git) -> TextCell { let git_char = |status| match status { - f::GitStatus::NotModified => self.colours.punctuation.paint("-"), - f::GitStatus::New => self.colours.git.new.paint("N"), - f::GitStatus::Modified => self.colours.git.modified.paint("M"), - f::GitStatus::Deleted => self.colours.git.deleted.paint("D"), - f::GitStatus::Renamed => self.colours.git.renamed.paint("R"), - f::GitStatus::TypeChange => self.colours.git.typechange.paint("T"), + f::GitStatus::NotModified => self.opts.colours.punctuation.paint("-"), + f::GitStatus::New => self.opts.colours.git.new.paint("N"), + f::GitStatus::Modified => self.opts.colours.git.modified.paint("M"), + f::GitStatus::Deleted => self.opts.colours.git.deleted.paint("D"), + f::GitStatus::Renamed => self.opts.colours.git.renamed.paint("R"), + f::GitStatus::TypeChange => self.opts.colours.git.typechange.paint("T"), }; TextCell { @@ -601,34 +611,38 @@ impl Table where U: Users { } } - fn render_user(&mut self, user: f::User) -> TextCell { - let user_name = match self.users.get_user_by_uid(user.0) { - Some(user) => user.name, + fn render_user(&self, user: f::User) -> TextCell { + let users = self.env.users.lock().unwrap(); + + + let user_name = match users.get_user_by_uid(user.0) { + Some(user) => (*user.name).clone(), None => user.0.to_string(), }; - let style = if self.users.get_current_uid() == user.0 { self.colours.users.user_you } - else { self.colours.users.user_someone_else }; + let style = if users.get_current_uid() == user.0 { self.opts.colours.users.user_you } + else { self.opts.colours.users.user_someone_else }; TextCell::paint(style, user_name) } - fn render_group(&mut self, group: f::Group) -> TextCell { - let mut style = self.colours.users.group_not_yours; + fn render_group(&self, group: f::Group) -> TextCell { + let mut style = self.opts.colours.users.group_not_yours; - let group = match self.users.get_group_by_gid(group.0) { - Some(g) => g, + let users = self.env.users.lock().unwrap(); + let group = match users.get_group_by_gid(group.0) { + Some(g) => (*g).clone(), None => return TextCell::paint(style, group.0.to_string()), }; - let current_uid = self.users.get_current_uid(); - if let Some(current_user) = self.users.get_user_by_uid(current_uid) { + let current_uid = users.get_current_uid(); + if let Some(current_user) = users.get_user_by_uid(current_uid) { if current_user.primary_group == group.gid || group.members.contains(¤t_user.name) { - style = self.colours.users.group_yours; + style = self.opts.colours.users.group_yours; } } - TextCell::paint(style, group.name) + TextCell::paint(style, (*group.name).clone()) } /// Render the table as a vector of Cells, to be displayed on standard output. @@ -667,7 +681,7 @@ impl Table where U: Users { let mut filename = TextCell::default(); for tree_part in tree_trunk.new_row(row.depth, row.last) { - filename.push(self.colours.punctuation.paint(tree_part.ascii_art()), 4); + filename.push(self.opts.colours.punctuation.paint(tree_part.ascii_art()), 4); } // If any tree characters have been printed, then add an extra @@ -699,10 +713,12 @@ lazy_static! { #[cfg(test)] pub mod test { - pub use super::Table; + pub use super::{Table, Environment, Details}; + pub use std::sync::Mutex; + pub use file::File; pub use file::fields as f; - pub use output::column::Column; + pub use output::column::{Column, Columns}; pub use output::cell::TextCell; pub use users::{User, Group, uid_t, gid_t}; @@ -711,35 +727,63 @@ pub mod test { pub use ansi_term::Style; pub use ansi_term::Colour::*; + impl Default for Environment { + fn default() -> Self { + use locale; + use datetime::zoned::TimeZone; + use users::mock::MockUsers; + use std::sync::Mutex; + + Environment { + current_year: 1234, + numeric: locale::Numeric::english(), + time: locale::Time::english(), + tz: TimeZone::localtime().unwrap(), + users: Mutex::new(MockUsers::with_current_uid(0)), + } + } + } + + pub fn new_table<'a>(columns: &'a [Column], details: &'a Details) -> Table<'a, MockUsers> { + use std::sync::Arc; + + Table { + columns: columns, + opts: details, + env: Arc::new(Environment::::default()), + rows: Vec::new(), + } + } + + pub fn newser(uid: uid_t, name: &str, group: gid_t) -> User { + use std::sync::Arc; + User { uid: uid, - name: name.to_string(), + name: Arc::new(name.to_string()), primary_group: group, home_dir: String::new(), shell: String::new(), } } - // These tests create a new, default Table object, then fill in the - // expected style in a certain way. This means we can check that the - // right style is being used, as otherwise, it would just be plain. - // - // Doing things with fields is way easier than having to fake the entire - // Metadata struct, which is what I was doing before! - mod users { #![allow(unused_results)] use super::*; + use std::sync::Arc; #[test] fn named() { - let mut table = Table::default(); - table.colours.users.user_you = Red.bold(); + let columns = Columns::default().for_dir(None); + let mut details = Details::default(); + details.colours.users.user_you = Red.bold(); + + let mut table = new_table(&columns, &details); let mut users = MockUsers::with_current_uid(1000); users.add_user(newser(1000, "enoch", 100)); - table.users = users; + Arc::get_mut(&mut table.env).unwrap().users = Mutex::new(users); let user = f::User(1000); let expected = TextCell::paint_str(Red.bold(), "enoch"); @@ -748,11 +792,14 @@ pub mod test { #[test] fn unnamed() { - let mut table = Table::default(); - table.colours.users.user_you = Cyan.bold(); + let columns = Columns::default().for_dir(None); + let mut details = Details::default(); + details.colours.users.user_you = Cyan.bold(); + + let mut table = new_table(&columns, &details); let users = MockUsers::with_current_uid(1000); - table.users = users; + Arc::get_mut(&mut table.env).unwrap().users = Mutex::new(users); let user = f::User(1000); let expected = TextCell::paint_str(Cyan.bold(), "1000"); @@ -761,9 +808,13 @@ pub mod test { #[test] fn different_named() { - let mut table = Table::default(); - table.colours.users.user_someone_else = Green.bold(); - table.users.add_user(newser(1000, "enoch", 100)); + let columns = Columns::default().for_dir(None); + let mut details = Details::default(); + details.colours.users.user_someone_else = Green.bold(); + + let table = new_table(&columns, &details); + + table.env.users.lock().unwrap().add_user(newser(1000, "enoch", 100)); let user = f::User(1000); let expected = TextCell::paint_str(Green.bold(), "enoch"); @@ -772,8 +823,11 @@ pub mod test { #[test] fn different_unnamed() { - let mut table = Table::default(); - table.colours.users.user_someone_else = Red.normal(); + let columns = Columns::default().for_dir(None); + let mut details = Details::default(); + details.colours.users.user_someone_else = Red.normal(); + + let table = new_table(&columns, &details); let user = f::User(1000); let expected = TextCell::paint_str(Red.normal(), "1000"); @@ -782,8 +836,11 @@ pub mod test { #[test] fn overflow() { - let mut table = Table::default(); - table.colours.users.user_someone_else = Blue.underline(); + let columns = Columns::default().for_dir(None); + let mut details = Details::default(); + details.colours.users.user_someone_else = Blue.underline(); + + let table = new_table(&columns, &details); let user = f::User(2_147_483_648); let expected = TextCell::paint_str(Blue.underline(), "2147483648"); @@ -794,15 +851,19 @@ pub mod test { mod groups { #![allow(unused_results)] use super::*; + use std::sync::Arc; #[test] fn named() { - let mut table = Table::default(); - table.colours.users.group_not_yours = Fixed(101).normal(); + let columns = Columns::default().for_dir(None); + let mut details = Details::default(); + details.colours.users.group_not_yours = Fixed(101).normal(); + + let mut table = new_table(&columns, &details); let mut users = MockUsers::with_current_uid(1000); - users.add_group(Group { gid: 100, name: "folk".to_string(), members: vec![] }); - table.users = users; + users.add_group(Group { gid: 100, name: Arc::new("folk".to_string()), members: vec![] }); + Arc::get_mut(&mut table.env).unwrap().users = Mutex::new(users); let group = f::Group(100); let expected = TextCell::paint_str(Fixed(101).normal(), "folk"); @@ -811,11 +872,14 @@ pub mod test { #[test] fn unnamed() { - let mut table = Table::default(); - table.colours.users.group_not_yours = Fixed(87).normal(); + let columns = Columns::default().for_dir(None); + let mut details = Details::default(); + details.colours.users.group_not_yours = Fixed(87).normal(); + + let mut table = new_table(&columns, &details); let users = MockUsers::with_current_uid(1000); - table.users = users; + Arc::get_mut(&mut table.env).unwrap().users = Mutex::new(users); let group = f::Group(100); let expected = TextCell::paint_str(Fixed(87).normal(), "100"); @@ -824,13 +888,16 @@ pub mod test { #[test] fn primary() { - let mut table = Table::default(); - table.colours.users.group_yours = Fixed(64).normal(); + let columns = Columns::default().for_dir(None); + let mut details = Details::default(); + details.colours.users.group_yours = Fixed(64).normal(); + + let mut table = new_table(&columns, &details); let mut users = MockUsers::with_current_uid(2); users.add_user(newser(2, "eve", 100)); - users.add_group(Group { gid: 100, name: "folk".to_string(), members: vec![] }); - table.users = users; + users.add_group(Group { gid: 100, name: Arc::new("folk".to_string()), members: vec![] }); + Arc::get_mut(&mut table.env).unwrap().users = Mutex::new(users); let group = f::Group(100); let expected = TextCell::paint_str(Fixed(64).normal(), "folk"); @@ -839,13 +906,16 @@ pub mod test { #[test] fn secondary() { - let mut table = Table::default(); - table.colours.users.group_yours = Fixed(31).normal(); + let columns = Columns::default().for_dir(None); + let mut details = Details::default(); + details.colours.users.group_yours = Fixed(31).normal(); + + let mut table = new_table(&columns, &details); let mut users = MockUsers::with_current_uid(2); users.add_user(newser(2, "eve", 666)); - users.add_group(Group { gid: 100, name: "folk".to_string(), members: vec![ "eve".to_string() ] }); - table.users = users; + users.add_group(Group { gid: 100, name: Arc::new("folk".to_string()), members: vec![ "eve".to_string() ] }); + Arc::get_mut(&mut table.env).unwrap().users = Mutex::new(users); let group = f::Group(100); let expected = TextCell::paint_str(Fixed(31).normal(), "folk"); @@ -854,8 +924,11 @@ pub mod test { #[test] fn overflow() { - let mut table = Table::default(); - table.colours.users.group_not_yours = Blue.underline(); + let columns = Columns::default().for_dir(None); + let mut details = Details::default(); + details.colours.users.group_not_yours = Blue.underline(); + + let table = new_table(&columns, &details); let group = f::Group(2_147_483_648); let expected = TextCell::paint_str(Blue.underline(), "2147483648"); diff --git a/src/output/grid_details.rs b/src/output/grid_details.rs index 3dcf4b1..d3cda3e 100644 --- a/src/output/grid_details.rs +++ b/src/output/grid_details.rs @@ -1,4 +1,4 @@ -use std::iter::repeat; +use std::sync::Arc; use ansi_term::ANSIStrings; use users::OSUsers; @@ -10,7 +10,7 @@ use file::File; use output::cell::TextCell; use output::column::Column; -use output::details::{Details, Table}; +use output::details::{Details, Table, Environment}; use output::grid::Grid; #[derive(PartialEq, Debug, Copy, Clone)] @@ -33,19 +33,27 @@ impl GridDetails { None => Vec::new(), }; - let mut first_table = Table::with_options(self.details.colours, columns_for_dir.clone()); - let cells = files.iter() - .map(|file| first_table.cells_for_file(file, file_has_xattrs(file))) - .collect::>(); + let env = Arc::new(Environment::default()); - let file_names = files.into_iter() - .map(|file| first_table.filename_cell(file, false)) + let (cells, file_names) = { + + let first_table = self.make_table(env.clone(), &*columns_for_dir); + + let cells = 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, &file_names, cells.clone()); + let file_names = files.into_iter() + .map(|file| first_table.filename_cell(file, false)) + .collect::>(); + + (cells, file_names) + }; + + let mut last_working_table = self.make_grid(env.clone(), 1, &columns_for_dir, &file_names, cells.clone()); for column_count in 2.. { - let grid = self.make_grid(column_count, &columns_for_dir, &file_names, cells.clone()); + let grid = self.make_grid(env.clone(), column_count, &columns_for_dir, &file_names, cells.clone()); let the_grid_fits = { let d = grid.fit_into_columns(column_count); @@ -62,14 +70,24 @@ impl GridDetails { } } - fn make_table(&self, columns_for_dir: &[Column]) -> Table { - let mut table = Table::with_options(self.details.colours, columns_for_dir.into()); + fn make_table<'a>(&'a self, env: Arc>, columns_for_dir: &'a [Column]) -> Table { + let mut table = Table { + columns: columns_for_dir, + opts: &self.details, + env: env, + + rows: Vec::new(), + }; + if self.details.header { table.add_header() } table } - fn make_grid(&self, column_count: usize, columns_for_dir: &[Column], file_names: &[TextCell], cells: Vec>) -> grid::Grid { - let mut tables: Vec<_> = repeat(()).map(|_| self.make_table(columns_for_dir)).take(column_count).collect(); + fn make_grid<'a>(&'a self, env: Arc>, column_count: usize, columns_for_dir: &'a [Column], file_names: &[TextCell], cells: Vec>) -> grid::Grid { + let mut tables = Vec::new(); + for _ in 0 .. column_count { + tables.push(self.make_table(env.clone(), columns_for_dir)); + } let mut num_cells = cells.len(); if self.details.header {