|
|
@@ -1,4 +1,5 @@
|
|
|
use std::cmp::max;
|
|
|
+use std::fmt;
|
|
|
use std::ops::Deref;
|
|
|
use std::sync::{Mutex, MutexGuard};
|
|
|
|
|
|
@@ -11,10 +12,214 @@ use users::UsersCache;
|
|
|
|
|
|
use output::cell::TextCell;
|
|
|
use output::colours::Colours;
|
|
|
-use output::column::{Alignment, Column};
|
|
|
use output::time::TimeFormat;
|
|
|
|
|
|
-use fs::{File, fields as f};
|
|
|
+use fs::{File, Dir, fields as f};
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+/// Options for displaying a table.
|
|
|
+pub struct Options {
|
|
|
+ pub env: Environment,
|
|
|
+ pub size_format: SizeFormat,
|
|
|
+ pub time_format: TimeFormat,
|
|
|
+ pub time_types: TimeTypes,
|
|
|
+ pub inode: bool,
|
|
|
+ pub links: bool,
|
|
|
+ pub blocks: bool,
|
|
|
+ pub group: bool,
|
|
|
+ pub git: bool
|
|
|
+}
|
|
|
+
|
|
|
+impl fmt::Debug for Options {
|
|
|
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
|
|
+ // I had to make other types derive Debug,
|
|
|
+ // and Mutex<UsersCache> is not that!
|
|
|
+ writeln!(f, "<table options>")
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl Options {
|
|
|
+ pub fn should_scan_for_git(&self) -> bool {
|
|
|
+ self.git
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn for_dir(&self, dir: Option<&Dir>) -> Vec<Column> {
|
|
|
+ let mut columns = vec![];
|
|
|
+
|
|
|
+ if self.inode {
|
|
|
+ columns.push(Column::Inode);
|
|
|
+ }
|
|
|
+
|
|
|
+ columns.push(Column::Permissions);
|
|
|
+
|
|
|
+ if self.links {
|
|
|
+ columns.push(Column::HardLinks);
|
|
|
+ }
|
|
|
+
|
|
|
+ columns.push(Column::FileSize(self.size_format));
|
|
|
+
|
|
|
+ if self.blocks {
|
|
|
+ columns.push(Column::Blocks);
|
|
|
+ }
|
|
|
+
|
|
|
+ columns.push(Column::User);
|
|
|
+
|
|
|
+ if self.group {
|
|
|
+ columns.push(Column::Group);
|
|
|
+ }
|
|
|
+
|
|
|
+ if self.time_types.modified {
|
|
|
+ columns.push(Column::Timestamp(TimeType::Modified));
|
|
|
+ }
|
|
|
+
|
|
|
+ if self.time_types.created {
|
|
|
+ columns.push(Column::Timestamp(TimeType::Created));
|
|
|
+ }
|
|
|
+
|
|
|
+ if self.time_types.accessed {
|
|
|
+ columns.push(Column::Timestamp(TimeType::Accessed));
|
|
|
+ }
|
|
|
+
|
|
|
+ if cfg!(feature="git") {
|
|
|
+ if let Some(d) = dir {
|
|
|
+ if self.should_scan_for_git() && d.has_git_repo() {
|
|
|
+ columns.push(Column::GitStatus);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ columns
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/// A table contains these.
|
|
|
+#[derive(Debug)]
|
|
|
+pub enum Column {
|
|
|
+ Permissions,
|
|
|
+ FileSize(SizeFormat),
|
|
|
+ Timestamp(TimeType),
|
|
|
+ Blocks,
|
|
|
+ User,
|
|
|
+ Group,
|
|
|
+ HardLinks,
|
|
|
+ Inode,
|
|
|
+ GitStatus,
|
|
|
+}
|
|
|
+
|
|
|
+/// Each column can pick its own **Alignment**. Usually, numbers are
|
|
|
+/// right-aligned, and text is left-aligned.
|
|
|
+#[derive(Copy, Clone)]
|
|
|
+pub enum Alignment {
|
|
|
+ Left, Right,
|
|
|
+}
|
|
|
+
|
|
|
+impl Column {
|
|
|
+
|
|
|
+ /// Get the alignment this column should use.
|
|
|
+ pub fn alignment(&self) -> Alignment {
|
|
|
+ match *self {
|
|
|
+ Column::FileSize(_)
|
|
|
+ | Column::HardLinks
|
|
|
+ | Column::Inode
|
|
|
+ | Column::Blocks
|
|
|
+ | Column::GitStatus => Alignment::Right,
|
|
|
+ _ => Alignment::Left,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Get the text that should be printed at the top, when the user elects
|
|
|
+ /// to have a header row printed.
|
|
|
+ pub fn header(&self) -> &'static str {
|
|
|
+ match *self {
|
|
|
+ Column::Permissions => "Permissions",
|
|
|
+ Column::FileSize(_) => "Size",
|
|
|
+ Column::Timestamp(t) => t.header(),
|
|
|
+ Column::Blocks => "Blocks",
|
|
|
+ Column::User => "User",
|
|
|
+ Column::Group => "Group",
|
|
|
+ Column::HardLinks => "Links",
|
|
|
+ Column::Inode => "inode",
|
|
|
+ Column::GitStatus => "Git",
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/// Formatting options for file sizes.
|
|
|
+#[derive(PartialEq, Debug, Copy, Clone)]
|
|
|
+pub enum SizeFormat {
|
|
|
+
|
|
|
+ /// Format the file size using **decimal** prefixes, such as “kilo”,
|
|
|
+ /// “mega”, or “giga”.
|
|
|
+ DecimalBytes,
|
|
|
+
|
|
|
+ /// Format the file size using **binary** prefixes, such as “kibi”,
|
|
|
+ /// “mebi”, or “gibi”.
|
|
|
+ BinaryBytes,
|
|
|
+
|
|
|
+ /// Do no formatting and just display the size as a number of bytes.
|
|
|
+ JustBytes,
|
|
|
+}
|
|
|
+
|
|
|
+impl Default for SizeFormat {
|
|
|
+ fn default() -> SizeFormat {
|
|
|
+ SizeFormat::DecimalBytes
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/// The types of a file’s time fields. These three fields are standard
|
|
|
+/// across most (all?) operating systems.
|
|
|
+#[derive(PartialEq, Debug, Copy, Clone)]
|
|
|
+pub enum TimeType {
|
|
|
+
|
|
|
+ /// The file’s accessed time (`st_atime`).
|
|
|
+ Accessed,
|
|
|
+
|
|
|
+ /// The file’s modified time (`st_mtime`).
|
|
|
+ Modified,
|
|
|
+
|
|
|
+ /// The file’s creation time (`st_ctime`).
|
|
|
+ Created,
|
|
|
+}
|
|
|
+
|
|
|
+impl TimeType {
|
|
|
+
|
|
|
+ /// Returns the text to use for a column’s heading in the columns output.
|
|
|
+ pub fn header(&self) -> &'static str {
|
|
|
+ match *self {
|
|
|
+ TimeType::Accessed => "Date Accessed",
|
|
|
+ TimeType::Modified => "Date Modified",
|
|
|
+ TimeType::Created => "Date Created",
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/// Fields for which of a file’s time fields should be displayed in the
|
|
|
+/// columns output.
|
|
|
+///
|
|
|
+/// There should always be at least one of these--there's no way to disable
|
|
|
+/// the time columns entirely (yet).
|
|
|
+#[derive(PartialEq, Debug, Copy, Clone)]
|
|
|
+pub struct TimeTypes {
|
|
|
+ pub accessed: bool,
|
|
|
+ pub modified: bool,
|
|
|
+ pub created: bool,
|
|
|
+}
|
|
|
+
|
|
|
+impl Default for TimeTypes {
|
|
|
+
|
|
|
+ /// By default, display just the ‘modified’ time. This is the most
|
|
|
+ /// common option, which is why it has this shorthand.
|
|
|
+ fn default() -> TimeTypes {
|
|
|
+ TimeTypes { accessed: false, modified: true, created: false }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
|
|
|
|
|
|
/// The **environment** struct contains any data that could change between
|
|
|
@@ -26,9 +231,6 @@ pub struct Environment {
|
|
|
/// Localisation rules for formatting numbers.
|
|
|
numeric: locale::Numeric,
|
|
|
|
|
|
- /// Rules for formatting timestamps.
|
|
|
- time_format: TimeFormat,
|
|
|
-
|
|
|
/// The computer's current time zone. This gets used to determine how to
|
|
|
/// offset files' timestamps.
|
|
|
tz: Option<TimeZone>,
|
|
|
@@ -41,10 +243,8 @@ impl Environment {
|
|
|
pub fn lock_users(&self) -> MutexGuard<UsersCache> {
|
|
|
self.users.lock().unwrap()
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-impl Default for Environment {
|
|
|
- fn default() -> Self {
|
|
|
+ pub fn load_all() -> Self {
|
|
|
let tz = match determine_time_zone() {
|
|
|
Ok(t) => Some(t),
|
|
|
Err(ref e) => {
|
|
|
@@ -53,14 +253,12 @@ impl Default for Environment {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- let time_format = TimeFormat::deduce();
|
|
|
-
|
|
|
let numeric = locale::Numeric::load_user_locale()
|
|
|
.unwrap_or_else(|_| locale::Numeric::english());
|
|
|
|
|
|
let users = Mutex::new(UsersCache::new());
|
|
|
|
|
|
- Environment { tz, time_format, numeric, users }
|
|
|
+ Environment { tz, numeric, users }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -73,10 +271,11 @@ fn determine_time_zone() -> TZResult<TimeZone> {
|
|
|
|
|
|
|
|
|
pub struct Table<'a> {
|
|
|
- columns: &'a [Column],
|
|
|
+ columns: Vec<Column>,
|
|
|
colours: &'a Colours,
|
|
|
env: &'a Environment,
|
|
|
widths: TableWidths,
|
|
|
+ time_format: &'a TimeFormat,
|
|
|
}
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
@@ -85,9 +284,10 @@ pub struct Row {
|
|
|
}
|
|
|
|
|
|
impl<'a, 'f> Table<'a> {
|
|
|
- pub fn new(columns: &'a [Column], colours: &'a Colours, env: &'a Environment) -> Table<'a> {
|
|
|
- let widths = TableWidths::zero(columns.len());
|
|
|
- Table { columns, colours, env, widths }
|
|
|
+ pub fn new(options: &'a Options, dir: Option<&'a Dir>, colours: &'a Colours) -> Table<'a> {
|
|
|
+ let colz = options.for_dir(dir);
|
|
|
+ let widths = TableWidths::zero(colz.len());
|
|
|
+ Table { columns: colz, colours, env: &options.env, widths, time_format: &options.time_format }
|
|
|
}
|
|
|
|
|
|
pub fn widths(&self) -> &TableWidths {
|
|
|
@@ -123,7 +323,7 @@ impl<'a, 'f> Table<'a> {
|
|
|
}
|
|
|
|
|
|
fn display(&self, file: &File, column: &Column, xattrs: bool) -> TextCell {
|
|
|
- use output::column::TimeType::*;
|
|
|
+ use output::table::TimeType::*;
|
|
|
|
|
|
match *column {
|
|
|
Column::Permissions => self.permissions_plus(file, xattrs).render(&self.colours),
|
|
|
@@ -135,9 +335,9 @@ impl<'a, 'f> Table<'a> {
|
|
|
Column::Group => file.group().render(&self.colours, &*self.env.lock_users()),
|
|
|
Column::GitStatus => file.git_status().render(&self.colours),
|
|
|
|
|
|
- Column::Timestamp(Modified) => file.modified_time().render(&self.colours, &self.env.tz, &self.env.time_format),
|
|
|
- Column::Timestamp(Created) => file.created_time().render( &self.colours, &self.env.tz, &self.env.time_format),
|
|
|
- Column::Timestamp(Accessed) => file.accessed_time().render(&self.colours, &self.env.tz, &self.env.time_format),
|
|
|
+ Column::Timestamp(Modified) => file.modified_time().render(&self.colours, &self.env.tz, &self.time_format),
|
|
|
+ Column::Timestamp(Created) => file.created_time().render( &self.colours, &self.env.tz, &self.time_format),
|
|
|
+ Column::Timestamp(Accessed) => file.accessed_time().render(&self.colours, &self.env.tz, &self.time_format),
|
|
|
}
|
|
|
}
|
|
|
|