Skip to content
Merged
1 change: 1 addition & 0 deletions src/uu/du/locales/en-US.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
1 change: 1 addition & 0 deletions src/uu/du/locales/fr-FR.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
45 changes: 35 additions & 10 deletions src/uu/du/src/du.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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()),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it make sense to use an enum here ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

confused in Rust.... How? ,-P

This is not allowed (you can set values but they need to be isize type:

enum Format {
    FULL_ISO = "%Y-%m-%d %H:%M:%S.%N %z",
    LONG_ISO = "%Y-%m-%d %H:%M",
    ISO = "%Y-%m-%d",
}

https://stackoverflow.com/questions/36928569/how-can-i-create-enums-with-constant-values-in-rust gave me this idea:

pub struct Format;

impl Format {
    pub const FULL_ISO: &str = "%Y-%m-%d %H:%M:%S.%N %z";
    pub const LONG_ISO: &str = "%Y-%m-%d %H:%M";
    pub const ISO: &str = "%Y-%m-%d";
}

This is not so bad (I somewhat prefer using static to const but that's not allowed here... but maybe I'm wrong, I think static and const should behave the same anyway in this case)

---- wait---

Or you mean something like:

enum Format {
   LongIso,
   FullIso,
   Iso,
   Custom(String)
}

That's not awesome. Would work ok in the du case, but really not in the ls case that has quite a bit more logic (and it's probably better performance-wise to collect the String format first and not do more checks at formatting time).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, enum Format {

but please ignore me if it doesn't make sense!

"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()),
}
}

Expand Down
7 changes: 6 additions & 1 deletion src/uu/ls/locales/en-US.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion src/uu/ls/locales/fr-FR.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
123 changes: 73 additions & 50 deletions src/uu/ls/src/ls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand All @@ -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)]
Expand All @@ -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};
Expand Down Expand Up @@ -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 {
Expand All @@ -217,7 +220,7 @@ impl UError for LsError {
Self::BlockSizeParseError(_) => 2,
Self::DiredAndZeroAreIncompatible => 2,
Self::AlreadyListedError(_) => 2,
Self::TimeStyleParseError(_, _) => 2,
Self::TimeStyleParseError(_) => 2,
}
}
}
Expand Down Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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)]
Expand All @@ -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 {
Expand Down Expand Up @@ -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,
};

Expand Down
6 changes: 6 additions & 0 deletions src/uucore/src/lib/features/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand Down
Loading
Loading