Skip to content

Commit

Permalink
Merge branch 'time-styles-properly'
Browse files Browse the repository at this point in the history
This merges in the ability to use different time styles, such as full ISO-formatted timestamps instead of just using the default variable style.

Firstly, this moved the Environment from the Table to the Columns, so it 1) would only be instantiated when a table is actually used, and 2) can be affected by command-line options.

Next, it renames Columns to table::Options, in line with what the view optionses were renamed to.

Finally, it adds support for more time styles, deferring timestamp formatting to an enum.

Fixes #133.
  • Loading branch information
ogham committed Jul 6, 2017
2 parents 651d23f + 6afde85 commit 690aa21
Show file tree
Hide file tree
Showing 27 changed files with 516 additions and 309 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,12 @@ These options are available when running with --long (`-l`):
- **-U**, **--created**: use the created timestamp field
- **-@**, **--extended**: list each file's extended attributes and sizes
- **--git**: list each file's Git status, if tracked
- **--time-style**: how to format timestamps

- Valid **--color** options are **always**, **automatic**, and **never**.
- Valid sort fields are **accessed**, **created**, **extension**, **Extension**, **inode**, **modified**, **name**, **Name**, **size**, **type**, and **none**. Fields starting with a capital letter are case-sensitive.
- Valid time fields are **modified**, **accessed**, and **created**.
- Valid time styles are **default**, **iso**, **long-iso**, and **full-iso**.


## Installation
Expand Down
2 changes: 2 additions & 0 deletions Vagrantfile
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,8 @@ Vagrant.configure(2) do |config|
touch -t #{old} -a "#{test_dir}/dates/plum"
touch -t #{med} -a "#{test_dir}/dates/pear"
touch -t #{new} -a "#{test_dir}/dates/peach"
sudo chown #{user}:#{user} -R "#{test_dir}/dates"
EOF


Expand Down
5 changes: 5 additions & 0 deletions contrib/completions.bash
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ _exa()
COMPREPLY=( $( compgen -W 'accessed modified created --' -- $cur ) )
return
;;

--time-style)
COMPREPLY=( $( compgen -W 'default iso long-iso full-iso --' -- $cur ) )
return
;;
esac

case "$cur" in
Expand Down
10 changes: 8 additions & 2 deletions contrib/completions.fish
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,14 @@ complete -c exa -s 't' -l 'time' -x -d "Which timestamp field to list" -a "
created\t'Display created time'
modified\t'Display modified time'
"
complete -c exa -s 'u' -l 'accessed' -d "Use the accessed timestamp field"
complete -c exa -s 'U' -l 'created' -d "Use the created timestamp field"
complete -c exa -s 'u' -l 'accessed' -d "Use the accessed timestamp field"
complete -c exa -s 'U' -l 'created' -d "Use the created timestamp field"
complete -c exa -l 'time-style' -x -d "How to format timestamps" -a "
default\t'Use the default time style'
iso\t'Display brief ISO timestamps'
long-iso\t'Display longer ISO timestaps, up to the minute'
full-iso\t'Display full ISO timestamps, up to the nanosecond'
"

