| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436 |
- use std::cmp::max;
- use std::fmt;
- use std::ops::Deref;
- use std::sync::{Mutex, MutexGuard};
- use datetime::TimeZone;
- use zoneinfo_compiled::{CompiledData, Result as TZResult};
- use log::debug;
- use users::UsersCache;
- use crate::style::Colours;
- use crate::output::cell::TextCell;
- use crate::output::render::TimeRender;
- use crate::output::time::TimeFormat;
- use crate::fs::{File, fields as f};
- use crate::fs::feature::git::GitCache;
- /// Options for displaying a table.
- pub struct Options {
- pub env: Environment,
- pub size_format: SizeFormat,
- pub time_format: TimeFormat,
- pub columns: Columns,
- }
- // I had to make other types derive Debug,
- // and Mutex<UsersCache> is not that!
- impl fmt::Debug for Options {
- fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
- write!(f, "Table({:#?})", self.columns)
- }
- }
- /// Extra columns to display in the table.
- #[derive(PartialEq, Debug)]
- pub struct Columns {
- /// At least one of these timestamps will be shown.
- pub time_types: TimeTypes,
- // The rest are just on/off
- pub inode: bool,
- pub links: bool,
- pub blocks: bool,
- pub group: bool,
- pub git: bool,
- pub octal: bool,
- // Defaults to true:
- pub permissions: bool,
- pub filesize: bool,
- pub user: bool,
- }
- impl Columns {
- pub fn collect(&self, actually_enable_git: bool) -> Vec<Column> {
- let mut columns = Vec::with_capacity(4);
- if self.inode {
- columns.push(Column::Inode);
- }
- if self.octal {
- columns.push(Column::Octal);
- }
- if self.permissions {
- columns.push(Column::Permissions);
- }
- if self.links {
- columns.push(Column::HardLinks);
- }
- if self.filesize {
- columns.push(Column::FileSize);
- }
- if self.blocks {
- columns.push(Column::Blocks);
- }
- if self.user {
- 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.changed {
- columns.push(Column::Timestamp(TimeType::Changed));
- }
- 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") && self.git && actually_enable_git {
- columns.push(Column::GitStatus);
- }
- columns
- }
- }
- /// A table contains these.
- #[derive(Debug)]
- pub enum Column {
- Permissions,
- FileSize,
- Timestamp(TimeType),
- Blocks,
- User,
- Group,
- HardLinks,
- Inode,
- GitStatus,
- Octal,
- }
- /// 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",
- Column::Octal => "Octal",
- }
- }
- }
- /// 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 modified time (`st_mtime`).
- Modified,
- /// The file’s changed time (`st_ctime`)
- Changed,
- /// The file’s accessed time (`st_atime`).
- Accessed,
- /// The file’s creation time (`btime` or `birthtime`).
- 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::Modified => "Date Modified",
- TimeType::Changed => "Date Changed",
- TimeType::Accessed => "Date Accessed",
- 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 modified: bool,
- pub changed: bool,
- pub accessed: 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 { modified: true, changed: false, accessed: false, created: false }
- }
- }
- /// 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 {
- /// Localisation rules for formatting numbers.
- numeric: locale::Numeric,
- /// The computer's current time zone. This gets used to determine how to
- /// offset files' timestamps.
- tz: Option<TimeZone>,
- /// Mapping cache of user IDs to usernames.
- users: Mutex<UsersCache>,
- }
- impl Environment {
- pub fn lock_users(&self) -> MutexGuard<UsersCache> {
- self.users.lock().unwrap()
- }
- pub fn load_all() -> Self {
- let tz = match determine_time_zone() {
- Ok(t) => Some(t),
- Err(ref e) => {
- println!("Unable to determine time zone: {}", e);
- None
- }
- };
- let numeric = locale::Numeric::load_user_locale()
- .unwrap_or_else(|_| locale::Numeric::english());
- let users = Mutex::new(UsersCache::new());
- Environment { tz, numeric, users }
- }
- }
- fn determine_time_zone() -> TZResult<TimeZone> {
- TimeZone::from_file("/etc/localtime")
- }
- pub struct Table<'a> {
- columns: Vec<Column>,
- colours: &'a Colours,
- env: &'a Environment,
- widths: TableWidths,
- time_format: &'a TimeFormat,
- size_format: SizeFormat,
- git: Option<&'a GitCache>,
- }
- #[derive(Clone)]
- pub struct Row {
- cells: Vec<TextCell>,
- }
- impl<'a, 'f> Table<'a> {
- pub fn new(options: &'a Options, git: Option<&'a GitCache>, colours: &'a Colours) -> Table<'a> {
- let columns = options.columns.collect(git.is_some());
- let widths = TableWidths::zero(columns.len());
- Table {
- colours, widths, columns, git,
- env: &options.env,
- time_format: &options.time_format,
- size_format: options.size_format,
- }
- }
- pub fn widths(&self) -> &TableWidths {
- &self.widths
- }
- pub fn header_row(&self) -> Row {
- let cells = self.columns.iter()
- .map(|c| TextCell::paint_str(self.colours.header, c.header()))
- .collect();
- Row { cells }
- }
- pub fn row_for_file(&self, file: &File, xattrs: bool) -> Row {
- let cells = self.columns.iter()
- .map(|c| self.display(file, c, xattrs))
- .collect();
- Row { cells }
- }
- pub fn add_widths(&mut self, row: &Row) {
- self.widths.add_widths(row)
- }
- fn permissions_plus(&self, file: &File, xattrs: bool) -> f::PermissionsPlus {
- f::PermissionsPlus {
- file_type: file.type_char(),
- permissions: file.permissions(),
- xattrs,
- }
- }
- fn octal_permissions(&self, file: &File) -> f::OctalPermissions {
- f::OctalPermissions {
- permissions: file.permissions(),
- }
- }
- fn display(&self, file: &File, column: &Column, xattrs: bool) -> TextCell {
- use crate::output::table::TimeType::*;
- match *column {
- Column::Permissions => self.permissions_plus(file, xattrs).render(self.colours),
- Column::FileSize => file.size().render(self.colours, self.size_format, &self.env.numeric),
- Column::HardLinks => file.links().render(self.colours, &self.env.numeric),
- Column::Inode => file.inode().render(self.colours.inode),
- Column::Blocks => file.blocks().render(self.colours),
- Column::User => file.user().render(self.colours, &*self.env.lock_users()),
- Column::Group => file.group().render(self.colours, &*self.env.lock_users()),
- Column::GitStatus => self.git_status(file).render(self.colours),
- Column::Octal => self.octal_permissions(file).render(self.colours.octal),
- Column::Timestamp(Modified) => file.modified_time().render(self.colours.date, &self.env.tz, &self.time_format),
- Column::Timestamp(Changed) => file.changed_time() .render(self.colours.date, &self.env.tz, &self.time_format),
- Column::Timestamp(Created) => file.created_time() .render(self.colours.date, &self.env.tz, &self.time_format),
- Column::Timestamp(Accessed) => file.accessed_time().render(self.colours.date, &self.env.tz, &self.time_format),
- }
- }
- fn git_status(&self, file: &File) -> f::Git {
- debug!("Getting Git status for file {:?}", file.path);
- self.git
- .map(|g| g.get(&file.path, file.is_directory()))
- .unwrap_or_default()
- }
- pub fn render(&self, row: Row) -> TextCell {
- let mut cell = TextCell::default();
- for (n, (this_cell, width)) in row.cells.into_iter().zip(self.widths.iter()).enumerate() {
- let padding = width - *this_cell.width;
- match self.columns[n].alignment() {
- Alignment::Left => { cell.append(this_cell); cell.add_spaces(padding); }
- Alignment::Right => { cell.add_spaces(padding); cell.append(this_cell); }
- }
- cell.add_spaces(1);
- }
- cell
- }
- }
- pub struct TableWidths(Vec<usize>);
- impl Deref for TableWidths {
- type Target = [usize];
- fn deref(&self) -> &Self::Target {
- &self.0
- }
- }
- impl TableWidths {
- pub fn zero(count: usize) -> TableWidths {
- TableWidths(vec![ 0; count ])
- }
- pub fn add_widths(&mut self, row: &Row) {
- for (old_width, cell) in self.0.iter_mut().zip(row.cells.iter()) {
- *old_width = max(*old_width, *cell.width);
- }
- }
- pub fn total(&self) -> usize {
- self.0.len() + self.0.iter().sum::<usize>()
- }
- }
|