mirror of
https://github.com/Llewellynvdm/exa.git
synced 2024-11-11 23:25:48 +00:00
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).
This commit is contained in:
parent
d009ba5938
commit
7f980935c5
@ -76,6 +76,7 @@ use std::io;
|
|||||||
use std::ops::Add;
|
use std::ops::Add;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::string::ToString;
|
use std::string::ToString;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use ansi_term::Style;
|
use ansi_term::Style;
|
||||||
|
|
||||||
@ -85,8 +86,7 @@ use datetime::zoned::TimeZone;
|
|||||||
|
|
||||||
use locale;
|
use locale;
|
||||||
|
|
||||||
use users::{OSUsers, Users};
|
use users::{OSUsers, Users, Groups};
|
||||||
use users::mock::MockUsers;
|
|
||||||
|
|
||||||
use dir::Dir;
|
use dir::Dir;
|
||||||
use feature::xattr::{Attribute, FileAttributes};
|
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
|
/// Almost all the heavy lifting is done in a Table object, which handles the
|
||||||
/// columns for each row.
|
/// columns for each row.
|
||||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
#[derive(PartialEq, Debug, Copy, Clone, Default)]
|
||||||
pub struct Details {
|
pub struct Details {
|
||||||
|
|
||||||
/// A Columns object that says which columns should be included in the
|
/// A Columns object that says which columns should be included in the
|
||||||
@ -138,6 +138,42 @@ pub struct Details {
|
|||||||
pub colours: Colours,
|
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<U: Users+Groups> {
|
||||||
|
|
||||||
|
/// 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<U>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Environment<OSUsers> {
|
||||||
|
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 {
|
impl Details {
|
||||||
|
|
||||||
/// Print the details of the given vector of files -- all of which will
|
/// Print the details of the given vector of files -- all of which will
|
||||||
@ -151,8 +187,18 @@ impl Details {
|
|||||||
None => Vec::new(),
|
None => Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Then, retrieve various environment variables.
|
||||||
|
let env = Arc::new(Environment::<OSUsers>::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.
|
// 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() }
|
if self.header { table.add_header() }
|
||||||
|
|
||||||
// Then add files to the table and print it out.
|
// 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
|
/// Adds files to the table, possibly recursively. This is easily
|
||||||
/// parallelisable, and uses a pool of threads.
|
/// parallelisable, and uses a pool of threads.
|
||||||
fn add_files_to_table<'dir, U: Users+Send>(&self, mut table: &mut Table<U>, src: Vec<File<'dir>>, depth: usize) {
|
fn add_files_to_table<'dir, U: Users+Groups+Send>(&self, mut table: &mut Table<U>, src: Vec<File<'dir>>, depth: usize) {
|
||||||
use num_cpus;
|
use num_cpus;
|
||||||
use scoped_threadpool::Pool;
|
use scoped_threadpool::Pool;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
@ -182,7 +228,7 @@ impl Details {
|
|||||||
|
|
||||||
pool.scoped(|scoped| {
|
pool.scoped(|scoped| {
|
||||||
let file_eggs = Arc::new(Mutex::new(&mut file_eggs));
|
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 {
|
for file in src {
|
||||||
let file_eggs = file_eggs.clone();
|
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;
|
let mut dir = None;
|
||||||
|
|
||||||
@ -292,7 +338,7 @@ impl Details {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct Row {
|
pub struct Row {
|
||||||
|
|
||||||
/// Vector of cells to display.
|
/// 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
|
/// A **Table** object gets built up by the view as it lists files and
|
||||||
/// directories.
|
/// directories.
|
||||||
pub struct Table<U> {
|
pub struct Table<'a, U: Users+Groups+'a> {
|
||||||
columns: Vec<Column>,
|
pub rows: Vec<Row>,
|
||||||
rows: Vec<Row>,
|
|
||||||
|
|
||||||
time: locale::Time,
|
pub columns: &'a [Column],
|
||||||
numeric: locale::Numeric,
|
pub opts: &'a Details,
|
||||||
tz: TimeZone,
|
pub env: Arc<Environment<U>>,
|
||||||
users: U,
|
|
||||||
colours: Colours,
|
|
||||||
current_year: i64,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Table<MockUsers> {
|
impl<'a, U: Users+Groups+'a> Table<'a, U> {
|
||||||
fn default() -> Table<MockUsers> {
|
|
||||||
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<OSUsers> {
|
|
||||||
|
|
||||||
/// Create a new, empty Table object, setting the caching fields to their
|
|
||||||
/// empty states.
|
|
||||||
pub fn with_options(colours: Colours, columns: Vec<Column>) -> Table<OSUsers> {
|
|
||||||
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<U> Table<U> where U: Users {
|
|
||||||
|
|
||||||
/// Add a dummy "header" row to the table, which contains the names of all
|
/// 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
|
/// the columns, underlined. This has dummy data for the cases that aren't
|
||||||
@ -384,8 +392,8 @@ impl<U> Table<U> where U: Users {
|
|||||||
pub fn add_header(&mut self) {
|
pub fn add_header(&mut self) {
|
||||||
let row = Row {
|
let row = Row {
|
||||||
depth: 0,
|
depth: 0,
|
||||||
cells: Some(self.columns.iter().map(|c| TextCell::paint_str(self.colours.header, c.header())).collect()),
|
cells: Some(self.columns.iter().map(|c| TextCell::paint_str(self.opts.colours.header, c.header())).collect()),
|
||||||
name: TextCell::paint_str(self.colours.header, "Name"),
|
name: TextCell::paint_str(self.opts.colours.header, "Name"),
|
||||||
last: false,
|
last: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -401,7 +409,7 @@ impl<U> Table<U> where U: Users {
|
|||||||
let row = Row {
|
let row = Row {
|
||||||
depth: depth,
|
depth: depth,
|
||||||
cells: None,
|
cells: None,
|
||||||
name: TextCell::paint(self.colours.broken_arrow, error_message),
|
name: TextCell::paint(self.opts.colours.broken_arrow, error_message),
|
||||||
last: last,
|
last: last,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -412,7 +420,7 @@ impl<U> Table<U> where U: Users {
|
|||||||
let row = Row {
|
let row = Row {
|
||||||
depth: depth,
|
depth: depth,
|
||||||
cells: None,
|
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,
|
last: last,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -423,7 +431,7 @@ impl<U> Table<U> where U: Users {
|
|||||||
let width = DisplayWidth::from(&*file.name);
|
let width = DisplayWidth::from(&*file.name);
|
||||||
|
|
||||||
TextCell {
|
TextCell {
|
||||||
contents: filename(file, &self.colours, links),
|
contents: filename(file, &self.opts.colours, links),
|
||||||
width: width,
|
width: width,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -441,13 +449,13 @@ impl<U> Table<U> where U: Users {
|
|||||||
|
|
||||||
/// Use the list of columns to find which cells should be produced for
|
/// Use the list of columns to find which cells should be produced for
|
||||||
/// this file, per-column.
|
/// this file, per-column.
|
||||||
pub fn cells_for_file(&mut self, file: &File, xattrs: bool) -> Vec<TextCell> {
|
pub fn cells_for_file(&self, file: &File, xattrs: bool) -> Vec<TextCell> {
|
||||||
self.columns.clone().iter()
|
self.columns.clone().iter()
|
||||||
.map(|c| self.display(file, c, xattrs))
|
.map(|c| self.display(file, c, xattrs))
|
||||||
.collect()
|
.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::*;
|
use output::column::TimeType::*;
|
||||||
|
|
||||||
match *column {
|
match *column {
|
||||||
@ -466,37 +474,39 @@ impl<U> Table<U> where U: Users {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render_permissions(&self, permissions: f::Permissions, xattrs: bool) -> TextCell {
|
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| {
|
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 {
|
let file_type = match permissions.file_type {
|
||||||
f::Type::File => self.colours.filetypes.normal.paint("."),
|
f::Type::File => types.normal.paint("."),
|
||||||
f::Type::Directory => self.colours.filetypes.directory.paint("d"),
|
f::Type::Directory => types.directory.paint("d"),
|
||||||
f::Type::Pipe => self.colours.filetypes.special.paint("|"),
|
f::Type::Pipe => types.special.paint("|"),
|
||||||
f::Type::Link => self.colours.filetypes.symlink.paint("l"),
|
f::Type::Link => types.symlink.paint("l"),
|
||||||
f::Type::Special => self.colours.filetypes.special.paint("?"),
|
f::Type::Special => types.special.paint("?"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let x_colour = if let f::Type::File = permissions.file_type { c.user_execute_file }
|
let x_colour = if let f::Type::File = permissions.file_type { perms.user_execute_file }
|
||||||
else { c.user_execute_other };
|
else { perms.user_execute_other };
|
||||||
|
|
||||||
let mut chars = vec![
|
let mut chars = vec![
|
||||||
file_type,
|
file_type,
|
||||||
bit(permissions.user_read, "r", c.user_read),
|
bit(permissions.user_read, "r", perms.user_read),
|
||||||
bit(permissions.user_write, "w", c.user_write),
|
bit(permissions.user_write, "w", perms.user_write),
|
||||||
bit(permissions.user_execute, "x", x_colour),
|
bit(permissions.user_execute, "x", x_colour),
|
||||||
bit(permissions.group_read, "r", c.group_read),
|
bit(permissions.group_read, "r", perms.group_read),
|
||||||
bit(permissions.group_write, "w", c.group_write),
|
bit(permissions.group_write, "w", perms.group_write),
|
||||||
bit(permissions.group_execute, "x", c.group_execute),
|
bit(permissions.group_execute, "x", perms.group_execute),
|
||||||
bit(permissions.other_read, "r", c.other_read),
|
bit(permissions.other_read, "r", perms.other_read),
|
||||||
bit(permissions.other_write, "w", c.other_write),
|
bit(permissions.other_write, "w", perms.other_write),
|
||||||
bit(permissions.other_execute, "x", c.other_execute),
|
bit(permissions.other_execute, "x", perms.other_execute),
|
||||||
];
|
];
|
||||||
|
|
||||||
if xattrs {
|
if xattrs {
|
||||||
chars.push(c.attribute.paint("@"));
|
chars.push(perms.attribute.paint("@"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// As these are all ASCII characters, we can guarantee that they’re
|
// As these are all ASCII characters, we can guarantee that they’re
|
||||||
@ -511,21 +521,21 @@ impl<U> Table<U> where U: Users {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render_links(&self, links: f::Links) -> TextCell {
|
fn render_links(&self, links: f::Links) -> TextCell {
|
||||||
let style = if links.multiple { self.colours.links.multi_link_file }
|
let style = if links.multiple { self.opts.colours.links.multi_link_file }
|
||||||
else { self.colours.links.normal };
|
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 {
|
fn render_blocks(&self, blocks: f::Blocks) -> TextCell {
|
||||||
match blocks {
|
match blocks {
|
||||||
f::Blocks::Some(blk) => TextCell::paint(self.colours.blocks, blk.to_string()),
|
f::Blocks::Some(blk) => TextCell::paint(self.opts.colours.blocks, blk.to_string()),
|
||||||
f::Blocks::None => TextCell::blank(self.colours.punctuation),
|
f::Blocks::None => TextCell::blank(self.opts.colours.punctuation),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_inode(&self, inode: f::Inode) -> TextCell {
|
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 {
|
fn render_size(&self, size: f::Size, size_format: SizeFormat) -> TextCell {
|
||||||
@ -534,26 +544,26 @@ impl<U> Table<U> where U: Users {
|
|||||||
|
|
||||||
let size = match size {
|
let size = match size {
|
||||||
f::Size::Some(s) => s,
|
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 {
|
let result = match size_format {
|
||||||
SizeFormat::DecimalBytes => decimal_prefix(size as f64),
|
SizeFormat::DecimalBytes => decimal_prefix(size as f64),
|
||||||
SizeFormat::BinaryBytes => binary_prefix(size as f64),
|
SizeFormat::BinaryBytes => binary_prefix(size as f64),
|
||||||
SizeFormat::JustBytes => {
|
SizeFormat::JustBytes => {
|
||||||
let string = self.numeric.format_int(size);
|
let string = self.env.numeric.format_int(size);
|
||||||
return TextCell::paint(self.colours.size.numbers, string);
|
return TextCell::paint(self.opts.colours.size.numbers, string);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let (prefix, n) = match result {
|
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)
|
Prefixed(p, n) => (p, n)
|
||||||
};
|
};
|
||||||
|
|
||||||
let symbol = prefix.symbol();
|
let symbol = prefix.symbol();
|
||||||
let number = if n < 10f64 { self.numeric.format_float(n, 1) }
|
let number = if n < 10f64 { self.env.numeric.format_float(n, 1) }
|
||||||
else { self.numeric.format_int(n as isize) };
|
else { self.env.numeric.format_int(n as isize) };
|
||||||
|
|
||||||
// The numbers and symbols are guaranteed to be written in ASCII, so
|
// The numbers and symbols are guaranteed to be written in ASCII, so
|
||||||
// we can skip the display width calculation.
|
// we can skip the display width calculation.
|
||||||
@ -562,34 +572,34 @@ impl<U> Table<U> where U: Users {
|
|||||||
TextCell {
|
TextCell {
|
||||||
width: width,
|
width: width,
|
||||||
contents: vec![
|
contents: vec![
|
||||||
self.colours.size.numbers.paint(number),
|
self.opts.colours.size.numbers.paint(number),
|
||||||
self.colours.size.unit.paint(symbol),
|
self.opts.colours.size.unit.paint(symbol),
|
||||||
].into(),
|
].into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(trivial_numeric_casts)]
|
#[allow(trivial_numeric_casts)]
|
||||||
fn render_time(&self, timestamp: f::Time) -> TextCell {
|
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 {
|
let datestamp = if date.year() == self.env.current_year {
|
||||||
DATE_AND_TIME.format(&date, &self.time)
|
DATE_AND_TIME.format(&date, &self.env.time)
|
||||||
}
|
}
|
||||||
else {
|
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 {
|
fn render_git_status(&self, git: f::Git) -> TextCell {
|
||||||
let git_char = |status| match status {
|
let git_char = |status| match status {
|
||||||
f::GitStatus::NotModified => self.colours.punctuation.paint("-"),
|
f::GitStatus::NotModified => self.opts.colours.punctuation.paint("-"),
|
||||||
f::GitStatus::New => self.colours.git.new.paint("N"),
|
f::GitStatus::New => self.opts.colours.git.new.paint("N"),
|
||||||
f::GitStatus::Modified => self.colours.git.modified.paint("M"),
|
f::GitStatus::Modified => self.opts.colours.git.modified.paint("M"),
|
||||||
f::GitStatus::Deleted => self.colours.git.deleted.paint("D"),
|
f::GitStatus::Deleted => self.opts.colours.git.deleted.paint("D"),
|
||||||
f::GitStatus::Renamed => self.colours.git.renamed.paint("R"),
|
f::GitStatus::Renamed => self.opts.colours.git.renamed.paint("R"),
|
||||||
f::GitStatus::TypeChange => self.colours.git.typechange.paint("T"),
|
f::GitStatus::TypeChange => self.opts.colours.git.typechange.paint("T"),
|
||||||
};
|
};
|
||||||
|
|
||||||
TextCell {
|
TextCell {
|
||||||
@ -601,34 +611,38 @@ impl<U> Table<U> where U: Users {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_user(&mut self, user: f::User) -> TextCell {
|
fn render_user(&self, user: f::User) -> TextCell {
|
||||||
let user_name = match self.users.get_user_by_uid(user.0) {
|
let users = self.env.users.lock().unwrap();
|
||||||
Some(user) => user.name,
|
|
||||||
|
|
||||||
|
let user_name = match users.get_user_by_uid(user.0) {
|
||||||
|
Some(user) => (*user.name).clone(),
|
||||||
None => user.0.to_string(),
|
None => user.0.to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let style = if self.users.get_current_uid() == user.0 { self.colours.users.user_you }
|
let style = if users.get_current_uid() == user.0 { self.opts.colours.users.user_you }
|
||||||
else { self.colours.users.user_someone_else };
|
else { self.opts.colours.users.user_someone_else };
|
||||||
TextCell::paint(style, user_name)
|
TextCell::paint(style, user_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_group(&mut self, group: f::Group) -> TextCell {
|
fn render_group(&self, group: f::Group) -> TextCell {
|
||||||
let mut style = self.colours.users.group_not_yours;
|
let mut style = self.opts.colours.users.group_not_yours;
|
||||||
|
|
||||||
let group = match self.users.get_group_by_gid(group.0) {
|
let users = self.env.users.lock().unwrap();
|
||||||
Some(g) => g,
|
let group = match users.get_group_by_gid(group.0) {
|
||||||
|
Some(g) => (*g).clone(),
|
||||||
None => return TextCell::paint(style, group.0.to_string()),
|
None => return TextCell::paint(style, group.0.to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let current_uid = self.users.get_current_uid();
|
let current_uid = users.get_current_uid();
|
||||||
if let Some(current_user) = self.users.get_user_by_uid(current_uid) {
|
if let Some(current_user) = users.get_user_by_uid(current_uid) {
|
||||||
if current_user.primary_group == group.gid
|
if current_user.primary_group == group.gid
|
||||||
|| group.members.contains(¤t_user.name) {
|
|| 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.
|
/// Render the table as a vector of Cells, to be displayed on standard output.
|
||||||
@ -667,7 +681,7 @@ impl<U> Table<U> where U: Users {
|
|||||||
let mut filename = TextCell::default();
|
let mut filename = TextCell::default();
|
||||||
|
|
||||||
for tree_part in tree_trunk.new_row(row.depth, row.last) {
|
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
|
// If any tree characters have been printed, then add an extra
|
||||||
@ -699,10 +713,12 @@ lazy_static! {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod 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::File;
|
||||||
pub use file::fields as f;
|
pub use file::fields as f;
|
||||||
pub use output::column::Column;
|
pub use output::column::{Column, Columns};
|
||||||
pub use output::cell::TextCell;
|
pub use output::cell::TextCell;
|
||||||
|
|
||||||
pub use users::{User, Group, uid_t, gid_t};
|
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::Style;
|
||||||
pub use ansi_term::Colour::*;
|
pub use ansi_term::Colour::*;
|
||||||
|
|
||||||
|
impl Default for Environment<MockUsers> {
|
||||||
|
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::<MockUsers>::default()),
|
||||||
|
rows: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn newser(uid: uid_t, name: &str, group: gid_t) -> User {
|
pub fn newser(uid: uid_t, name: &str, group: gid_t) -> User {
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
User {
|
User {
|
||||||
uid: uid,
|
uid: uid,
|
||||||
name: name.to_string(),
|
name: Arc::new(name.to_string()),
|
||||||
primary_group: group,
|
primary_group: group,
|
||||||
home_dir: String::new(),
|
home_dir: String::new(),
|
||||||
shell: 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 {
|
mod users {
|
||||||
#![allow(unused_results)]
|
#![allow(unused_results)]
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn named() {
|
fn named() {
|
||||||
let mut table = Table::default();
|
let columns = Columns::default().for_dir(None);
|
||||||
table.colours.users.user_you = Red.bold();
|
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);
|
let mut users = MockUsers::with_current_uid(1000);
|
||||||
users.add_user(newser(1000, "enoch", 100));
|
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 user = f::User(1000);
|
||||||
let expected = TextCell::paint_str(Red.bold(), "enoch");
|
let expected = TextCell::paint_str(Red.bold(), "enoch");
|
||||||
@ -748,11 +792,14 @@ pub mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unnamed() {
|
fn unnamed() {
|
||||||
let mut table = Table::default();
|
let columns = Columns::default().for_dir(None);
|
||||||
table.colours.users.user_you = Cyan.bold();
|
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);
|
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 user = f::User(1000);
|
||||||
let expected = TextCell::paint_str(Cyan.bold(), "1000");
|
let expected = TextCell::paint_str(Cyan.bold(), "1000");
|
||||||
@ -761,9 +808,13 @@ pub mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn different_named() {
|
fn different_named() {
|
||||||
let mut table = Table::default();
|
let columns = Columns::default().for_dir(None);
|
||||||
table.colours.users.user_someone_else = Green.bold();
|
let mut details = Details::default();
|
||||||
table.users.add_user(newser(1000, "enoch", 100));
|
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 user = f::User(1000);
|
||||||
let expected = TextCell::paint_str(Green.bold(), "enoch");
|
let expected = TextCell::paint_str(Green.bold(), "enoch");
|
||||||
@ -772,8 +823,11 @@ pub mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn different_unnamed() {
|
fn different_unnamed() {
|
||||||
let mut table = Table::default();
|
let columns = Columns::default().for_dir(None);
|
||||||
table.colours.users.user_someone_else = Red.normal();
|
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 user = f::User(1000);
|
||||||
let expected = TextCell::paint_str(Red.normal(), "1000");
|
let expected = TextCell::paint_str(Red.normal(), "1000");
|
||||||
@ -782,8 +836,11 @@ pub mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn overflow() {
|
fn overflow() {
|
||||||
let mut table = Table::default();
|
let columns = Columns::default().for_dir(None);
|
||||||
table.colours.users.user_someone_else = Blue.underline();
|
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 user = f::User(2_147_483_648);
|
||||||
let expected = TextCell::paint_str(Blue.underline(), "2147483648");
|
let expected = TextCell::paint_str(Blue.underline(), "2147483648");
|
||||||
@ -794,15 +851,19 @@ pub mod test {
|
|||||||
mod groups {
|
mod groups {
|
||||||
#![allow(unused_results)]
|
#![allow(unused_results)]
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn named() {
|
fn named() {
|
||||||
let mut table = Table::default();
|
let columns = Columns::default().for_dir(None);
|
||||||
table.colours.users.group_not_yours = Fixed(101).normal();
|
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);
|
let mut users = MockUsers::with_current_uid(1000);
|
||||||
users.add_group(Group { gid: 100, name: "folk".to_string(), members: vec![] });
|
users.add_group(Group { gid: 100, name: Arc::new("folk".to_string()), members: vec![] });
|
||||||
table.users = users;
|
Arc::get_mut(&mut table.env).unwrap().users = Mutex::new(users);
|
||||||
|
|
||||||
let group = f::Group(100);
|
let group = f::Group(100);
|
||||||
let expected = TextCell::paint_str(Fixed(101).normal(), "folk");
|
let expected = TextCell::paint_str(Fixed(101).normal(), "folk");
|
||||||
@ -811,11 +872,14 @@ pub mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unnamed() {
|
fn unnamed() {
|
||||||
let mut table = Table::default();
|
let columns = Columns::default().for_dir(None);
|
||||||
table.colours.users.group_not_yours = Fixed(87).normal();
|
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);
|
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 group = f::Group(100);
|
||||||
let expected = TextCell::paint_str(Fixed(87).normal(), "100");
|
let expected = TextCell::paint_str(Fixed(87).normal(), "100");
|
||||||
@ -824,13 +888,16 @@ pub mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn primary() {
|
fn primary() {
|
||||||
let mut table = Table::default();
|
let columns = Columns::default().for_dir(None);
|
||||||
table.colours.users.group_yours = Fixed(64).normal();
|
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);
|
let mut users = MockUsers::with_current_uid(2);
|
||||||
users.add_user(newser(2, "eve", 100));
|
users.add_user(newser(2, "eve", 100));
|
||||||
users.add_group(Group { gid: 100, name: "folk".to_string(), members: vec![] });
|
users.add_group(Group { gid: 100, name: Arc::new("folk".to_string()), members: vec![] });
|
||||||
table.users = users;
|
Arc::get_mut(&mut table.env).unwrap().users = Mutex::new(users);
|
||||||
|
|
||||||
let group = f::Group(100);
|
let group = f::Group(100);
|
||||||
let expected = TextCell::paint_str(Fixed(64).normal(), "folk");
|
let expected = TextCell::paint_str(Fixed(64).normal(), "folk");
|
||||||
@ -839,13 +906,16 @@ pub mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn secondary() {
|
fn secondary() {
|
||||||
let mut table = Table::default();
|
let columns = Columns::default().for_dir(None);
|
||||||
table.colours.users.group_yours = Fixed(31).normal();
|
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);
|
let mut users = MockUsers::with_current_uid(2);
|
||||||
users.add_user(newser(2, "eve", 666));
|
users.add_user(newser(2, "eve", 666));
|
||||||
users.add_group(Group { gid: 100, name: "folk".to_string(), members: vec![ "eve".to_string() ] });
|
users.add_group(Group { gid: 100, name: Arc::new("folk".to_string()), members: vec![ "eve".to_string() ] });
|
||||||
table.users = users;
|
Arc::get_mut(&mut table.env).unwrap().users = Mutex::new(users);
|
||||||
|
|
||||||
let group = f::Group(100);
|
let group = f::Group(100);
|
||||||
let expected = TextCell::paint_str(Fixed(31).normal(), "folk");
|
let expected = TextCell::paint_str(Fixed(31).normal(), "folk");
|
||||||
@ -854,8 +924,11 @@ pub mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn overflow() {
|
fn overflow() {
|
||||||
let mut table = Table::default();
|
let columns = Columns::default().for_dir(None);
|
||||||
table.colours.users.group_not_yours = Blue.underline();
|
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 group = f::Group(2_147_483_648);
|
||||||
let expected = TextCell::paint_str(Blue.underline(), "2147483648");
|
let expected = TextCell::paint_str(Blue.underline(), "2147483648");
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::iter::repeat;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use ansi_term::ANSIStrings;
|
use ansi_term::ANSIStrings;
|
||||||
use users::OSUsers;
|
use users::OSUsers;
|
||||||
@ -10,7 +10,7 @@ use file::File;
|
|||||||
|
|
||||||
use output::cell::TextCell;
|
use output::cell::TextCell;
|
||||||
use output::column::Column;
|
use output::column::Column;
|
||||||
use output::details::{Details, Table};
|
use output::details::{Details, Table, Environment};
|
||||||
use output::grid::Grid;
|
use output::grid::Grid;
|
||||||
|
|
||||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||||
@ -33,19 +33,27 @@ impl GridDetails {
|
|||||||
None => Vec::new(),
|
None => Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut first_table = Table::with_options(self.details.colours, columns_for_dir.clone());
|
let env = Arc::new(Environment::default());
|
||||||
let cells = files.iter()
|
|
||||||
.map(|file| first_table.cells_for_file(file, file_has_xattrs(file)))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let file_names = files.into_iter()
|
let (cells, file_names) = {
|
||||||
.map(|file| first_table.filename_cell(file, false))
|
|
||||||
|
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::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
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::<Vec<_>>();
|
||||||
|
|
||||||
|
(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.. {
|
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 the_grid_fits = {
|
||||||
let d = grid.fit_into_columns(column_count);
|
let d = grid.fit_into_columns(column_count);
|
||||||
@ -62,14 +70,24 @@ impl GridDetails {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_table(&self, columns_for_dir: &[Column]) -> Table<OSUsers> {
|
fn make_table<'a>(&'a self, env: Arc<Environment<OSUsers>>, columns_for_dir: &'a [Column]) -> Table<OSUsers> {
|
||||||
let mut table = Table::with_options(self.details.colours, columns_for_dir.into());
|
let mut table = Table {
|
||||||
|
columns: columns_for_dir,
|
||||||
|
opts: &self.details,
|
||||||
|
env: env,
|
||||||
|
|
||||||
|
rows: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
if self.details.header { table.add_header() }
|
if self.details.header { table.add_header() }
|
||||||
table
|
table
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_grid(&self, column_count: usize, columns_for_dir: &[Column], file_names: &[TextCell], cells: Vec<Vec<TextCell>>) -> grid::Grid {
|
fn make_grid<'a>(&'a self, env: Arc<Environment<OSUsers>>, column_count: usize, columns_for_dir: &'a [Column], file_names: &[TextCell], cells: Vec<Vec<TextCell>>) -> grid::Grid {
|
||||||
let mut tables: Vec<_> = repeat(()).map(|_| self.make_table(columns_for_dir)).take(column_count).collect();
|
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();
|
let mut num_cells = cells.len();
|
||||||
if self.details.header {
|
if self.details.header {
|
||||||
|
Loading…
Reference in New Issue
Block a user