# Optional extras
complete -c exa -s 'g' -l 'git' -d "List each file's Git status, if tracked"
Expand Down
1 change: 1 addition & 0 deletions contrib/completions.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ __exa() {
{-m,--modified}"[Use the modified timestamp field]" \
{-S,--blocks}"[List each file's number of filesystem blocks]" \
{-t,--time}"[Which time field to show]:(time field):(accessed created modified)" \
--time-style"[How to format timestamps]:(time style):(default iso long-iso full-iso)" \
{-u,--accessed}"[Use the accessed timestamp field]" \
{-U,--created}"[Use the created timestamp field]" \
--git"[List each file's Git status, if tracked]" \
Expand Down
5 changes: 5 additions & 0 deletions contrib/man/exa.1
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,11 @@ which timestamp field to list (modified, accessed, created)
.RS
.RE
.TP
.B \-\-time\-style=\f[I]STYLE\f[]
how to format timestamps (default, iso, long-iso, full-iso)
.RS
.RE
.TP
.B \-u, \-\-accessed
use the accessed timestamp field
.RS
Expand Down
6 changes: 5 additions & 1 deletion src/fs/fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,11 @@ pub struct DeviceIDs {


/// One of a file’s timestamps (created, accessed, or modified).
pub struct Time(pub time_t);
#[derive(Copy, Clone)]
pub struct Time {
pub seconds: time_t,
pub nanoseconds: time_t,
}


/// A file’s status in a Git repository. Whether a file is in a repository or
Expand Down
28 changes: 20 additions & 8 deletions src/fs/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,23 +273,35 @@ impl<'dir> File<'dir> {
}
}

/// This file’s last modified timestamp.
pub fn modified_time(&self) -> f::Time {
f::Time(self.metadata.mtime())
f::Time {
seconds: self.metadata.mtime(),
nanoseconds: self.metadata.mtime_nsec()
}
}

/// This file’s created timestamp.
pub fn created_time(&self) -> f::Time {
f::Time(self.metadata.ctime())
f::Time {
seconds: self.metadata.ctime(),
nanoseconds: self.metadata.ctime_nsec()
}
}

/// This file’s last accessed timestamp.
pub fn accessed_time(&self) -> f::Time {
f::Time(self.metadata.mtime())
f::Time {
seconds: self.metadata.atime(),
nanoseconds: self.metadata.atime_nsec()
}
}

/// This file's 'type'.
/// This file’s ‘type.
///
/// This is used in the leftmost column of the permissions column.
/// Although the file type can usually be guessed from the colour of the
/// file, `ls` puts this character there, so people will expect it.
/// This is used a the leftmost character of the permissions column.
/// The file type can usually be guessed from the colour of the file, but
/// ls puts this character there.
pub fn type_char(&self) -> f::Type {
if self.is_file() {
f::Type::File
Expand Down Expand Up @@ -341,7 +353,7 @@ impl<'dir> File<'dir> {
}
}

/// Whether this file's extension is any of the strings that get passed in.
/// Whether this files extension is any of the strings that get passed in.
///
/// This will always return `false` if the file has no extension.
pub fn extension_is_one_of(&self, choices: &[&str]) -> bool {
Expand Down
3 changes: 2 additions & 1 deletion src/options/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ LONG VIEW OPTIONS
-S, --blocks show number of file system blocks
-t, --time FIELD which timestamp field to list (modified, accessed, created)
-u, --accessed use the accessed timestamp field
-U, --created use the created timestamp field"##;
-U, --created use the created timestamp field
--time-style how to format timestamps (default, iso, long-iso, full-iso)"##;

static GIT_HELP: &str = r##" --git list each file's Git status, if tracked"##;
static EXTENDED_HELP: &str = r##" -@, --extended list each file's extended attributes and sizes"##;
Expand Down
33 changes: 17 additions & 16 deletions src/options/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ pub use self::view::{View, Mode};

/// These **options** represent a parsed, error-checked versions of the
/// user’s command-line options.
#[derive(PartialEq, Debug, Clone)]
#[derive(Debug)]
pub struct Options {

/// The action to perform when encountering a directory rather than a
Expand Down Expand Up @@ -77,17 +77,18 @@ impl Options {
opts.optopt ("I", "ignore-glob", "ignore files that match these glob patterns", "GLOB1|GLOB2...");

// Long view options
opts.optflag("b", "binary", "list file sizes with binary prefixes");
opts.optflag("B", "bytes", "list file sizes in bytes, without prefixes");
opts.optflag("g", "group", "list each file's group");
opts.optflag("h", "header", "add a header row to each column");
opts.optflag("H", "links", "list each file's number of hard links");
opts.optflag("i", "inode", "list each file's inode number");
opts.optflag("m", "modified", "use the modified timestamp field");
opts.optflag("S", "blocks", "list each file's number of file system blocks");
opts.optopt ("t", "time", "which timestamp field to show", "WORD");
opts.optflag("u", "accessed", "use the accessed timestamp field");
opts.optflag("U", "created", "use the created timestamp field");
opts.optflag("b", "binary", "list file sizes with binary prefixes");
opts.optflag("B", "bytes", "list file sizes in bytes, without prefixes");
opts.optflag("g", "group", "list each file's group");
opts.optflag("h", "header", "add a header row to each column");
opts.optflag("H", "links", "list each file's number of hard links");
opts.optflag("i", "inode", "list each file's inode number");
opts.optflag("m", "modified", "use the modified timestamp field");
opts.optflag("S", "blocks", "list each file's number of file system blocks");
opts.optopt ("t", "time", "which timestamp field to show", "WORD");
opts.optflag("u", "accessed", "use the accessed timestamp field");
opts.optflag("U", "created", "use the created timestamp field");
opts.optopt ("", "time-style", "how to format timestamp fields", "STYLE");

if cfg!(feature="git") {
opts.optflag("", "git", "list each file's git status");
Expand Down Expand Up @@ -124,8 +125,8 @@ impl Options {
/// results will end up being displayed.
pub fn should_scan_for_git(&self) -> bool {
match self.view.mode {
Mode::Details(details::Options { columns: Some(cols), .. }) |
Mode::GridDetails(_, details::Options { columns: Some(cols), .. }) => cols.should_scan_for_git(),
Mode::Details(details::Options { table: Some(ref table), .. }) |
Mode::GridDetails(_, details::Options { table: Some(ref table), .. }) => table.should_scan_for_git(),
_ => false,
}
}
Expand Down Expand Up @@ -201,13 +202,13 @@ mod test {
#[test]
fn long_across() {
let opts = Options::getopts(&[ "--long", "--across" ]);
assert_eq!(opts, Err(Misfire::Useless("across", true, "long")))
assert_eq!(opts.unwrap_err(), Misfire::Useless("across", true, "long"))
}

#[test]
fn oneline_across() {
let opts = Options::getopts(&[ "--oneline", "--across" ]);
assert_eq!(opts, Err(Misfire::Useless("across", true, "oneline")))
assert_eq!(opts.unwrap_err(), Misfire::Useless("across", true, "oneline"))
}

#[test]
Expand Down
44 changes: 35 additions & 9 deletions src/options/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ use getopts;

use output::Colours;
use output::{grid, details};
use output::column::{Columns, TimeTypes, SizeFormat};
use output::table::{TimeTypes, Environment, SizeFormat, Options as TableOptions};
use output::file_name::Classify;
use output::time::TimeFormat;
use options::Misfire;
use fs::feature::xattr;


/// The **view** contains all information about how to format output.
#[derive(PartialEq, Debug, Clone)]
#[derive(Debug)]
pub struct View {
pub mode: Mode,
pub colours: Colours,
Expand All @@ -31,7 +32,7 @@ impl View {


/// The **mode** is the “type” of output.
#[derive(PartialEq, Debug, Clone)]
#[derive(Debug)]
pub enum Mode {
Grid(grid::Options),
Details(details::Options),
Expand All @@ -54,7 +55,7 @@ impl Mode {
}
else {
Ok(details::Options {
columns: Some(Columns::deduce(matches)?),
table: Some(TableOptions::deduce(matches)?),
header: matches.opt_present("header"),
xattr: xattr::ENABLED && matches.opt_present("extended"),
})
Expand Down Expand Up @@ -94,7 +95,7 @@ impl Mode {
}
else if matches.opt_present("tree") {
let details = details::Options {
columns: None,
table: None,
header: false,
xattr: false,
};
Expand All @@ -117,7 +118,7 @@ impl Mode {

if matches.opt_present("tree") {
let details = details::Options {
columns: None,
table: None,
header: false,
xattr: false,
};
Expand Down Expand Up @@ -194,9 +195,11 @@ impl TerminalWidth {
}


impl Columns {
fn deduce(matches: &getopts::Matches) -> Result<Columns, Misfire> {
Ok(Columns {
impl TableOptions {
fn deduce(matches: &getopts::Matches) -> Result<Self, Misfire> {
Ok(TableOptions {
env: Environment::load_all(),
time_format: TimeFormat::deduce(matches)?,
size_format: SizeFormat::deduce(matches)?,
time_types: TimeTypes::deduce(matches)?,
inode: matches.opt_present("inode"),
Expand Down Expand Up @@ -233,6 +236,29 @@ impl SizeFormat {
}


impl TimeFormat {

/// Determine how time should be formatted in timestamp columns.
fn deduce(matches: &getopts::Matches) -> Result<TimeFormat, Misfire> {
pub use output::time::{DefaultFormat, ISOFormat};
const STYLES: &[&str] = &["default", "long-iso", "full-iso", "iso"];

if let Some(word) = matches.opt_str("time-style") {
match &*word {
"default" => Ok(TimeFormat::DefaultFormat(DefaultFormat::new())),
"iso" => Ok(TimeFormat::ISOFormat(ISOFormat::new())),
"long-iso" => Ok(TimeFormat::LongISO),
"full-iso" => Ok(TimeFormat::FullISO),
otherwise => Err(Misfire::bad_argument("time-style", otherwise, STYLES)),
}
}
else {
Ok(TimeFormat::DefaultFormat(DefaultFormat::new()))
}
}
}


impl TimeTypes {

/// Determine which of a file’s time fields should be displayed for it
Expand Down
Loading

0 comments on commit 690aa21

Please sign in to comment.