mirror of
https://github.com/Llewellynvdm/exa.git
synced 2024-11-10 14:50:57 +00:00
Merge pull request #457 from ariasuni/fix-sorting-by-created-time
Fix sorting by created time
This commit is contained in:
commit
faed8f9b82
@ -62,8 +62,8 @@ These options are available when running with --long (`-l`):
|
||||
- **--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 sort uppercase before lowercase. The modified field has the aliases **date**, **time**, and **newest**, while its reverse has the aliases **age** and **oldest**.
|
||||
- Valid time fields are **modified**, **accessed**, and **created**.
|
||||
- Valid sort fields are **accessed**, **changed**, **created**, **extension**, **Extension**, **inode**, **modified**, **name**, **Name**, **size**, **type**, and **none**. Fields starting with a capital letter sort uppercase before lowercase. The modified field has the aliases **date**, **time**, and **newest**, while its reverse has the aliases **age** and **oldest**.
|
||||
- Valid time fields are **modified**, **changed**, **accessed**, and **created**.
|
||||
- Valid time styles are **default**, **iso**, **long-iso**, and **full-iso**.
|
||||
|
||||
|
||||
|
@ -14,12 +14,12 @@ _exa()
|
||||
;;
|
||||
|
||||
-s|--sort)
|
||||
COMPREPLY=( $( compgen -W 'name filename Name Filename size filesize extension Extension date time modified accessed created type inode oldest newest age none --' -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W 'name filename Name Filename size filesize extension Extension date time modified changed accessed created type inode oldest newest age none --' -- "$cur" ) )
|
||||
return
|
||||
;;
|
||||
|
||||
-t|--time)
|
||||
COMPREPLY=( $( compgen -W 'accessed modified created --' -- $cur ) )
|
||||
COMPREPLY=( $( compgen -W 'modified changed accessed created --' -- $cur ) )
|
||||
return
|
||||
;;
|
||||
|
||||
|
@ -25,6 +25,7 @@ complete -c exa -s 'r' -l 'reverse' -d "Reverse the sort order"
|
||||
complete -c exa -s 's' -l 'sort' -x -d "Which field to sort by" -a "
|
||||
accessed\t'Sort by file accessed time'
|
||||
age\t'Sort by file modified time (newest first)'
|
||||
changed\t'Sort by changed time'
|
||||
created\t'Sort by file modified time'
|
||||
date\t'Sort by file modified time'
|
||||
ext\t'Sort by file extension'
|
||||
@ -54,13 +55,15 @@ complete -c exa -s 'g' -l 'group' -d "List each file's group"
|
||||
complete -c exa -s 'h' -l 'header' -d "Add a header row to each column"
|
||||
complete -c exa -s 'h' -l 'links' -d "List each file's number of hard links"
|
||||
complete -c exa -s 'g' -l 'group' -d "List each file's inode number"
|
||||
complete -c exa -s 'm' -l 'modified' -d "Use the modified timestamp field"
|
||||
complete -c exa -s 'S' -l 'blocks' -d "List each file's number of filesystem blocks"
|
||||
complete -c exa -s 't' -l 'time' -x -d "Which timestamp field to list" -a "
|
||||
modified\t'Display modified time'
|
||||
changed\t'Display changed time'
|
||||
accessed\t'Display accessed time'
|
||||
created\t'Display created time'
|
||||
modified\t'Display modified time'
|
||||
"
|
||||
complete -c exa -s 'm' -l 'modified' -d "Use the modified timestamp field"
|
||||
complete -c exa -l 'changed' -d "Use the changed 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 "
|
||||
|
@ -27,7 +27,7 @@ __exa() {
|
||||
{-d,--list-dirs}"[List directories like regular files]" \
|
||||
{-L,--level}"+[Limit the depth of recursion]" \
|
||||
{-r,--reverse}"[Reverse the sort order]" \
|
||||
{-s,--sort}="[Which field to sort by]:(sort field):(accessed age created date extension Extension filename Filename inode modified oldest name Name newest none size time type)" \
|
||||
{-s,--sort}="[Which field to sort by]:(sort field):(accessed age changed created date extension Extension filename Filename inode modified oldest name Name newest none size time type)" \
|
||||
{-I,--ignore-glob}"[Ignore files that match these glob patterns]" \
|
||||
{-b,--binary}"[List file sizes with binary prefixes]" \
|
||||
{-B,--bytes}"[List file sizes in bytes, without any prefixes]" \
|
||||
@ -37,7 +37,7 @@ __exa() {
|
||||
{-i,--inode}"[List each file's inode number]" \
|
||||
{-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)" \
|
||||
{-t,--time}="[Which time field to show]:(time field):(accessed changed 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]" \
|
||||
|
@ -1,5 +1,5 @@
|
||||
.hy
|
||||
.TH "exa" "1" "2017\-07\-07" "exa 0.7.0" ""
|
||||
.TH "exa" "1" "2018\-12\-17" "exa 0.9.0" ""
|
||||
.SH NAME
|
||||
.PP
|
||||
exa \- a modern replacement for ls
|
||||
@ -86,7 +86,7 @@ reverse the sort order
|
||||
.TP
|
||||
.B \-s, \-\-sort=\f[I]SORT_FIELD\f[]
|
||||
which field to sort by.
|
||||
Valid fields are name, Name, extension, Extension, size, modified, accessed, created, inode, type, and none.
|
||||
Valid fields are name, Name, extension, Extension, size, modified, changed, accessed, created, inode, type, and none.
|
||||
The modified field has the aliases date, time, and newest, and its reverse order has the aliases age and oldest.
|
||||
Fields starting with a capital letter will sort uppercase before lowercase: 'A' then 'B' then 'a' then 'b'.
|
||||
Fields starting with a lowercase letter will mix them: 'A' then 'a' then 'B' then 'b'.
|
||||
@ -158,7 +158,7 @@ list each file\[aq]s number of file system blocks
|
||||
.RE
|
||||
.TP
|
||||
.B \-t, \-\-time=\f[I]WORD\f[]
|
||||
which timestamp field to list (modified, accessed, created)
|
||||
which timestamp field to list (modified, changed, accessed, created)
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
|
@ -1,13 +1,15 @@
|
||||
//! Files, and methods and fields to access their metadata.
|
||||
|
||||
use std::fs;
|
||||
use std::fs::{self, metadata};
|
||||
use std::io::Error as IOError;
|
||||
use std::io::Result as IOResult;
|
||||
use std::os::unix::fs::{MetadataExt, PermissionsExt, FileTypeExt};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::{UNIX_EPOCH, Duration};
|
||||
|
||||
use fs::dir::Dir;
|
||||
use fs::fields as f;
|
||||
use options::Misfire;
|
||||
|
||||
|
||||
/// A **File** is a wrapper around one of Rust's Path objects, along with
|
||||
@ -325,27 +327,23 @@ impl<'dir> File<'dir> {
|
||||
}
|
||||
|
||||
/// This file’s last modified timestamp.
|
||||
pub fn modified_time(&self) -> f::Time {
|
||||
f::Time {
|
||||
seconds: self.metadata.mtime(),
|
||||
nanoseconds: self.metadata.mtime_nsec()
|
||||
}
|
||||
pub fn modified_time(&self) -> Duration {
|
||||
self.metadata.modified().unwrap().duration_since(UNIX_EPOCH).unwrap()
|
||||
}
|
||||
|
||||
/// This file’s created timestamp.
|
||||
pub fn created_time(&self) -> f::Time {
|
||||
f::Time {
|
||||
seconds: self.metadata.ctime(),
|
||||
nanoseconds: self.metadata.ctime_nsec()
|
||||
}
|
||||
/// This file’s last changed timestamp.
|
||||
pub fn changed_time(&self) -> Duration {
|
||||
Duration::new(self.metadata.ctime() as u64, self.metadata.ctime_nsec() as u32)
|
||||
}
|
||||
|
||||
/// This file’s last accessed timestamp.
|
||||
pub fn accessed_time(&self) -> f::Time {
|
||||
f::Time {
|
||||
seconds: self.metadata.atime(),
|
||||
nanoseconds: self.metadata.atime_nsec()
|
||||
pub fn accessed_time(&self) -> Duration {
|
||||
self.metadata.accessed().unwrap().duration_since(UNIX_EPOCH).unwrap()
|
||||
}
|
||||
|
||||
/// This file’s created timestamp.
|
||||
pub fn created_time(&self) -> Duration {
|
||||
self.metadata.created().unwrap().duration_since(UNIX_EPOCH).unwrap()
|
||||
}
|
||||
|
||||
/// This file’s ‘type’.
|
||||
@ -462,6 +460,41 @@ impl<'dir> FileTarget<'dir> {
|
||||
}
|
||||
|
||||
|
||||
pub enum PlatformMetadata {
|
||||
ModifiedTime,
|
||||
ChangedTime,
|
||||
AccessedTime,
|
||||
CreatedTime,
|
||||
}
|
||||
|
||||
impl PlatformMetadata {
|
||||
pub fn check_supported(&self) -> Result<(), Misfire> {
|
||||
use std::env::temp_dir;
|
||||
let result = match self {
|
||||
// Call the functions that return a Result to see if it works
|
||||
PlatformMetadata::AccessedTime => metadata(temp_dir()).unwrap().accessed(),
|
||||
PlatformMetadata::ModifiedTime => metadata(temp_dir()).unwrap().modified(),
|
||||
PlatformMetadata::CreatedTime => metadata(temp_dir()).unwrap().created(),
|
||||
// We use the Unix API so we know it’s not available elsewhere
|
||||
PlatformMetadata::ChangedTime => {
|
||||
if cfg!(target_family = "unix") {
|
||||
return Ok(())
|
||||
} else {
|
||||
return Err(Misfire::Unsupported(
|
||||
// for consistency, this error message similar to the one Rust
|
||||
// use when created time is not available
|
||||
"status modified time is not available on this platform currently".to_string()));
|
||||
}
|
||||
},
|
||||
};
|
||||
match result {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(Misfire::Unsupported(err.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// More readable aliases for the permission bits exposed by libc.
|
||||
#[allow(trivial_numeric_casts)]
|
||||
mod modes {
|
||||
|
@ -173,13 +173,16 @@ pub enum SortField {
|
||||
/// http://unix.stackexchange.com/a/8842
|
||||
AccessedDate,
|
||||
|
||||
/// The time the file was changed or created (the “ctime”).
|
||||
/// The time the file was changed (the “ctime”).
|
||||
///
|
||||
/// Contrary to the name, this field is used to mark the time when a
|
||||
/// file’s metadata changed -- its permissions, owners, or link count.
|
||||
/// This field is used to mark the time when a file’s metadata
|
||||
/// changed -- its permissions, owners, or link count.
|
||||
///
|
||||
/// In original Unix, this was, however, meant as creation time.
|
||||
/// https://www.bell-labs.com/usr/dmr/www/cacm.html
|
||||
ChangedDate,
|
||||
|
||||
/// The time the file was created (the "btime" or "birthtime").
|
||||
CreatedDate,
|
||||
|
||||
/// The type of the file: directories, links, pipes, regular, files, etc.
|
||||
@ -247,6 +250,7 @@ impl SortField {
|
||||
SortField::FileInode => a.metadata.ino().cmp(&b.metadata.ino()),
|
||||
SortField::ModifiedDate => a.modified_time().cmp(&b.modified_time()),
|
||||
SortField::AccessedDate => a.accessed_time().cmp(&b.accessed_time()),
|
||||
SortField::ChangedDate => a.changed_time().cmp(&b.changed_time()),
|
||||
SortField::CreatedDate => a.created_time().cmp(&b.created_time()),
|
||||
SortField::ModifiedAge => b.modified_time().cmp(&a.modified_time()), // flip b and a
|
||||
|
||||
|
@ -2,7 +2,7 @@ mod dir;
|
||||
pub use self::dir::{Dir, DotFilter};
|
||||
|
||||
mod file;
|
||||
pub use self::file::{File, FileTarget};
|
||||
pub use self::file::{File, FileTarget, PlatformMetadata};
|
||||
|
||||
pub mod feature;
|
||||
pub mod fields;
|
||||
|
@ -1,6 +1,6 @@
|
||||
//! Parsing the options for `FileFilter`.
|
||||
|
||||
use fs::DotFilter;
|
||||
use fs::{DotFilter, PlatformMetadata};
|
||||
use fs::filter::{FileFilter, SortField, SortCase, IgnorePatterns, GitIgnore};
|
||||
|
||||
use options::{flags, Misfire};
|
||||
@ -35,62 +35,59 @@ impl SortField {
|
||||
None => return Ok(SortField::default()),
|
||||
};
|
||||
|
||||
// The field is an OsStr, so can’t be matched.
|
||||
if word == "name" || word == "filename" {
|
||||
Ok(SortField::Name(SortCase::AaBbCc))
|
||||
}
|
||||
else if word == "Name" || word == "Filename" {
|
||||
Ok(SortField::Name(SortCase::ABCabc))
|
||||
}
|
||||
else if word == ".name" || word == ".filename" {
|
||||
Ok(SortField::NameMixHidden(SortCase::AaBbCc))
|
||||
}
|
||||
else if word == ".Name" || word == ".Filename" {
|
||||
Ok(SortField::NameMixHidden(SortCase::ABCabc))
|
||||
}
|
||||
else if word == "size" || word == "filesize" {
|
||||
Ok(SortField::Size)
|
||||
}
|
||||
else if word == "ext" || word == "extension" {
|
||||
Ok(SortField::Extension(SortCase::AaBbCc))
|
||||
}
|
||||
else if word == "Ext" || word == "Extension" {
|
||||
Ok(SortField::Extension(SortCase::ABCabc))
|
||||
}
|
||||
else if word == "date" || word == "time" || word == "mod" || word == "modified" || word == "new" || word == "newest" {
|
||||
// Get String because we can’t match an OsStr
|
||||
let word = match word.to_str() {
|
||||
Some(ref w) => *w,
|
||||
None => return Err(Misfire::BadArgument(&flags::SORT, word.into()))
|
||||
};
|
||||
|
||||
let field = match word {
|
||||
"name" | "filename" => SortField::Name(SortCase::AaBbCc),
|
||||
"Name" | "Filename" => SortField::Name(SortCase::ABCabc),
|
||||
".name" | ".filename" => SortField::NameMixHidden(SortCase::AaBbCc),
|
||||
".Name" | ".Filename" => SortField::NameMixHidden(SortCase::ABCabc),
|
||||
"size" | "filesize" => SortField::Size,
|
||||
"ext" | "extension" => SortField::Extension(SortCase::AaBbCc),
|
||||
"Ext" | "Extension" => SortField::Extension(SortCase::ABCabc),
|
||||
// “new” sorts oldest at the top and newest at the bottom; “old”
|
||||
// sorts newest at the top and oldest at the bottom. I think this
|
||||
// is the right way round to do this: “size” puts the smallest at
|
||||
// the top and the largest at the bottom, doesn’t it?
|
||||
Ok(SortField::ModifiedDate)
|
||||
}
|
||||
else if word == "age" || word == "old" || word == "oldest" {
|
||||
"date" | "time" | "mod" | "modified" | "new" | "newest" => SortField::ModifiedDate,
|
||||
// Similarly, “age” means that files with the least age (the
|
||||
// newest files) get sorted at the top, and files with the most
|
||||
// age (the oldest) at the bottom.
|
||||
Ok(SortField::ModifiedAge)
|
||||
"age" | "old" | "oldest" => SortField::ModifiedAge,
|
||||
"ch" | "changed" => SortField::ChangedDate,
|
||||
"acc" | "accessed" => SortField::AccessedDate,
|
||||
"cr" | "created" => SortField::CreatedDate,
|
||||
"inode" => SortField::FileInode,
|
||||
"type" => SortField::FileType,
|
||||
"none" => SortField::Unsorted,
|
||||
_ => return Err(Misfire::BadArgument(&flags::SORT, word.into()))
|
||||
};
|
||||
|
||||
match SortField::to_platform_metadata(field) {
|
||||
Some(m) => match m.check_supported() {
|
||||
Ok(_) => Ok(field),
|
||||
Err(misfire) => Err(misfire),
|
||||
},
|
||||
None => Ok(field),
|
||||
}
|
||||
else if word == "acc" || word == "accessed" {
|
||||
Ok(SortField::AccessedDate)
|
||||
}
|
||||
else if word == "cr" || word == "created" {
|
||||
Ok(SortField::CreatedDate)
|
||||
}
|
||||
else if word == "inode" {
|
||||
Ok(SortField::FileInode)
|
||||
}
|
||||
else if word == "type" {
|
||||
Ok(SortField::FileType)
|
||||
}
|
||||
else if word == "none" {
|
||||
Ok(SortField::Unsorted)
|
||||
}
|
||||
else {
|
||||
Err(Misfire::BadArgument(&flags::SORT, word.into()))
|
||||
|
||||
fn to_platform_metadata(field: Self) -> Option<PlatformMetadata> {
|
||||
match field {
|
||||
SortField::ModifiedDate => Some(PlatformMetadata::ModifiedTime),
|
||||
SortField::ChangedDate => Some(PlatformMetadata::ChangedTime),
|
||||
SortField::AccessedDate => Some(PlatformMetadata::AccessedTime),
|
||||
SortField::CreatedDate => Some(PlatformMetadata::CreatedTime),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// I’ve gone back and forth between whether to sort case-sensitively or
|
||||
// insensitively by default. The default string sort in most programming
|
||||
// languages takes each character’s ASCII value into account, sorting
|
||||
@ -227,7 +224,7 @@ mod test {
|
||||
test!(empty: SortField <- []; Both => Ok(SortField::default()));
|
||||
|
||||
// Sort field arguments
|
||||
test!(one_arg: SortField <- ["--sort=cr"]; Both => Ok(SortField::CreatedDate));
|
||||
test!(one_arg: SortField <- ["--sort=mod"]; Both => Ok(SortField::ModifiedDate));
|
||||
test!(one_long: SortField <- ["--sort=size"]; Both => Ok(SortField::Size));
|
||||
test!(one_short: SortField <- ["-saccessed"]; Both => Ok(SortField::AccessedDate));
|
||||
test!(lowercase: SortField <- ["--sort", "name"]; Both => Ok(SortField::Name(SortCase::AaBbCc)));
|
||||
|
@ -32,7 +32,7 @@ pub static GIT_IGNORE: Arg = Arg { short: None, long: "git-ignore", t
|
||||
pub static DIRS_FIRST: Arg = Arg { short: None, long: "group-directories-first", takes_value: TakesValue::Forbidden };
|
||||
pub static ONLY_DIRS: Arg = Arg { short: Some(b'D'), long: "only-dirs", takes_value: TakesValue::Forbidden };
|
||||
const SORTS: Values = &[ "name", "Name", "size", "extension",
|
||||
"Extension", "modified", "accessed",
|
||||
"Extension", "modified", "changed", "accessed",
|
||||
"created", "inode", "type", "none" ];
|
||||
|
||||
// display options
|
||||
@ -43,12 +43,13 @@ pub static HEADER: Arg = Arg { short: Some(b'h'), long: "header", takes_
|
||||
pub static INODE: Arg = Arg { short: Some(b'i'), long: "inode", takes_value: TakesValue::Forbidden };
|
||||
pub static LINKS: Arg = Arg { short: Some(b'H'), long: "links", takes_value: TakesValue::Forbidden };
|
||||
pub static MODIFIED: Arg = Arg { short: Some(b'm'), long: "modified", takes_value: TakesValue::Forbidden };
|
||||
pub static CHANGED: Arg = Arg { short: None, long: "changed", takes_value: TakesValue::Forbidden };
|
||||
pub static BLOCKS: Arg = Arg { short: Some(b'S'), long: "blocks", takes_value: TakesValue::Forbidden };
|
||||
pub static TIME: Arg = Arg { short: Some(b't'), long: "time", takes_value: TakesValue::Necessary(Some(TIMES)) };
|
||||
pub static ACCESSED: Arg = Arg { short: Some(b'u'), long: "accessed", takes_value: TakesValue::Forbidden };
|
||||
pub static CREATED: Arg = Arg { short: Some(b'U'), long: "created", takes_value: TakesValue::Forbidden };
|
||||
pub static TIME_STYLE: Arg = Arg { short: None, long: "time-style", takes_value: TakesValue::Necessary(Some(TIME_STYLES)) };
|
||||
const TIMES: Values = &["modified", "accessed", "created"];
|
||||
const TIMES: Values = &["modified", "changed", "accessed", "created"];
|
||||
const TIME_STYLES: Values = &["default", "long-iso", "full-iso", "iso"];
|
||||
|
||||
// optional feature options
|
||||
@ -65,9 +66,8 @@ pub static ALL_ARGS: Args = Args(&[
|
||||
&ALL, &LIST_DIRS, &LEVEL, &REVERSE, &SORT, &DIRS_FIRST,
|
||||
&IGNORE_GLOB, &GIT_IGNORE, &ONLY_DIRS,
|
||||
|
||||
&BINARY, &BYTES, &GROUP, &HEADER, &INODE, &LINKS, &MODIFIED, &BLOCKS,
|
||||
&TIME, &ACCESSED, &CREATED, &TIME_STYLE,
|
||||
&BINARY, &BYTES, &GROUP, &HEADER, &INODE, &LINKS, &MODIFIED, &CHANGED,
|
||||
&BLOCKS, &TIME, &ACCESSED, &CREATED, &TIME_STYLE,
|
||||
|
||||
&GIT, &EXTENDED,
|
||||
]);
|
||||
|
||||
|
@ -19,6 +19,9 @@ pub enum Misfire {
|
||||
/// The user supplied an illegal choice to an Argument.
|
||||
BadArgument(&'static Arg, OsString),
|
||||
|
||||
/// The user supplied a set of options
|
||||
Unsupported(String),
|
||||
|
||||
/// The user asked for help. This isn’t strictly an error, which is why
|
||||
/// this enum isn’t named Error!
|
||||
Help(HelpString),
|
||||
@ -83,6 +86,7 @@ impl fmt::Display for Misfire {
|
||||
}
|
||||
},
|
||||
InvalidOptions(ref e) => write!(f, "{}", e),
|
||||
Unsupported(ref e) => write!(f, "{}", e),
|
||||
Help(ref text) => write!(f, "{}", text),
|
||||
Version(ref version) => write!(f, "{}", version),
|
||||
Conflict(ref a, ref b) => write!(f, "Option {} conflicts with option {}", a, b),
|
||||
|
@ -6,6 +6,7 @@ use output::time::TimeFormat;
|
||||
use options::{flags, Misfire, Vars};
|
||||
use options::parser::MatchedFlags;
|
||||
|
||||
use fs::PlatformMetadata;
|
||||
use fs::feature::xattr;
|
||||
|
||||
|
||||
@ -296,39 +297,59 @@ impl TimeTypes {
|
||||
fn deduce(matches: &MatchedFlags) -> Result<TimeTypes, Misfire> {
|
||||
let possible_word = matches.get(&flags::TIME)?;
|
||||
let modified = matches.has(&flags::MODIFIED)?;
|
||||
let created = matches.has(&flags::CREATED)?;
|
||||
let changed = matches.has(&flags::CHANGED)?;
|
||||
let accessed = matches.has(&flags::ACCESSED)?;
|
||||
let created = matches.has(&flags::CREATED)?;
|
||||
|
||||
if let Some(word) = possible_word {
|
||||
let time_types = if let Some(word) = possible_word {
|
||||
if modified {
|
||||
Err(Misfire::Useless(&flags::MODIFIED, true, &flags::TIME))
|
||||
return Err(Misfire::Useless(&flags::MODIFIED, true, &flags::TIME));
|
||||
}
|
||||
else if created {
|
||||
Err(Misfire::Useless(&flags::CREATED, true, &flags::TIME))
|
||||
else if changed {
|
||||
return Err(Misfire::Useless(&flags::CHANGED, true, &flags::TIME));
|
||||
}
|
||||
else if accessed {
|
||||
Err(Misfire::Useless(&flags::ACCESSED, true, &flags::TIME))
|
||||
return Err(Misfire::Useless(&flags::ACCESSED, true, &flags::TIME));
|
||||
}
|
||||
else if created {
|
||||
return Err(Misfire::Useless(&flags::CREATED, true, &flags::TIME));
|
||||
}
|
||||
else if word == "mod" || word == "modified" {
|
||||
Ok(TimeTypes { accessed: false, modified: true, created: false })
|
||||
TimeTypes { modified: true, changed: false, accessed: false, created: false }
|
||||
}
|
||||
else if word == "ch" || word == "changed" {
|
||||
TimeTypes { modified: false, changed: true, accessed: false, created: false }
|
||||
}
|
||||
else if word == "acc" || word == "accessed" {
|
||||
Ok(TimeTypes { accessed: true, modified: false, created: false })
|
||||
TimeTypes { modified: false, changed: false, accessed: true, created: false }
|
||||
}
|
||||
else if word == "cr" || word == "created" {
|
||||
Ok(TimeTypes { accessed: false, modified: false, created: true })
|
||||
TimeTypes { modified: false, changed: false, accessed: false, created: true }
|
||||
}
|
||||
else {
|
||||
Err(Misfire::BadArgument(&flags::TIME, word.into()))
|
||||
return Err(Misfire::BadArgument(&flags::TIME, word.into()));
|
||||
}
|
||||
}
|
||||
else if modified || created || accessed {
|
||||
Ok(TimeTypes { accessed, modified, created })
|
||||
else if modified || changed || accessed || created {
|
||||
TimeTypes { modified, changed, accessed, created }
|
||||
}
|
||||
else {
|
||||
Ok(TimeTypes::default())
|
||||
TimeTypes::default()
|
||||
};
|
||||
|
||||
let mut fields = vec![];
|
||||
if time_types.modified { fields.push(PlatformMetadata::ModifiedTime); }
|
||||
if time_types.changed { fields.push(PlatformMetadata::ChangedTime); }
|
||||
if time_types.accessed { fields.push(PlatformMetadata::AccessedTime); }
|
||||
if time_types.created { fields.push(PlatformMetadata::CreatedTime); }
|
||||
|
||||
for field in fields {
|
||||
if let Err(misfire) = field.check_supported() {
|
||||
return Err(misfire);
|
||||
}
|
||||
}
|
||||
Ok(time_types)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -358,7 +379,8 @@ mod test {
|
||||
use options::test::Strictnesses::*;
|
||||
|
||||
static TEST_ARGS: &[&Arg] = &[ &flags::BINARY, &flags::BYTES, &flags::TIME_STYLE,
|
||||
&flags::TIME, &flags::MODIFIED, &flags::CREATED, &flags::ACCESSED,
|
||||
&flags::TIME, &flags::MODIFIED, &flags::CHANGED,
|
||||
&flags::CREATED, &flags::ACCESSED,
|
||||
&flags::HEADER, &flags::GROUP, &flags::INODE, &flags::GIT,
|
||||
&flags::LINKS, &flags::BLOCKS, &flags::LONG, &flags::LEVEL,
|
||||
&flags::GRID, &flags::ACROSS, &flags::ONE_LINE ];
|
||||
@ -493,32 +515,47 @@ mod test {
|
||||
test!(empty: TimeTypes <- []; Both => Ok(TimeTypes::default()));
|
||||
|
||||
// Modified
|
||||
test!(modified: TimeTypes <- ["--modified"]; Both => Ok(TimeTypes { accessed: false, modified: true, created: false }));
|
||||
test!(m: TimeTypes <- ["-m"]; Both => Ok(TimeTypes { accessed: false, modified: true, created: false }));
|
||||
test!(time_mod: TimeTypes <- ["--time=modified"]; Both => Ok(TimeTypes { accessed: false, modified: true, created: false }));
|
||||
test!(time_m: TimeTypes <- ["-tmod"]; Both => Ok(TimeTypes { accessed: false, modified: true, created: false }));
|
||||
test!(modified: TimeTypes <- ["--modified"]; Both => Ok(TimeTypes { modified: true, changed: false, accessed: false, created: false }));
|
||||
test!(m: TimeTypes <- ["-m"]; Both => Ok(TimeTypes { modified: true, changed: false, accessed: false, created: false }));
|
||||
test!(time_mod: TimeTypes <- ["--time=modified"]; Both => Ok(TimeTypes { modified: true, changed: false, accessed: false, created: false }));
|
||||
test!(time_m: TimeTypes <- ["-tmod"]; Both => Ok(TimeTypes { modified: true, changed: false, accessed: false, created: false }));
|
||||
|
||||
// Changed
|
||||
#[cfg(target_family = "unix")]
|
||||
test!(changed: TimeTypes <- ["--changed"]; Both => Ok(TimeTypes { modified: false, changed: true, accessed: false, created: false }));
|
||||
#[cfg(target_family = "unix")]
|
||||
test!(time_ch: TimeTypes <- ["--time=changed"]; Both => Ok(TimeTypes { modified: false, changed: true, accessed: false, created: false }));
|
||||
#[cfg(target_family = "unix")]
|
||||
test!(time_c: TimeTypes <- ["-t", "ch"]; Both => Ok(TimeTypes { modified: false, changed: true, accessed: false, created: false }));
|
||||
|
||||
// Accessed
|
||||
test!(acc: TimeTypes <- ["--accessed"]; Both => Ok(TimeTypes { accessed: true, modified: false, created: false }));
|
||||
test!(a: TimeTypes <- ["-u"]; Both => Ok(TimeTypes { accessed: true, modified: false, created: false }));
|
||||
test!(time_acc: TimeTypes <- ["--time", "accessed"]; Both => Ok(TimeTypes { accessed: true, modified: false, created: false }));
|
||||
test!(time_a: TimeTypes <- ["-t", "acc"]; Both => Ok(TimeTypes { accessed: true, modified: false, created: false }));
|
||||
test!(acc: TimeTypes <- ["--accessed"]; Both => Ok(TimeTypes { modified: false, changed: false, accessed: true, created: false }));
|
||||
test!(a: TimeTypes <- ["-u"]; Both => Ok(TimeTypes { modified: false, changed: false, accessed: true, created: false }));
|
||||
test!(time_acc: TimeTypes <- ["--time", "accessed"]; Both => Ok(TimeTypes { modified: false, changed: false, accessed: true, created: false }));
|
||||
test!(time_a: TimeTypes <- ["-t", "acc"]; Both => Ok(TimeTypes { modified: false, changed: false, accessed: true, created: false }));
|
||||
|
||||
// Created
|
||||
test!(cr: TimeTypes <- ["--created"]; Both => Ok(TimeTypes { accessed: false, modified: false, created: true }));
|
||||
test!(c: TimeTypes <- ["-U"]; Both => Ok(TimeTypes { accessed: false, modified: false, created: true }));
|
||||
test!(time_cr: TimeTypes <- ["--time=created"]; Both => Ok(TimeTypes { accessed: false, modified: false, created: true }));
|
||||
test!(time_c: TimeTypes <- ["-tcr"]; Both => Ok(TimeTypes { accessed: false, modified: false, created: true }));
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
test!(cr: TimeTypes <- ["--created"]; Both => Ok(TimeTypes { modified: false, changed: false, accessed: false, created: true }));
|
||||
#[cfg(target_os = "linux")]
|
||||
test!(cr: TimeTypes <- ["--created"]; Both => err Misfire::Unsupported("creation time is not available on this platform currently".to_string()));
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
test!(c: TimeTypes <- ["-U"]; Both => Ok(TimeTypes { modified: false, changed: false, accessed: false, created: true }));
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
test!(time_cr: TimeTypes <- ["--time=created"]; Both => Ok(TimeTypes { modified: false, changed: false, accessed: false, created: true }));
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
test!(time_c: TimeTypes <- ["-tcr"]; Both => Ok(TimeTypes { modified: false, changed: false, accessed: false, created: true }));
|
||||
|
||||
// Multiples
|
||||
test!(time_uu: TimeTypes <- ["-uU"]; Both => Ok(TimeTypes { accessed: true, modified: false, created: true }));
|
||||
test!(time_uu: TimeTypes <- ["-u", "--modified"]; Both => Ok(TimeTypes { modified: true, changed: false, accessed: true, created: false }));
|
||||
|
||||
|
||||
// Errors
|
||||
test!(time_tea: TimeTypes <- ["--time=tea"]; Both => err Misfire::BadArgument(&flags::TIME, OsString::from("tea")));
|
||||
test!(time_ea: TimeTypes <- ["-tea"]; Both => err Misfire::BadArgument(&flags::TIME, OsString::from("ea")));
|
||||
|
||||
// Overriding
|
||||
test!(overridden: TimeTypes <- ["-tcr", "-tmod"]; Last => Ok(TimeTypes { accessed: false, modified: true, created: false }));
|
||||
test!(overridden: TimeTypes <- ["-tcr", "-tmod"]; Last => Ok(TimeTypes { modified: true, changed: false, accessed: false, created: false }));
|
||||
test!(overridden_2: TimeTypes <- ["-tcr", "-tmod"]; Complain => err Misfire::Duplicate(Flag::Short(b't'), Flag::Short(b't')));
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,7 @@ mod size;
|
||||
pub use self::size::Colours as SizeColours;
|
||||
|
||||
mod times;
|
||||
pub use self::times::Render as TimeRender;
|
||||
// times does too
|
||||
|
||||
mod users;
|
||||
|
@ -1,13 +1,18 @@
|
||||
use datetime::TimeZone;
|
||||
use ansi_term::Style;
|
||||
|
||||
use fs::fields as f;
|
||||
use output::cell::TextCell;
|
||||
use output::time::TimeFormat;
|
||||
|
||||
|
||||
impl f::Time {
|
||||
pub fn render(self, style: Style,
|
||||
pub trait Render {
|
||||
fn render(self, style: Style,
|
||||
tz: &Option<TimeZone>,
|
||||
format: &TimeFormat) -> TextCell;
|
||||
}
|
||||
|
||||
impl Render for std::time::Duration {
|
||||
fn render(self, style: Style,
|
||||
tz: &Option<TimeZone>,
|
||||
format: &TimeFormat) -> TextCell {
|
||||
|
||||
|
@ -12,6 +12,7 @@ use users::UsersCache;
|
||||
|
||||
use style::Colours;
|
||||
use output::cell::TextCell;
|
||||
use output::render::TimeRender;
|
||||
use output::time::TimeFormat;
|
||||
use fs::{File, fields as f};
|
||||
use fs::feature::git::GitCache;
|
||||
@ -78,6 +79,10 @@ impl Columns {
|
||||
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));
|
||||
}
|
||||
@ -175,14 +180,16 @@ impl Default for SizeFormat {
|
||||
/// 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 modified time (`st_mtime`).
|
||||
Modified,
|
||||
|
||||
/// The file’s creation time (`st_ctime`).
|
||||
/// The file’s creation time (`btime` or `birthtime`).
|
||||
Created,
|
||||
}
|
||||
|
||||
@ -191,8 +198,9 @@ 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::Changed => "Date Changed",
|
||||
TimeType::Accessed => "Date Accessed",
|
||||
TimeType::Created => "Date Created",
|
||||
}
|
||||
}
|
||||
@ -206,8 +214,9 @@ impl TimeType {
|
||||
/// the time columns entirely (yet).
|
||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||
pub struct TimeTypes {
|
||||
pub accessed: bool,
|
||||
pub modified: bool,
|
||||
pub changed: bool,
|
||||
pub accessed: bool,
|
||||
pub created: bool,
|
||||
}
|
||||
|
||||
@ -216,7 +225,7 @@ 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 }
|
||||
TimeTypes { modified: true, changed: false, accessed: false, created: false }
|
||||
}
|
||||
}
|
||||
|
||||
@ -342,6 +351,7 @@ impl<'a, 'f> Table<'a> {
|
||||
Column::GitStatus => self.git_status(file).render(self.colours),
|
||||
|
||||
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),
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
//! Timestamp formatting.
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use datetime::{LocalDateTime, TimeZone, DatePiece, TimePiece};
|
||||
use datetime::fmt::DateFormat;
|
||||
use locale;
|
||||
use std::cmp;
|
||||
|
||||
use fs::fields::Time;
|
||||
|
||||
|
||||
/// Every timestamp in exa needs to be rendered by a **time format**.
|
||||
/// Formatting times is tricky, because how a timestamp is rendered can
|
||||
@ -51,7 +51,7 @@ pub enum TimeFormat {
|
||||
// timestamps are separate types.
|
||||
|
||||
impl TimeFormat {
|
||||
pub fn format_local(&self, time: Time) -> String {
|
||||
pub fn format_local(&self, time: Duration) -> String {
|
||||
match *self {
|
||||
TimeFormat::DefaultFormat(ref fmt) => fmt.format_local(time),
|
||||
TimeFormat::ISOFormat(ref iso) => iso.format_local(time),
|
||||
@ -60,7 +60,7 @@ impl TimeFormat {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_zoned(&self, time: Time, zone: &TimeZone) -> String {
|
||||
pub fn format_zoned(&self, time: Duration, zone: &TimeZone) -> String {
|
||||
match *self {
|
||||
TimeFormat::DefaultFormat(ref fmt) => fmt.format_zoned(time, zone),
|
||||
TimeFormat::ISOFormat(ref iso) => iso.format_zoned(time, zone),
|
||||
@ -128,8 +128,8 @@ impl DefaultFormat {
|
||||
}
|
||||
|
||||
#[allow(trivial_numeric_casts)]
|
||||
fn format_local(&self, time: Time) -> String {
|
||||
let date = LocalDateTime::at(time.seconds as i64);
|
||||
fn format_local(&self, time: Duration) -> String {
|
||||
let date = LocalDateTime::at(time.as_secs() as i64);
|
||||
|
||||
if self.is_recent(date) {
|
||||
self.date_and_time.format(&date, &self.locale)
|
||||
@ -140,8 +140,8 @@ impl DefaultFormat {
|
||||
}
|
||||
|
||||
#[allow(trivial_numeric_casts)]
|
||||
fn format_zoned(&self, time: Time, zone: &TimeZone) -> String {
|
||||
let date = zone.to_zoned(LocalDateTime::at(time.seconds as i64));
|
||||
fn format_zoned(&self, time: Duration, zone: &TimeZone) -> String {
|
||||
let date = zone.to_zoned(LocalDateTime::at(time.as_secs() as i64));
|
||||
|
||||
if self.is_recent(date) {
|
||||
self.date_and_time.format(&date, &self.locale)
|
||||
@ -154,16 +154,16 @@ impl DefaultFormat {
|
||||
|
||||
|
||||
#[allow(trivial_numeric_casts)]
|
||||
fn long_local(time: Time) -> String {
|
||||
let date = LocalDateTime::at(time.seconds as i64);
|
||||
fn long_local(time: Duration) -> String {
|
||||
let date = LocalDateTime::at(time.as_secs() as i64);
|
||||
format!("{:04}-{:02}-{:02} {:02}:{:02}",
|
||||
date.year(), date.month() as usize, date.day(),
|
||||
date.hour(), date.minute())
|
||||
}
|
||||
|
||||
#[allow(trivial_numeric_casts)]
|
||||
fn long_zoned(time: Time, zone: &TimeZone) -> String {
|
||||
let date = zone.to_zoned(LocalDateTime::at(time.seconds as i64));
|
||||
fn long_zoned(time: Duration, zone: &TimeZone) -> String {
|
||||
let date = zone.to_zoned(LocalDateTime::at(time.as_secs() as i64));
|
||||
format!("{:04}-{:02}-{:02} {:02}:{:02}",
|
||||
date.year(), date.month() as usize, date.day(),
|
||||
date.hour(), date.minute())
|
||||
@ -171,23 +171,23 @@ fn long_zoned(time: Time, zone: &TimeZone) -> String {
|
||||
|
||||
|
||||
#[allow(trivial_numeric_casts)]
|
||||
fn full_local(time: Time) -> String {
|
||||
let date = LocalDateTime::at(time.seconds as i64);
|
||||
fn full_local(time: Duration) -> String {
|
||||
let date = LocalDateTime::at(time.as_secs() as i64);
|
||||
format!("{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:09}",
|
||||
date.year(), date.month() as usize, date.day(),
|
||||
date.hour(), date.minute(), date.second(), time.nanoseconds)
|
||||
date.hour(), date.minute(), date.second(), time.subsec_nanos())
|
||||
}
|
||||
|
||||
#[allow(trivial_numeric_casts)]
|
||||
fn full_zoned(time: Time, zone: &TimeZone) -> String {
|
||||
fn full_zoned(time: Duration, zone: &TimeZone) -> String {
|
||||
use datetime::Offset;
|
||||
|
||||
let local = LocalDateTime::at(time.seconds as i64);
|
||||
let local = LocalDateTime::at(time.as_secs() as i64);
|
||||
let date = zone.to_zoned(local);
|
||||
let offset = Offset::of_seconds(zone.offset(local) as i32).expect("Offset out of range");
|
||||
format!("{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:09} {:+03}{:02}",
|
||||
date.year(), date.month() as usize, date.day(),
|
||||
date.hour(), date.minute(), date.second(), time.nanoseconds,
|
||||
date.hour(), date.minute(), date.second(), time.subsec_nanos(),
|
||||
offset.hours(), offset.minutes().abs())
|
||||
}
|
||||
|
||||
@ -214,8 +214,8 @@ impl ISOFormat {
|
||||
}
|
||||
|
||||
#[allow(trivial_numeric_casts)]
|
||||
fn format_local(&self, time: Time) -> String {
|
||||
let date = LocalDateTime::at(time.seconds as i64);
|
||||
fn format_local(&self, time: Duration) -> String {
|
||||
let date = LocalDateTime::at(time.as_secs() as i64);
|
||||
|
||||
if self.is_recent(date) {
|
||||
format!("{:02}-{:02} {:02}:{:02}",
|
||||
@ -229,8 +229,8 @@ impl ISOFormat {
|
||||
}
|
||||
|
||||
#[allow(trivial_numeric_casts)]
|
||||
fn format_zoned(&self, time: Time, zone: &TimeZone) -> String {
|
||||
let date = zone.to_zoned(LocalDateTime::at(time.seconds as i64));
|
||||
fn format_zoned(&self, time: Duration, zone: &TimeZone) -> String {
|
||||
let date = zone.to_zoned(LocalDateTime::at(time.as_secs() as i64));
|
||||
|
||||
if self.is_recent(date) {
|
||||
format!("{:02}-{:02} {:02}:{:02}",
|
||||
|
Loading…
Reference in New Issue
Block a user