Skip to content

Commit

Permalink
Date format supports recent/non-recent format
Browse files Browse the repository at this point in the history
  • Loading branch information
manno committed Feb 2, 2023
1 parent ea56a7c commit eb2ceaf
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 37 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add `--system-protected` to include files with the Windows `system` flag set,
on other platform the same as `--all` [#752](https://github.com/Peltoche/lsd/issues/752)
- Add many icons from https://github.com/Peltoche/lsd/issues/764 [@TruncatedDinosour](https://ari-web.xyz/gh)
- Add support to format dates for recent files differently. If `'+<date_format>'` contains a second format it is applied to files from the last six months. The formats are separated by new-line, similar to `TIME_STYLE` in GNU ls.

### Fixed
- Do not quote filename when piping into another program from [TeamTamoad](https://github.com/TeamTamoad)
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ color:
# When "classic" is set, this is set to "date".
# Possible values: date, relative, '+<date_format>'
# `date_format` will be a `strftime` formatted value. e.g. `date: '+%d %b %y %X'` will give you a date like this: 17 Jun 21 20:14:55
# `'+<date_format>'` can contain a second format after a new-line. The second format is used format dates for files from the last six months.

date: date

# == Dereference ==
Expand Down
80 changes: 44 additions & 36 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -363,49 +363,57 @@ fn validate_date_argument(arg: &str) -> Result<String, String> {
}

pub fn validate_time_format(formatter: &str) -> Result<String, String> {
let mut chars = formatter.chars();
loop {
match chars.next() {
Some('%') => match chars.next() {
Some('.') => match chars.next() {
Some('f') => (),
Some(n @ ('3' | '6' | '9')) => match chars.next() {
let vec: Vec<&str> = formatter.split('\n').collect();

if vec.len() > 2 {
return Err("invalid date format, cannot contain more than two entries".to_owned());
}

for s in vec {
let mut chars = s.chars();
loop {
match chars.next() {
Some('%') => match chars.next() {
Some('.') => match chars.next() {
Some('f') => (),
Some(c) => return Err(format!("invalid format specifier: %.{}{}", n, c)),
Some(n @ ('3' | '6' | '9')) => match chars.next() {
Some('f') => (),
Some(c) => return Err(format!("invalid format specifier: %.{}{}", n, c)),
None => return Err("missing format specifier".to_owned()),
},
Some(c) => return Err(format!("invalid format specifier: %.{}", c)),
None => return Err("missing format specifier".to_owned()),
},
Some(n @ (':' | '#')) => match chars.next() {
Some('z') => (),
Some(c) => return Err(format!("invalid format specifier: %{}{}", n, c)),
None => return Err("missing format specifier".to_owned()),
},
Some(n @ ('-' | '_' | '0')) => match chars.next() {
Some(
'C' | 'd' | 'e' | 'f' | 'G' | 'g' | 'H' | 'I' | 'j' | 'k' | 'l' | 'M'
| 'm' | 'S' | 's' | 'U' | 'u' | 'V' | 'W' | 'w' | 'Y' | 'y',
) => (),
Some(c) => return Err(format!("invalid format specifier: %{}{}", n, c)),
None => return Err("missing format specifier".to_owned()),
},
Some(c) => return Err(format!("invalid format specifier: %.{}", c)),
None => return Err("missing format specifier".to_owned()),
},
Some(n @ (':' | '#')) => match chars.next() {
Some('z') => (),
Some(c) => return Err(format!("invalid format specifier: %{}{}", n, c)),
None => return Err("missing format specifier".to_owned()),
},
Some(n @ ('-' | '_' | '0')) => match chars.next() {
Some(
'C' | 'd' | 'e' | 'f' | 'G' | 'g' | 'H' | 'I' | 'j' | 'k' | 'l' | 'M' | 'm'
| 'S' | 's' | 'U' | 'u' | 'V' | 'W' | 'w' | 'Y' | 'y',
'A' | 'a' | 'B' | 'b' | 'C' | 'c' | 'D' | 'd' | 'e' | 'F' | 'f' | 'G' | 'g'
| 'H' | 'h' | 'I' | 'j' | 'k' | 'l' | 'M' | 'm' | 'n' | 'P' | 'p' | 'R'
| 'r' | 'S' | 's' | 'T' | 't' | 'U' | 'u' | 'V' | 'v' | 'W' | 'w' | 'X'
| 'x' | 'Y' | 'y' | 'Z' | 'z' | '+' | '%',
) => (),
Some(c) => return Err(format!("invalid format specifier: %{}{}", n, c)),
None => return Err("missing format specifier".to_owned()),
},
Some(
'A' | 'a' | 'B' | 'b' | 'C' | 'c' | 'D' | 'd' | 'e' | 'F' | 'f' | 'G' | 'g'
| 'H' | 'h' | 'I' | 'j' | 'k' | 'l' | 'M' | 'm' | 'n' | 'P' | 'p' | 'R' | 'r'
| 'S' | 's' | 'T' | 't' | 'U' | 'u' | 'V' | 'v' | 'W' | 'w' | 'X' | 'x' | 'Y'
| 'y' | 'Z' | 'z' | '+' | '%',
) => (),
Some(n @ ('3' | '6' | '9')) => match chars.next() {
Some('f') => (),
Some(c) => return Err(format!("invalid format specifier: %{}{}", n, c)),
Some(n @ ('3' | '6' | '9')) => match chars.next() {
Some('f') => (),
Some(c) => return Err(format!("invalid format specifier: %{}{}", n, c)),
None => return Err("missing format specifier".to_owned()),
},
Some(c) => return Err(format!("invalid format specifier: %{}", c)),
None => return Err("missing format specifier".to_owned()),
},
Some(c) => return Err(format!("invalid format specifier: %{}", c)),
None => return Err("missing format specifier".to_owned()),
},
None => break,
_ => continue,
None => break,
_ => continue,
}
}
}
Ok(formatter.to_owned())
Expand Down
68 changes: 67 additions & 1 deletion src/meta/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,17 @@ impl Date {
val.format("%F").to_string()
}
}
DateFlag::Formatted(format) => val.format(format).to_string(),
DateFlag::Formatted(format) => {
let vec: Vec<&str> = format.split('\n').collect();

if vec.len() == 1 || *val < Local::now() - Duration::seconds(15_778_476) {
// non-recent or only one format
val.format(vec[0]).to_string()
} else {
// recent
val.format(vec[1]).to_string()
}
}
}
} else {
String::from('-')
Expand Down Expand Up @@ -305,6 +315,62 @@ mod test {
fs::remove_file(file_path).unwrap();
}

#[test]
fn test_recent_format_now() {
let mut file_path = env::temp_dir();
file_path.push("test_recent_format_now.tmp");

let creation_date = Local::now();
let success = cross_platform_touch(&file_path, &creation_date)
.unwrap()
.success();
assert_eq!(true, success, "failed to exec touch");

let colors = Colors::new(ThemeOption::Default);
let date = Date::from(&file_path.metadata().unwrap());

let mut flags = Flags::default();
flags.date = DateFlag::Formatted(String::from("%F\n%H:%M"));

assert_eq!(
creation_date
.format("%H:%M")
.to_string()
.with(Color::AnsiValue(40)),
date.render(&colors, &flags)
);

fs::remove_file(file_path).unwrap();
}

#[test]
fn test_recent_format_year_old() {
let mut file_path = env::temp_dir();
file_path.push("test_recent_format_year_old.tmp");

let creation_date = Local::now() - Duration::days(400);
let success = cross_platform_touch(&file_path, &creation_date)
.unwrap()
.success();
assert_eq!(true, success, "failed to exec touch");

let colors = Colors::new(ThemeOption::Default);
let date = Date::from(&file_path.metadata().unwrap());

let mut flags = Flags::default();
flags.date = DateFlag::Formatted(String::from("%F\n%H:%M"));

assert_eq!(
creation_date
.format("%F")
.to_string()
.with(Color::AnsiValue(36)),
date.render(&colors, &flags)
);

fs::remove_file(file_path).unwrap();
}

#[test]
#[cfg(all(not(windows), target_arch = "x86_64"))]
fn test_bad_date() {
Expand Down

0 comments on commit eb2ceaf

Please sign in to comment.