Skip to content

Commit 9cfeb65

Browse files
committed
Add command line optional argument ftime
- This argument can take 3 value atime (access), btime(birth), mtime(modification). - This argument will be used to change the file time displayed accordingly.
1 parent 1636cab commit 9cfeb65

File tree

6 files changed

+291
-8
lines changed

6 files changed

+291
-8
lines changed

doc/lsd.md

+10-7
Original file line numberDiff line numberDiff line change
@@ -96,37 +96,40 @@ lsd is a ls command with a lot of pretty colours and some other stuff to enrich
9696
: Specify the blocks that will be displayed and in what order [possible values: permission, user, group, size, date, name, inode, git]
9797

9898
`--color <color>...`
99-
: When to use terminal colours [default: auto] [possible values: always, auto, never]
99+
: When to use terminal colours [default: auto] [possible values: always, auto, never]
100100

101101
`--date <date>...`
102102
: How to display date [possible values: date, locale, relative, +date-time-format] [default: date]
103103

104104
`--depth <num>...`
105105
: Stop recursing into directories after reaching specified depth
106106

107+
`--ftime <time>...`
108+
: Which file timestamp to display [possible values: mtime (modification time), btime (birth time), atime (access time)] [default: mtime]
109+
107110
`--group-dirs <group-dirs>...`
108-
: Sort the directories then the files [default: none] [possible values: none, first, last]
111+
: Sort the directories then the files [default: none] [possible values: none, first, last]
109112

110113
`--group-directories-first`
111114
: Groups the directories at the top before the files. Same as `--group-dirs=first`
112115

113116
`--hyperlink <hyperlink>...`
114-
: Attach hyperlink to filenames [default: never] [possible values: always, auto, never]
117+
: Attach hyperlink to filenames [default: never] [possible values: always, auto, never]
115118

116119
`--icon <icon>...`
117-
: When to print the icons [default: auto] [possible values: always, auto, never]
120+
: When to print the icons [default: auto] [possible values: always, auto, never]
118121

119122
`--icon-theme <icon-theme>...`
120-
: Whether to use fancy or unicode icons [default: fancy] [possible values: fancy, unicode]
123+
: Whether to use fancy or unicode icons [default: fancy] [possible values: fancy, unicode]
121124

122125
`-I, --ignore-glob <pattern>...`
123126
: Do not display files/directories with names matching the glob pattern(s). More than one can be specified by repeating the argument [default: ]
124127

125128
`--permission <permission>...`
126-
: How to display permissions [default: rwx for linux, attributes for windows] [possible values: rwx, octal, attributes, disable]
129+
: How to display permissions [default: rwx for linux, attributes for windows] [possible values: rwx, octal, attributes, disable]
127130

128131
`--size <size>...`
129-
: How to display size [default: default] [possible values: default, short, bytes]
132+
: How to display size [default: default] [possible values: default, short, bytes]
130133

131134
`--sort <WORD>...`
132135
: Sort by WORD instead of name [possible values: size, time, version, extension, git]

src/app.rs

+12
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,10 @@ pub struct Cli {
193193
#[arg(short = 'N', long)]
194194
pub literal: bool,
195195

196+
/// Displayed file time [default: mtime] [possible values: mtime (modification time), btime (birth time), atime (access time)]
197+
#[arg(long, value_parser = validate_ftime_argument)]
198+
pub ftime: Option<String>,
199+
196200
/// Print help information
197201
#[arg(long, action = ArgAction::Help)]
198202
help: (),
@@ -208,6 +212,14 @@ fn validate_date_argument(arg: &str) -> Result<String, String> {
208212
}
209213
}
210214

215+
fn validate_ftime_argument(arg: &str) -> Result<String, String> {
216+
if arg == "mtime" || arg == "atime" || arg == "btime" {
217+
Result::Ok(arg.to_owned())
218+
} else {
219+
Result::Err("possible values: mtime, btime, atime".to_owned())
220+
}
221+
}
222+
211223
pub fn validate_time_format(formatter: &str) -> Result<String, String> {
212224
let mut chars = formatter.chars();
213225
loop {

src/config_file.rs

+3
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ pub struct Config {
4444
pub header: Option<bool>,
4545
pub literal: Option<bool>,
4646
pub truncate_owner: Option<TruncateOwner>,
47+
pub ftime: Option<String>,
4748
}
4849

4950
#[derive(Eq, PartialEq, Debug, Deserialize)]
@@ -129,6 +130,7 @@ impl Config {
129130
header: None,
130131
literal: None,
131132
truncate_owner: None,
133+
ftime: None,
132134
}
133135
}
134136

@@ -428,6 +430,7 @@ mod tests {
428430
after: None,
429431
marker: Some("".to_string()),
430432
}),
433+
ftime: None,
431434
},
432435
c
433436
);

src/flags.rs

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pub mod color;
33
pub mod date;
44
pub mod dereference;
55
pub mod display;
6+
pub mod ftime;
67
pub mod header;
78
pub mod hyperlink;
89
pub mod icons;
@@ -25,6 +26,7 @@ pub use color::{ColorOption, ThemeOption};
2526
pub use date::DateFlag;
2627
pub use dereference::Dereference;
2728
pub use display::Display;
29+
pub use ftime::FileTimeFlag;
2830
pub use header::Header;
2931
pub use hyperlink::HyperlinkOption;
3032
pub use icons::IconOption;
@@ -77,6 +79,7 @@ pub struct Flags {
7779
pub header: Header,
7880
pub literal: Literal,
7981
pub truncate_owner: TruncateOwner,
82+
pub ftime: FileTimeFlag,
8083
}
8184

8285
impl Flags {
@@ -108,6 +111,7 @@ impl Flags {
108111
header: Header::configure_from(cli, config),
109112
literal: Literal::configure_from(cli, config),
110113
truncate_owner: TruncateOwner::configure_from(cli, config),
114+
ftime: FileTimeFlag::configure_from(cli, config),
111115
})
112116
}
113117
}

src/flags/ftime.rs

+261
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
//! This module defines the [FileTimeFlag]. To set it up from [Cli], a [Config] and its
2+
//! [Default] value, use its [configure_from](Configurable::configure_from) method.
3+
4+
use super::Configurable;
5+
6+
use crate::app::Cli;
7+
use crate::config_file::Config;
8+
use crate::print_error;
9+
10+
/// The flag showing which kind of time stamps to display.
11+
#[derive(Clone, Debug, PartialEq, Eq, Default)]
12+
pub enum FileTimeFlag {
13+
#[default]
14+
Modification,
15+
Birth,
16+
Access,
17+
}
18+
19+
impl FileTimeFlag {
20+
/// Get a value from a str.
21+
fn from_str<S: AsRef<str>>(value: S) -> Option<Self> {
22+
let value = value.as_ref();
23+
match value {
24+
"mtime" => Some(Self::Modification),
25+
"btime" => Some(Self::Birth),
26+
"atime" => Some(Self::Access),
27+
_ => {
28+
print_error!("Not a valid ftime value: {}.", value);
29+
None
30+
}
31+
}
32+
}
33+
}
34+
35+
impl Configurable<Self> for FileTimeFlag {
36+
/// Get a potential `FileTimeFlag` variant from [Cli].
37+
///
38+
/// If the "classic" argument is passed, then this returns the [FileTimeFlag::ModificationTime] variant in a
39+
/// [Some]. Otherwise if the argument is passed, this returns the variant corresponding to its
40+
/// parameter in a [Some]. Otherwise this returns [None].
41+
fn from_cli(cli: &Cli) -> Option<Self> {
42+
if cli.classic {
43+
Some(Self::Modification)
44+
} else {
45+
cli.ftime.as_deref().and_then(Self::from_str)
46+
}
47+
}
48+
49+
/// Get a potential `FileTimeFlag` variant from a [Config].
50+
///
51+
/// If the `Config::classic` is `true` then this returns the Some(FileTimeFlag::Date),
52+
/// Otherwise if the `Config::date` has value and is one of "date", "locale" or "relative",
53+
/// this returns its corresponding variant in a [Some].
54+
/// Otherwise this returns [None].
55+
fn from_config(config: &Config) -> Option<Self> {
56+
if config.classic == Some(true) {
57+
Some(Self::Modification)
58+
} else {
59+
config.ftime.as_ref().and_then(Self::from_str)
60+
}
61+
}
62+
63+
/// Get a potential `FileTimeFlag` variant from the environment.
64+
fn from_environment() -> Option<Self> {
65+
if let Ok(value) = std::env::var("LSD_FILE_TIME") {
66+
match value.as_str() {
67+
"atime" => Some(Self::Access),
68+
"btime" => Some(Self::Birth),
69+
"mtime" => Some(Self::Modification),
70+
_ => {
71+
print_error!("Not a valid date value: {}.", value);
72+
None
73+
}
74+
}
75+
} else {
76+
None
77+
}
78+
}
79+
}
80+
81+
#[cfg(test)]
82+
mod test {
83+
use clap::Parser;
84+
85+
use super::FileTimeFlag;
86+
87+
use crate::app::Cli;
88+
use crate::config_file::Config;
89+
use crate::flags::Configurable;
90+
91+
#[test]
92+
fn test_from_cli_none() {
93+
let argv = ["lsd"];
94+
let cli = Cli::try_parse_from(argv).unwrap();
95+
assert_eq!(None, FileTimeFlag::from_cli(&cli));
96+
}
97+
98+
#[test]
99+
fn test_from_cli_mtime() {
100+
let argv = ["lsd", "--ftime", "mtime"];
101+
let cli = Cli::try_parse_from(argv).unwrap();
102+
assert_eq!(
103+
Some(FileTimeFlag::Modification),
104+
FileTimeFlag::from_cli(&cli)
105+
);
106+
}
107+
108+
#[test]
109+
fn test_from_cli_atime() {
110+
let argv = ["lsd", "--ftime", "atime"];
111+
let cli = Cli::try_parse_from(argv).unwrap();
112+
assert_eq!(Some(FileTimeFlag::Access), FileTimeFlag::from_cli(&cli));
113+
}
114+
115+
#[test]
116+
fn test_from_cli_btime() {
117+
let argv = ["lsd", "--ftime", "btime"];
118+
let cli = Cli::try_parse_from(argv).unwrap();
119+
assert_eq!(Some(FileTimeFlag::Birth), FileTimeFlag::from_cli(&cli));
120+
}
121+
122+
#[test]
123+
#[should_panic = "possible values: mtime, btime, atime"]
124+
fn test_from_cli_invalid() {
125+
let argv = ["lsd", "--ftime", "foo"];
126+
let cli = Cli::try_parse_from(argv).unwrap();
127+
dbg!(&cli);
128+
FileTimeFlag::from_cli(&cli);
129+
}
130+
131+
#[test]
132+
fn test_from_cli_classic_mode() {
133+
let argv = ["lsd", "--ftime", "mtime", "--classic"];
134+
let cli = Cli::try_parse_from(argv).unwrap();
135+
assert_eq!(
136+
Some(FileTimeFlag::Modification),
137+
FileTimeFlag::from_cli(&cli)
138+
);
139+
}
140+
141+
#[test]
142+
fn test_from_cli_ftime_multi() {
143+
let argv = ["lsd", "--ftime", "mtime", "--ftime", "atime"];
144+
let cli = Cli::try_parse_from(argv).unwrap();
145+
assert_eq!(Some(FileTimeFlag::Access), FileTimeFlag::from_cli(&cli));
146+
}
147+
148+
#[test]
149+
fn test_from_config_none() {
150+
assert_eq!(None, FileTimeFlag::from_config(&Config::with_none()));
151+
}
152+
153+
#[test]
154+
fn test_from_config_mtime() {
155+
let mut c = Config::with_none();
156+
c.ftime = Some("mtime".into());
157+
158+
assert_eq!(
159+
Some(FileTimeFlag::Modification),
160+
FileTimeFlag::from_config(&c)
161+
);
162+
}
163+
164+
#[test]
165+
fn test_from_config_atime() {
166+
let mut c = Config::with_none();
167+
c.ftime = Some("atime".into());
168+
assert_eq!(Some(FileTimeFlag::Access), FileTimeFlag::from_config(&c));
169+
}
170+
171+
#[test]
172+
fn test_from_config_btime() {
173+
let mut c = Config::with_none();
174+
c.ftime = Some("btime".into());
175+
assert_eq!(Some(FileTimeFlag::Birth), FileTimeFlag::from_config(&c));
176+
}
177+
178+
#[test]
179+
fn test_from_config_classic_mode() {
180+
let mut c = Config::with_none();
181+
c.ftime = Some("atime".into());
182+
c.classic = Some(true);
183+
assert_eq!(
184+
Some(FileTimeFlag::Modification),
185+
FileTimeFlag::from_config(&c)
186+
);
187+
}
188+
189+
#[test]
190+
#[serial_test::serial]
191+
fn test_from_environment_none() {
192+
std::env::set_var("LSD_FILE_TIME", "");
193+
assert_eq!(None, FileTimeFlag::from_environment());
194+
}
195+
196+
#[test]
197+
#[serial_test::serial]
198+
fn test_from_environment_mtime() {
199+
std::env::set_var("LSD_FILE_TIME", "mtime");
200+
assert_eq!(
201+
Some(FileTimeFlag::Modification),
202+
FileTimeFlag::from_environment()
203+
);
204+
}
205+
206+
#[test]
207+
#[serial_test::serial]
208+
fn test_from_environment_atime() {
209+
std::env::set_var("LSD_FILE_TIME", "atime");
210+
assert_eq!(Some(FileTimeFlag::Access), FileTimeFlag::from_environment());
211+
}
212+
213+
#[test]
214+
#[serial_test::serial]
215+
fn test_from_environment_btime() {
216+
std::env::set_var("LSD_FILE_TIME", "btime");
217+
assert_eq!(Some(FileTimeFlag::Birth), FileTimeFlag::from_environment());
218+
}
219+
220+
#[test]
221+
#[serial_test::serial]
222+
fn test_parsing_order_arg() {
223+
std::env::set_var("LSD_FILE_TIME", "mtime");
224+
let argv = ["lsd", "--ftime", "atime"];
225+
let cli = Cli::try_parse_from(argv).unwrap();
226+
let mut config = Config::with_none();
227+
config.ftime = Some("btime".into());
228+
assert_eq!(
229+
FileTimeFlag::Access,
230+
FileTimeFlag::configure_from(&cli, &config)
231+
);
232+
}
233+
234+
#[test]
235+
#[serial_test::serial]
236+
fn test_parsing_order_env() {
237+
std::env::set_var("LSD_FILE_TIME", "mtime");
238+
let argv = ["lsd"];
239+
let cli = Cli::try_parse_from(argv).unwrap();
240+
let mut config = Config::with_none();
241+
config.ftime = Some("btime".into());
242+
assert_eq!(
243+
FileTimeFlag::Modification,
244+
FileTimeFlag::configure_from(&cli, &config)
245+
);
246+
}
247+
248+
#[test]
249+
#[serial_test::serial]
250+
fn test_parsing_order_config() {
251+
std::env::set_var("LSD_FILE_TIME", "");
252+
let argv = ["lsd"];
253+
let cli = Cli::try_parse_from(argv).unwrap();
254+
let mut config = Config::with_none();
255+
config.ftime = Some("btime".into());
256+
assert_eq!(
257+
FileTimeFlag::Birth,
258+
FileTimeFlag::configure_from(&cli, &config)
259+
);
260+
}
261+
}

0 commit comments

Comments
 (0)