mirror of
https://github.com/uutils/coreutils.git
synced 2025-12-23 08:47:37 +00:00
Merge pull request #8415 from drinkcat/duls-time
`du`/`ls`: Improve time-style handling based on GNU coreutils manual
This commit is contained in:
commit
f877f64362
10 changed files with 432 additions and 84 deletions
|
|
@ -52,6 +52,7 @@ du-error-invalid-time-style = invalid argument { $style } for 'time style'
|
|||
- 'full-iso'
|
||||
- 'long-iso'
|
||||
- 'iso'
|
||||
- +FORMAT (e.g., +%H:%M) for a 'date'-style format
|
||||
Try '{ $help }' for more information.
|
||||
du-error-invalid-time-arg = 'birth' and 'creation' arguments for --time are not supported on this platform.
|
||||
du-error-invalid-glob = Invalid exclude syntax: { $error }
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ du-error-invalid-time-style = argument invalide { $style } pour 'style de temps'
|
|||
- 'full-iso'
|
||||
- 'long-iso'
|
||||
- 'iso'
|
||||
- +FORMAT (e.g., +%H:%M) pour un format de type 'date'
|
||||
Essayez '{ $help }' pour plus d'informations.
|
||||
du-error-invalid-time-arg = les arguments 'birth' et 'creation' pour --time ne sont pas supportés sur cette plateforme.
|
||||
du-error-invalid-glob = Syntaxe d'exclusion invalide : { $error }
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ use uucore::translate;
|
|||
use uucore::parser::parse_glob;
|
||||
use uucore::parser::parse_size::{ParseSizeError, parse_size_u64};
|
||||
use uucore::parser::shortcut_value_parser::ShortcutValueParser;
|
||||
use uucore::time::{FormatSystemTimeFallback, format_system_time};
|
||||
use uucore::time::{FormatSystemTimeFallback, format, format_system_time};
|
||||
use uucore::{format_usage, show, show_error, show_warning};
|
||||
#[cfg(windows)]
|
||||
use windows_sys::Win32::Foundation::HANDLE;
|
||||
|
|
@ -666,9 +666,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
};
|
||||
|
||||
let time_format = if time.is_some() {
|
||||
parse_time_style(matches.get_one::<String>("time-style").map(|s| s.as_str()))?.to_string()
|
||||
parse_time_style(matches.get_one::<String>("time-style"))?
|
||||
} else {
|
||||
"%Y-%m-%d %H:%M".to_string()
|
||||
format::LONG_ISO.to_string()
|
||||
};
|
||||
|
||||
let stat_printer = StatPrinter {
|
||||
|
|
@ -755,15 +755,40 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_time_style(s: Option<&str>) -> UResult<&str> {
|
||||
// Parse --time-style argument, falling back to environment variable if necessary.
|
||||
fn parse_time_style(s: Option<&String>) -> UResult<String> {
|
||||
let s = match s {
|
||||
Some(s) => Some(s.into()),
|
||||
None => {
|
||||
match env::var("TIME_STYLE") {
|
||||
// Per GNU manual, strip `posix-` if present, ignore anything after a newline if
|
||||
// the string starts with +, and ignore "locale".
|
||||
Ok(s) => {
|
||||
let s = s.strip_prefix("posix-").unwrap_or(s.as_str());
|
||||
let s = match s.chars().next().unwrap() {
|
||||
'+' => s.split('\n').next().unwrap(),
|
||||
_ => s,
|
||||
};
|
||||
match s {
|
||||
"locale" => None,
|
||||
_ => Some(s.to_string()),
|
||||
}
|
||||
}
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
};
|
||||
match s {
|
||||
Some(s) => match s {
|
||||
"full-iso" => Ok("%Y-%m-%d %H:%M:%S.%f %z"),
|
||||
"long-iso" => Ok("%Y-%m-%d %H:%M"),
|
||||
"iso" => Ok("%Y-%m-%d"),
|
||||
_ => Err(DuError::InvalidTimeStyleArg(s.into()).into()),
|
||||
Some(s) => match s.as_ref() {
|
||||
"full-iso" => Ok(format::FULL_ISO.to_string()),
|
||||
"long-iso" => Ok(format::LONG_ISO.to_string()),
|
||||
"iso" => Ok(format::ISO.to_string()),
|
||||
_ => match s.chars().next().unwrap() {
|
||||
'+' => Ok(s[1..].to_string()),
|
||||
_ => Err(DuError::InvalidTimeStyleArg(s).into()),
|
||||
},
|
||||
},
|
||||
None => Ok("%Y-%m-%d %H:%M"),
|
||||
None => Ok(format::LONG_ISO.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,12 @@ ls-error-invalid-block-size = invalid --block-size argument {$size}
|
|||
ls-error-dired-and-zero-incompatible = --dired and --zero are incompatible
|
||||
ls-error-not-listing-already-listed = {$path}: not listing already-listed directory
|
||||
ls-error-invalid-time-style = invalid --time-style argument {$style}
|
||||
Possible values are: {$values}
|
||||
Possible values are:
|
||||
- [posix-]full-iso
|
||||
- [posix-]long-iso
|
||||
- [posix-]iso
|
||||
- [posix-]locale
|
||||
- +FORMAT (e.g., +%H:%M) for a 'date'-style format
|
||||
|
||||
For more information try --help
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,12 @@ ls-error-invalid-block-size = argument --block-size invalide {$size}
|
|||
ls-error-dired-and-zero-incompatible = --dired et --zero sont incompatibles
|
||||
ls-error-not-listing-already-listed = {$path} : ne liste pas un répertoire déjà listé
|
||||
ls-error-invalid-time-style = argument --time-style invalide {$style}
|
||||
Les valeurs possibles sont : {$values}
|
||||
Les valeurs possibles sont :
|
||||
- [posix-]full-iso
|
||||
- [posix-]long-iso
|
||||
- [posix-]iso
|
||||
- [posix-]locale
|
||||
- +FORMAT (e.g., +%H:%M) pour un format de type 'date'
|
||||
|
||||
Pour plus d'informations, essayez --help
|
||||
|
||||
|
|
|
|||
|
|
@ -5,23 +5,26 @@
|
|||
|
||||
// spell-checker:ignore (ToDO) somegroup nlink tabsize dired subdired dtype colorterm stringly nohash strtime
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::collections::HashMap;
|
||||
use std::iter;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::{FileTypeExt, MetadataExt};
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::fs::MetadataExt;
|
||||
use std::{cell::LazyCell, cell::OnceCell, num::IntErrorKind};
|
||||
use std::{
|
||||
cell::{LazyCell, OnceCell},
|
||||
cmp::Reverse,
|
||||
collections::HashSet,
|
||||
ffi::{OsStr, OsString},
|
||||
fmt::Write as FmtWrite,
|
||||
fs::{self, DirEntry, FileType, Metadata, ReadDir},
|
||||
io::{BufWriter, ErrorKind, Stdout, Write, stdout},
|
||||
io::{BufWriter, ErrorKind, IsTerminal, Stdout, Write, stdout},
|
||||
iter,
|
||||
num::IntErrorKind,
|
||||
ops::RangeInclusive,
|
||||
path::{Path, PathBuf},
|
||||
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
use std::{collections::HashSet, io::IsTerminal};
|
||||
|
||||
use ansi_width::ansi_width;
|
||||
use clap::{
|
||||
|
|
@ -32,12 +35,9 @@ use glob::{MatchOptions, Pattern};
|
|||
use lscolors::LsColors;
|
||||
use term_grid::{DEFAULT_SEPARATOR_SIZE, Direction, Filling, Grid, GridOptions, SPACES_IN_TAB};
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg(unix)]
|
||||
use uucore::entries;
|
||||
use uucore::error::USimpleError;
|
||||
use uucore::format::human::{SizeFormat, human_readable};
|
||||
use uucore::fs::FileInformation;
|
||||
use uucore::fsext::{MetadataTimeField, metadata_get_time};
|
||||
#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))]
|
||||
use uucore::fsxattr::has_acl;
|
||||
#[cfg(unix)]
|
||||
|
|
@ -55,22 +55,25 @@ use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR};
|
|||
target_os = "solaris"
|
||||
))]
|
||||
use uucore::libc::{dev_t, major, minor};
|
||||
use uucore::line_ending::LineEnding;
|
||||
use uucore::translate;
|
||||
|
||||
use uucore::quoting_style::{QuotingStyle, locale_aware_escape_dir_name, locale_aware_escape_name};
|
||||
use uucore::time::{FormatSystemTimeFallback, format_system_time};
|
||||
use uucore::{
|
||||
display::Quotable,
|
||||
error::{UError, UResult, set_exit_code},
|
||||
error::{UError, UResult, USimpleError, set_exit_code},
|
||||
format::human::{SizeFormat, human_readable},
|
||||
format_usage,
|
||||
fs::FileInformation,
|
||||
fs::display_permissions,
|
||||
fsext::{MetadataTimeField, metadata_get_time},
|
||||
line_ending::LineEnding,
|
||||
os_str_as_bytes_lossy,
|
||||
parser::parse_glob,
|
||||
parser::parse_size::parse_size_u64,
|
||||
parser::shortcut_value_parser::ShortcutValueParser,
|
||||
quoting_style::{QuotingStyle, locale_aware_escape_dir_name, locale_aware_escape_name},
|
||||
show, show_error, show_warning,
|
||||
time::{FormatSystemTimeFallback, format, format_system_time},
|
||||
translate,
|
||||
version_cmp::version_cmp,
|
||||
};
|
||||
use uucore::{parser::parse_glob, show, show_error, show_warning};
|
||||
|
||||
mod dired;
|
||||
use dired::{DiredOutput, is_dired_arg_present};
|
||||
|
|
@ -203,8 +206,8 @@ enum LsError {
|
|||
#[error("{}", translate!("ls-error-not-listing-already-listed", "path" => .0.to_string_lossy()))]
|
||||
AlreadyListedError(PathBuf),
|
||||
|
||||
#[error("{}", translate!("ls-error-invalid-time-style", "style" => .0.quote(), "values" => format!("{:?}", .1)))]
|
||||
TimeStyleParseError(String, Vec<String>),
|
||||
#[error("{}", translate!("ls-error-invalid-time-style", "style" => .0.quote()))]
|
||||
TimeStyleParseError(String),
|
||||
}
|
||||
|
||||
impl UError for LsError {
|
||||
|
|
@ -217,7 +220,7 @@ impl UError for LsError {
|
|||
Self::BlockSizeParseError(_) => 2,
|
||||
Self::DiredAndZeroAreIncompatible => 2,
|
||||
Self::AlreadyListedError(_) => 2,
|
||||
Self::TimeStyleParseError(_, _) => 2,
|
||||
Self::TimeStyleParseError(_) => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -250,53 +253,70 @@ enum Files {
|
|||
}
|
||||
|
||||
fn parse_time_style(options: &clap::ArgMatches) -> Result<(String, Option<String>), LsError> {
|
||||
const TIME_STYLES: [(&str, (&str, Option<&str>)); 4] = [
|
||||
("full-iso", ("%Y-%m-%d %H:%M:%S.%f %z", None)),
|
||||
("long-iso", ("%Y-%m-%d %H:%M", None)),
|
||||
("iso", ("%m-%d %H:%M", Some("%Y-%m-%d "))),
|
||||
// TODO: Using correct locale string is not implemented.
|
||||
("locale", ("%b %e %H:%M", Some("%b %e %Y"))),
|
||||
];
|
||||
// A map from a time-style parameter to a length-2 tuple of formats:
|
||||
// the first one is used for recent dates, the second one for older ones (optional).
|
||||
let time_styles = HashMap::from(TIME_STYLES);
|
||||
let possible_time_styles = TIME_STYLES
|
||||
.iter()
|
||||
.map(|(x, _)| *x)
|
||||
.chain(iter::once(
|
||||
"+FORMAT (e.g., +%H:%M) for a 'date'-style format",
|
||||
))
|
||||
.map(|s| s.to_string());
|
||||
// TODO: Using correct locale string is not implemented.
|
||||
const LOCALE_FORMAT: (&str, Option<&str>) = ("%b %e %H:%M", Some("%b %e %Y"));
|
||||
|
||||
// Convert time_styles references to owned String/option.
|
||||
fn ok((recent, older): (&str, Option<&str>)) -> Result<(String, Option<String>), LsError> {
|
||||
Ok((recent.to_string(), older.map(String::from)))
|
||||
}
|
||||
|
||||
if let Some(field) = options.get_one::<String>(options::TIME_STYLE) {
|
||||
if let Some(field) = options
|
||||
.get_one::<String>(options::TIME_STYLE)
|
||||
.map(|s| s.to_owned())
|
||||
.or_else(|| std::env::var("TIME_STYLE").ok())
|
||||
{
|
||||
//If both FULL_TIME and TIME_STYLE are present
|
||||
//The one added last is dominant
|
||||
if options.get_flag(options::FULL_TIME)
|
||||
&& options.indices_of(options::FULL_TIME).unwrap().next_back()
|
||||
> options.indices_of(options::TIME_STYLE).unwrap().next_back()
|
||||
{
|
||||
ok(time_styles["full-iso"])
|
||||
ok((format::FULL_ISO, None))
|
||||
} else {
|
||||
match time_styles.get(field.as_str()) {
|
||||
Some(formats) => ok(*formats),
|
||||
None => match field.chars().next().unwrap() {
|
||||
'+' => Ok((field[1..].to_string(), None)),
|
||||
_ => Err(LsError::TimeStyleParseError(
|
||||
String::from(field),
|
||||
possible_time_styles.collect(),
|
||||
)),
|
||||
let field = if let Some(field) = field.strip_prefix("posix-") {
|
||||
// See GNU documentation, set format to "locale" if LC_TIME="POSIX",
|
||||
// else just strip the prefix and continue (even "posix+FORMAT" is
|
||||
// supported).
|
||||
// TODO: This needs to be moved to uucore and handled by icu?
|
||||
if std::env::var("LC_TIME").unwrap_or_default() == "POSIX"
|
||||
|| std::env::var("LC_ALL").unwrap_or_default() == "POSIX"
|
||||
{
|
||||
return ok(LOCALE_FORMAT);
|
||||
}
|
||||
field
|
||||
} else {
|
||||
&field
|
||||
};
|
||||
|
||||
match field {
|
||||
"full-iso" => ok((format::FULL_ISO, None)),
|
||||
"long-iso" => ok((format::LONG_ISO, None)),
|
||||
// ISO older format needs extra padding.
|
||||
"iso" => Ok((
|
||||
"%m-%d %H:%M".to_string(),
|
||||
Some(format::ISO.to_string() + " "),
|
||||
)),
|
||||
"locale" => ok(LOCALE_FORMAT),
|
||||
_ => match field.chars().next().unwrap() {
|
||||
'+' => {
|
||||
// recent/older formats are (optionally) separated by a newline
|
||||
let mut it = field[1..].split('\n');
|
||||
let recent = it.next().unwrap_or_default();
|
||||
let older = it.next();
|
||||
match it.next() {
|
||||
None => ok((recent, older)),
|
||||
Some(_) => Err(LsError::TimeStyleParseError(String::from(field))),
|
||||
}
|
||||
}
|
||||
_ => Err(LsError::TimeStyleParseError(String::from(field))),
|
||||
},
|
||||
}
|
||||
}
|
||||
} else if options.get_flag(options::FULL_TIME) {
|
||||
ok(time_styles["full-iso"])
|
||||
ok((format::FULL_ISO, None))
|
||||
} else {
|
||||
ok(time_styles["locale"])
|
||||
ok(LOCALE_FORMAT)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1941,7 +1961,7 @@ struct ListState<'a> {
|
|||
uid_cache: HashMap<u32, String>,
|
||||
#[cfg(unix)]
|
||||
gid_cache: HashMap<u32, String>,
|
||||
recent_time_threshold: SystemTime,
|
||||
recent_time_range: RangeInclusive<SystemTime>,
|
||||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
|
|
@ -1958,8 +1978,11 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> {
|
|||
uid_cache: HashMap::new(),
|
||||
#[cfg(unix)]
|
||||
gid_cache: HashMap::new(),
|
||||
// Time range for which to use the "recent" format. Anything from 0.5 year in the past to now
|
||||
// (files with modification time in the future use "old" format).
|
||||
// According to GNU a Gregorian year has 365.2425 * 24 * 60 * 60 == 31556952 seconds on the average.
|
||||
recent_time_threshold: SystemTime::now() - Duration::new(31_556_952 / 2, 0),
|
||||
recent_time_range: (SystemTime::now() - Duration::new(31_556_952 / 2, 0))
|
||||
..=SystemTime::now(),
|
||||
};
|
||||
|
||||
for loc in locs {
|
||||
|
|
@ -2943,7 +2966,7 @@ fn display_date(
|
|||
// Use "recent" format if the given date is considered recent (i.e., in the last 6 months),
|
||||
// or if no "older" format is available.
|
||||
let fmt = match &config.time_format_older {
|
||||
Some(time_format_older) if time <= state.recent_time_threshold => time_format_older,
|
||||
Some(time_format_older) if !state.recent_time_range.contains(&time) => time_format_older,
|
||||
_ => &config.time_format_recent,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,12 @@ pub fn system_time_to_sec(time: SystemTime) -> (i64, u32) {
|
|||
}
|
||||
}
|
||||
|
||||
pub mod format {
|
||||
pub static FULL_ISO: &str = "%Y-%m-%d %H:%M:%S.%N %z";
|
||||
pub static LONG_ISO: &str = "%Y-%m-%d %H:%M";
|
||||
pub static ISO: &str = "%Y-%m-%d";
|
||||
}
|
||||
|
||||
/// Sets how `format_system_time` behaves if the time cannot be converted.
|
||||
pub enum FormatSystemTimeFallback {
|
||||
Integer, // Just print seconds since epoch (`ls`)
|
||||
|
|
|
|||
|
|
@ -591,6 +591,7 @@ fn test_du_h_precision() {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
#[cfg(feature = "touch")]
|
||||
#[test]
|
||||
fn test_du_time() {
|
||||
|
|
@ -626,6 +627,107 @@ fn test_du_time() {
|
|||
.succeeds();
|
||||
result.stdout_only("0\t2016-06-16 00:00\tdate_test\n");
|
||||
|
||||
// long-iso (same as default)
|
||||
let result = ts
|
||||
.ucmd()
|
||||
.env("TZ", "UTC")
|
||||
.arg("--time")
|
||||
.arg("--time-style=long-iso")
|
||||
.arg("date_test")
|
||||
.succeeds();
|
||||
result.stdout_only("0\t2016-06-16 00:00\tdate_test\n");
|
||||
|
||||
// full-iso
|
||||
let result = ts
|
||||
.ucmd()
|
||||
.env("TZ", "UTC")
|
||||
.arg("--time")
|
||||
.arg("--time-style=full-iso")
|
||||
.arg("date_test")
|
||||
.succeeds();
|
||||
result.stdout_only("0\t2016-06-16 00:00:00.000000000 +0000\tdate_test\n");
|
||||
|
||||
// iso
|
||||
let result = ts
|
||||
.ucmd()
|
||||
.env("TZ", "UTC")
|
||||
.arg("--time")
|
||||
.arg("--time-style=iso")
|
||||
.arg("date_test")
|
||||
.succeeds();
|
||||
result.stdout_only("0\t2016-06-16\tdate_test\n");
|
||||
|
||||
// custom +FORMAT
|
||||
let result = ts
|
||||
.ucmd()
|
||||
.env("TZ", "UTC")
|
||||
.arg("--time")
|
||||
.arg("--time-style=+%Y__%H")
|
||||
.arg("date_test")
|
||||
.succeeds();
|
||||
result.stdout_only("0\t2016__00\tdate_test\n");
|
||||
|
||||
// ls has special handling for new line in format, du doesn't.
|
||||
let result = ts
|
||||
.ucmd()
|
||||
.env("TZ", "UTC")
|
||||
.arg("--time")
|
||||
.arg("--time-style=+%Y_\n_%H")
|
||||
.arg("date_test")
|
||||
.succeeds();
|
||||
result.stdout_only("0\t2016_\n_00\tdate_test\n");
|
||||
|
||||
// Time style can also be setup from environment
|
||||
let result = ts
|
||||
.ucmd()
|
||||
.env("TZ", "UTC")
|
||||
.env("TIME_STYLE", "full-iso")
|
||||
.arg("--time")
|
||||
.arg("date_test")
|
||||
.succeeds();
|
||||
result.stdout_only("0\t2016-06-16 00:00:00.000000000 +0000\tdate_test\n");
|
||||
|
||||
// For compatibility reason, we also allow posix- prefix.
|
||||
let result = ts
|
||||
.ucmd()
|
||||
.env("TZ", "UTC")
|
||||
.env("TIME_STYLE", "posix-full-iso")
|
||||
.arg("--time")
|
||||
.arg("date_test")
|
||||
.succeeds();
|
||||
result.stdout_only("0\t2016-06-16 00:00:00.000000000 +0000\tdate_test\n");
|
||||
|
||||
// ... and we strip content after a new line
|
||||
let result = ts
|
||||
.ucmd()
|
||||
.env("TZ", "UTC")
|
||||
.env("TIME_STYLE", "+XXX\nYYY")
|
||||
.arg("--time")
|
||||
.arg("date_test")
|
||||
.succeeds();
|
||||
result.stdout_only("0\tXXX\tdate_test\n");
|
||||
|
||||
// ... and we ignore "locale", fall back to full-iso.
|
||||
let result = ts
|
||||
.ucmd()
|
||||
.env("TZ", "UTC")
|
||||
.env("TIME_STYLE", "locale")
|
||||
.arg("--time")
|
||||
.arg("date_test")
|
||||
.succeeds();
|
||||
result.stdout_only("0\t2016-06-16 00:00\tdate_test\n");
|
||||
|
||||
// Command line option takes precedence
|
||||
let result = ts
|
||||
.ucmd()
|
||||
.env("TZ", "UTC")
|
||||
.env("TIME_STYLE", "full-iso")
|
||||
.arg("--time")
|
||||
.arg("--time-style=iso")
|
||||
.arg("date_test")
|
||||
.succeeds();
|
||||
result.stdout_only("0\t2016-06-16\tdate_test\n");
|
||||
|
||||
for argument in ["--time=atime", "--time=atim", "--time=a"] {
|
||||
let result = ts
|
||||
.ucmd()
|
||||
|
|
|
|||
|
|
@ -19,11 +19,11 @@ use std::collections::HashMap;
|
|||
use std::ffi::OsStr;
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::path::Path;
|
||||
#[cfg(not(windows))]
|
||||
use std::path::PathBuf;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
use std::{path::Path, time::SystemTime};
|
||||
use uutests::new_ucmd;
|
||||
#[cfg(unix)]
|
||||
use uutests::unwrap_or_return;
|
||||
|
|
@ -1923,24 +1923,38 @@ fn test_ls_order_birthtime() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_ls_styles() {
|
||||
fn test_ls_time_styles() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
// Create a recent and old (<6 months) file, as format can be different.
|
||||
at.touch("test");
|
||||
let f3 = at.make_file("test-old");
|
||||
f3.set_modified(SystemTime::now() - Duration::from_secs(3600 * 24 * 365))
|
||||
.unwrap();
|
||||
|
||||
let re_full = Regex::new(
|
||||
let re_full_recent = Regex::new(
|
||||
r"[a-z-]* \d* [\w.]* [\w.]* \d* \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d* (\+|\-)\d{4} test\n",
|
||||
)
|
||||
.unwrap();
|
||||
let re_long =
|
||||
let re_long_recent =
|
||||
Regex::new(r"[a-z-]* \d* [\w.]* [\w.]* \d* \d{4}-\d{2}-\d{2} \d{2}:\d{2} test\n").unwrap();
|
||||
let re_iso =
|
||||
let re_iso_recent =
|
||||
Regex::new(r"[a-z-]* \d* [\w.]* [\w.]* \d* \d{2}-\d{2} \d{2}:\d{2} test\n").unwrap();
|
||||
let re_locale =
|
||||
let re_locale_recent =
|
||||
Regex::new(r"[a-z-]* \d* [\w.]* [\w.]* \d* [A-Z][a-z]{2} ( |\d)\d \d{2}:\d{2} test\n")
|
||||
.unwrap();
|
||||
let re_custom_format =
|
||||
Regex::new(r"[a-z-]* \d* [\w.]* [\w.]* \d* \d{4}__\d{2} test\n").unwrap();
|
||||
let re_full_old = Regex::new(
|
||||
r"[a-z-]* \d* [\w.]* [\w.]* \d* \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d* (\+|\-)\d{4} test-old\n",
|
||||
)
|
||||
.unwrap();
|
||||
let re_long_old =
|
||||
Regex::new(r"[a-z-]* \d* [\w.]* [\w.]* \d* \d{4}-\d{2}-\d{2} \d{2}:\d{2} test-old\n")
|
||||
.unwrap();
|
||||
let re_iso_old =
|
||||
Regex::new(r"[a-z-]* \d* [\w.]* [\w.]* \d* \d{4}-\d{2}-\d{2} test-old\n").unwrap();
|
||||
let re_locale_old =
|
||||
Regex::new(r"[a-z-]* \d* [\w.]* [\w.]* \d* [A-Z][a-z]{2} ( |\d)\d \d{4} test-old\n")
|
||||
.unwrap();
|
||||
|
||||
//full-iso
|
||||
scene
|
||||
|
|
@ -1948,36 +1962,99 @@ fn test_ls_styles() {
|
|||
.arg("-l")
|
||||
.arg("--time-style=full-iso")
|
||||
.succeeds()
|
||||
.stdout_matches(&re_full);
|
||||
.stdout_matches(&re_full_recent)
|
||||
.stdout_matches(&re_full_old);
|
||||
//long-iso
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-l")
|
||||
.arg("--time-style=long-iso")
|
||||
.succeeds()
|
||||
.stdout_matches(&re_long);
|
||||
.stdout_matches(&re_long_recent)
|
||||
.stdout_matches(&re_long_old);
|
||||
//iso
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-l")
|
||||
.arg("--time-style=iso")
|
||||
.succeeds()
|
||||
.stdout_matches(&re_iso);
|
||||
.stdout_matches(&re_iso_recent)
|
||||
.stdout_matches(&re_iso_old);
|
||||
//locale
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-l")
|
||||
.arg("--time-style=locale")
|
||||
.succeeds()
|
||||
.stdout_matches(&re_locale);
|
||||
.stdout_matches(&re_locale_recent)
|
||||
.stdout_matches(&re_locale_old);
|
||||
|
||||
//posix-full-iso
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-l")
|
||||
.arg("--time-style=posix-full-iso")
|
||||
.succeeds()
|
||||
.stdout_matches(&re_full_recent)
|
||||
.stdout_matches(&re_full_old);
|
||||
//posix-long-iso
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-l")
|
||||
.arg("--time-style=posix-long-iso")
|
||||
.succeeds()
|
||||
.stdout_matches(&re_long_recent)
|
||||
.stdout_matches(&re_long_old);
|
||||
//posix-iso
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-l")
|
||||
.arg("--time-style=posix-iso")
|
||||
.succeeds()
|
||||
.stdout_matches(&re_iso_recent)
|
||||
.stdout_matches(&re_iso_old);
|
||||
|
||||
//posix-* with LC_TIME/LC_ALL=POSIX is equivalent to locale
|
||||
scene
|
||||
.ucmd()
|
||||
.env("LC_TIME", "POSIX")
|
||||
.arg("-l")
|
||||
.arg("--time-style=posix-full-iso")
|
||||
.succeeds()
|
||||
.stdout_matches(&re_locale_recent)
|
||||
.stdout_matches(&re_locale_old);
|
||||
scene
|
||||
.ucmd()
|
||||
.env("LC_ALL", "POSIX")
|
||||
.arg("-l")
|
||||
.arg("--time-style=posix-iso")
|
||||
.succeeds()
|
||||
.stdout_matches(&re_locale_recent)
|
||||
.stdout_matches(&re_locale_old);
|
||||
|
||||
//+FORMAT
|
||||
let re_custom_format_recent =
|
||||
Regex::new(r"[a-z-]* \d* [\w.]* [\w.]* \d* \d{4}__\d{2} test\n").unwrap();
|
||||
let re_custom_format_old =
|
||||
Regex::new(r"[a-z-]* \d* [\w.]* [\w.]* \d* \d{4}__\d{2} test-old\n").unwrap();
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-l")
|
||||
.arg("--time-style=+%Y__%M")
|
||||
.succeeds()
|
||||
.stdout_matches(&re_custom_format);
|
||||
.stdout_matches(&re_custom_format_recent)
|
||||
.stdout_matches(&re_custom_format_old);
|
||||
|
||||
//+FORMAT_RECENT\nFORMAT_OLD
|
||||
let re_custom_format_old =
|
||||
Regex::new(r"[a-z-]* \d* [\w.]* [\w.]* \d* \d{4}--\d{2} test-old\n").unwrap();
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-l")
|
||||
.arg("--time-style=+%Y__%M\n%Y--%M")
|
||||
.succeeds()
|
||||
.stdout_matches(&re_custom_format_recent)
|
||||
.stdout_matches(&re_custom_format_old);
|
||||
|
||||
// Also fails due to not having full clap support for time_styles
|
||||
scene
|
||||
|
|
@ -1986,6 +2063,13 @@ fn test_ls_styles() {
|
|||
.arg("--time-style=invalid")
|
||||
.fails_with_code(2);
|
||||
|
||||
// Cannot have 2 new lines in custom format
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-l")
|
||||
.arg("--time-style=+%Y__%M\n%Y--%M\n")
|
||||
.fails_with_code(2);
|
||||
|
||||
//Overwrite options tests
|
||||
scene
|
||||
.ucmd()
|
||||
|
|
@ -1993,19 +2077,19 @@ fn test_ls_styles() {
|
|||
.arg("--time-style=long-iso")
|
||||
.arg("--time-style=iso")
|
||||
.succeeds()
|
||||
.stdout_matches(&re_iso);
|
||||
.stdout_matches(&re_iso_recent);
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("--time-style=iso")
|
||||
.arg("--full-time")
|
||||
.succeeds()
|
||||
.stdout_matches(&re_full);
|
||||
.stdout_matches(&re_full_recent);
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("--full-time")
|
||||
.arg("--time-style=iso")
|
||||
.succeeds()
|
||||
.stdout_matches(&re_iso);
|
||||
.stdout_matches(&re_iso_recent);
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
|
|
@ -2013,7 +2097,7 @@ fn test_ls_styles() {
|
|||
.arg("--time-style=iso")
|
||||
.arg("--full-time")
|
||||
.succeeds()
|
||||
.stdout_matches(&re_full);
|
||||
.stdout_matches(&re_full_recent);
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
|
|
@ -2021,15 +2105,109 @@ fn test_ls_styles() {
|
|||
.arg("-x")
|
||||
.arg("-l")
|
||||
.succeeds()
|
||||
.stdout_matches(&re_full);
|
||||
.stdout_matches(&re_full_recent);
|
||||
|
||||
at.touch("test2");
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("--full-time")
|
||||
.arg("-x")
|
||||
.succeeds()
|
||||
.stdout_is("test test2\n");
|
||||
.stdout_is("test test-old\n");
|
||||
|
||||
// Time style can also be setup from environment
|
||||
scene
|
||||
.ucmd()
|
||||
.env("TIME_STYLE", "full-iso")
|
||||
.arg("-l")
|
||||
.succeeds()
|
||||
.stdout_matches(&re_full_recent);
|
||||
|
||||
// ... but option takes precedence
|
||||
scene
|
||||
.ucmd()
|
||||
.env("TIME_STYLE", "full-iso")
|
||||
.arg("-l")
|
||||
.arg("--time-style=long-iso")
|
||||
.succeeds()
|
||||
.stdout_matches(&re_long_recent);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ls_time_recent_future() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
let f = at.make_file("test");
|
||||
|
||||
let re_iso_recent =
|
||||
Regex::new(r"[a-z-]* \d* [\w.]* [\w.]* \d* \d{2}-\d{2} \d{2}:\d{2} test\n").unwrap();
|
||||
let re_iso_old =
|
||||
Regex::new(r"[a-z-]* \d* [\w.]* [\w.]* \d* \d{4}-\d{2}-\d{2} test\n").unwrap();
|
||||
|
||||
// `test` has just been created, so it's recent
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-l")
|
||||
.arg("--time-style=iso")
|
||||
.succeeds()
|
||||
.stdout_matches(&re_iso_recent);
|
||||
|
||||
// 100 days ago is still recent (<0.5 years)
|
||||
f.set_modified(SystemTime::now() - Duration::from_secs(3600 * 24 * 100))
|
||||
.unwrap();
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-l")
|
||||
.arg("--time-style=iso")
|
||||
.succeeds()
|
||||
.stdout_matches(&re_iso_recent);
|
||||
|
||||
// 200 days ago is not recent
|
||||
f.set_modified(SystemTime::now() - Duration::from_secs(3600 * 24 * 200))
|
||||
.unwrap();
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-l")
|
||||
.arg("--time-style=iso")
|
||||
.succeeds()
|
||||
.stdout_matches(&re_iso_old);
|
||||
|
||||
// A timestamp in the future (even just a minute), is not considered "recent"
|
||||
f.set_modified(SystemTime::now() + Duration::from_secs(60))
|
||||
.unwrap();
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-l")
|
||||
.arg("--time-style=iso")
|
||||
.succeeds()
|
||||
.stdout_matches(&re_iso_old);
|
||||
|
||||
// Also test that we can set a format that varies for recent of older files.
|
||||
//+FORMAT_RECENT\nFORMAT_OLD
|
||||
f.set_modified(SystemTime::now()).unwrap();
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-l")
|
||||
.arg("--time-style=+RECENT\nOLD")
|
||||
.succeeds()
|
||||
.stdout_contains("RECENT");
|
||||
|
||||
// Old file
|
||||
f.set_modified(SystemTime::now() - Duration::from_secs(3600 * 24 * 200))
|
||||
.unwrap();
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-l")
|
||||
.arg("--time-style=+RECENT\nOLD")
|
||||
.succeeds()
|
||||
.stdout_contains("OLD");
|
||||
|
||||
// RECENT format is still used if no "OLD" one provided.
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-l")
|
||||
.arg("--time-style=+RECENT")
|
||||
.succeeds()
|
||||
.stdout_contains("RECENT");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -329,8 +329,10 @@ sed -i -e "s|44 45|48 49|" tests/ls/stat-failed.sh
|
|||
|
||||
# small difference in the error message
|
||||
# Use GNU sed for /c command
|
||||
"${SED}" -i -e "/ls: invalid argument 'XX' for 'time style'/,/Try 'ls --help' for more information\./c\
|
||||
ls: invalid --time-style argument 'XX'\nPossible values are: [\"full-iso\", \"long-iso\", \"iso\", \"locale\", \"+FORMAT (e.g., +%H:%M) for a 'date'-style format\"]\n\nFor more information try --help" tests/ls/time-style-diag.sh
|
||||
"${SED}" -i -e "s/ls: invalid argument 'XX' for 'time style'/ls: invalid --time-style argument 'XX'/" \
|
||||
-e "s/Valid arguments are:/Possible values are:/" \
|
||||
-e "s/Try 'ls --help' for more information./\nFor more information try --help/" \
|
||||
tests/ls/time-style-diag.sh
|
||||
|
||||
# disable two kind of tests:
|
||||
# "hostid BEFORE --help" doesn't fail for GNU. we fail. we are probably doing better
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue