Skip to content

Commit c0d2dc6

Browse files
committed
feat(flag): add en size separator flag
1 parent 86290a1 commit c0d2dc6

File tree

8 files changed

+145
-44
lines changed

8 files changed

+145
-44
lines changed

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,9 +223,16 @@ recursion:
223223

224224
# == Size ==
225225
# Specifies the format of the size column.
226-
# Possible values: default, short, bytes, bytes-with-separator
226+
# Possible values: default, short, bytes
227227
size: default
228228

229+
# == Size Separator ==
230+
# Specifies the number separator kind used for the size column.
231+
# use number format from https://docs.rs/num-format/latest/num_format/
232+
# Possible values: "en", "fr"
233+
#
234+
# size-separator: "en"
235+
229236
# == Permission ==
230237
# Specify the format of the permission column
231238
# Possible value: rwx, octal, attributes (windows only), disable

doc/lsd.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,10 @@ lsd is a ls command with a lot of pretty colours and some other stuff to enrich
126126
: How to display permissions [default: rwx for linux, attributes for windows] [possible values: rwx, octal, attributes, disable]
127127

128128
`--size <size>...`
129-
: How to display size [default: default] [possible values: default, short, bytes, bytes-with-separator]
129+
: How to display size [default: default] [possible values: default, short, bytes]
130+
131+
`--size-separator <size-separator>...`
132+
: Separator kind to format file size [default: none] [possible values: none, en]
130133

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

src/app.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,13 @@ pub struct Cli {
7373
pub permission: Option<String>,
7474

7575
/// How to display size [default: default]
76-
#[arg(long, value_name = "MODE", value_parser = ["default", "short", "bytes", "bytes-with-separator"])]
76+
#[arg(long, value_name = "MODE", value_parser = ["default", "short", "bytes"])]
7777
pub size: Option<String>,
7878

79+
/// Size separator kind [default: none]
80+
#[arg(long, value_name = "KIND", value_parser = ["en"])]
81+
pub size_separator: Option<String>,
82+
7983
/// Display the total size of directories
8084
#[arg(long)]
8185
pub total_size: bool,

src/config_file.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::flags::icons::{IconOption, IconTheme};
55
use crate::flags::layout::Layout;
66
use crate::flags::permission::PermissionFlag;
77
use crate::flags::size::SizeFlag;
8+
89
use crate::flags::sorting::{DirGrouping, SortColumn};
910
use crate::flags::HyperlinkOption;
1011
use crate::flags::{ColorOption, ThemeOption};
@@ -35,6 +36,7 @@ pub struct Config {
3536
pub layout: Option<Layout>,
3637
pub recursion: Option<Recursion>,
3738
pub size: Option<SizeFlag>,
39+
pub size_separator: Option<String>,
3840
pub permission: Option<PermissionFlag>,
3941
pub sorting: Option<Sorting>,
4042
pub no_symlink: Option<bool>,
@@ -120,6 +122,7 @@ impl Config {
120122
layout: None,
121123
recursion: None,
122124
size: None,
125+
size_separator: None,
123126
permission: None,
124127
sorting: None,
125128
no_symlink: None,
@@ -305,9 +308,16 @@ recursion:
305308
306309
# == Size ==
307310
# Specifies the format of the size column.
308-
# Possible values: default, short, bytes, bytes-with-separator
311+
# Possible values: default, short, bytes
309312
size: default
310313
314+
# == Size Separator ==
315+
# Specifies the number separator kind used for the size column.
316+
# use number format from https://docs.rs/num-format/latest/num_format/
317+
# Possible values: "en"
318+
#
319+
# size-separator: "en"
320+
311321
# == Permission ==
312322
# Specify the format of the permission column.
313323
# Possible value: rwx, octal, attributes, disable
@@ -412,6 +422,7 @@ mod tests {
412422
depth: None,
413423
}),
414424
size: Some(SizeFlag::Default),
425+
size_separator: None,
415426
permission: None,
416427
sorting: Some(config_file::Sorting {
417428
column: Some(SortColumn::Name),

src/flags.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub mod literal;
1313
pub mod permission;
1414
pub mod recursion;
1515
pub mod size;
16+
pub mod size_separator;
1617
pub mod sorting;
1718
pub mod symlink_arrow;
1819
pub mod symlinks;
@@ -37,6 +38,7 @@ pub use literal::Literal;
3738
pub use permission::PermissionFlag;
3839
pub use recursion::Recursion;
3940
pub use size::SizeFlag;
41+
use size_separator::SizeSeparator;
4042
pub use sorting::DirGrouping;
4143
pub use sorting::SortColumn;
4244
pub use sorting::SortOrder;
@@ -69,6 +71,7 @@ pub struct Flags {
6971
pub no_symlink: NoSymlink,
7072
pub recursion: Recursion,
7173
pub size: SizeFlag,
74+
pub size_separator: Option<SizeSeparator>,
7275
pub permission: PermissionFlag,
7376
pub sorting: Sorting,
7477
pub total_size: TotalSize,
@@ -95,6 +98,7 @@ impl Flags {
9598
display: Display::configure_from(cli, config),
9699
layout: Layout::configure_from(cli, config),
97100
size: SizeFlag::configure_from(cli, config),
101+
size_separator: Some(SizeSeparator::configure_from(cli, config)),
98102
permission: PermissionFlag::configure_from(cli, config),
99103
display_indicators: Indicators::configure_from(cli, config),
100104
icons: Icons::configure_from(cli, config),

src/flags/size.rs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ pub enum SizeFlag {
1919
Short,
2020
/// The variant to show file size in bytes.
2121
Bytes,
22-
/// The variant to show file size in bytes using a locale-specific separator.
23-
BytesWithSeparator,
2422
}
2523

2624
impl SizeFlag {
@@ -29,7 +27,6 @@ impl SizeFlag {
2927
"default" => Self::Default,
3028
"short" => Self::Short,
3129
"bytes" => Self::Bytes,
32-
"bytes-with-separator" => Self::BytesWithSeparator,
3330
// Invalid value should be handled by `clap` when building an `Cli`
3431
other => unreachable!("Invalid value '{other}' for 'size'"),
3532
}
@@ -107,13 +104,6 @@ mod test {
107104
assert_eq!(Some(SizeFlag::Bytes), SizeFlag::from_cli(&cli));
108105
}
109106

110-
#[test]
111-
fn test_from_cli_bytes_with_separator() {
112-
let argv = ["lsd", "--size", "bytes-with-separator"];
113-
let cli = Cli::try_parse_from(argv).unwrap();
114-
assert_eq!(Some(SizeFlag::BytesWithSeparator), SizeFlag::from_cli(&cli));
115-
}
116-
117107
#[test]
118108
#[should_panic]
119109
fn test_from_cli_unknown() {

src/flags/size_separator.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//! This module defines the [SizeSeparator]. 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+
use num_format::Locale;
11+
use serde::Deserialize;
12+
13+
/// The flag showing which separator to use for file sizes.
14+
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Default)]
15+
pub struct SizeSeparator {
16+
locale: Option<String>,
17+
}
18+
19+
impl SizeSeparator {
20+
pub fn new(locale: Option<String>) -> Self {
21+
Self { locale }
22+
}
23+
24+
pub fn get_locale(&self) -> Option<Locale> {
25+
self.locale.as_ref().and_then(|s| {
26+
Locale::from_name(s).ok().or_else(|| {
27+
print_error!("Invalid locale: {s}");
28+
None
29+
})
30+
})
31+
}
32+
}
33+
34+
impl Configurable<Self> for SizeSeparator {
35+
fn from_cli(cli: &Cli) -> Option<Self> {
36+
cli.size_separator
37+
.as_ref()
38+
.map(|s| Self::new(Some(s.clone())))
39+
}
40+
41+
fn from_config(config: &Config) -> Option<Self> {
42+
config
43+
.size_separator
44+
.as_ref()
45+
.map(|s| Self::new(Some(s.clone())))
46+
}
47+
}
48+
49+
#[cfg(test)]
50+
mod test {
51+
use clap::Parser;
52+
53+
use super::SizeSeparator;
54+
55+
use crate::app::Cli;
56+
use crate::flags::Configurable;
57+
58+
#[test]
59+
fn test_from_cli_bytes_with_separator() {
60+
let argv = ["lsd", "--size-separator", "en"];
61+
let cli = Cli::try_parse_from(argv).unwrap();
62+
assert_eq!(
63+
Some(SizeSeparator::new(Some("en".to_string()))),
64+
SizeSeparator::from_cli(&cli)
65+
);
66+
}
67+
}

src/meta/size.rs

Lines changed: 45 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use crate::color::{ColoredString, Colors, Elem};
2+
use crate::flags::size_separator::SizeSeparator;
23
use crate::flags::{Flags, SizeFlag};
3-
use num_format::{Locale, ToFormattedString as _};
4+
5+
use num_format::ToFormattedString as _;
46
use std::fs::Metadata;
57

68
const KB: u64 = 1024;
@@ -37,36 +39,28 @@ impl Size {
3739
self.bytes
3840
}
3941

40-
#[cfg(windows)]
41-
fn format_bytes_with_separator(&self) -> String {
42-
self.bytes.to_formatted_string(&Locale::en)
43-
}
44-
45-
#[cfg(not(windows))]
46-
fn format_bytes_with_separator(&self) -> String {
47-
use num_format::SystemLocale;
48-
49-
if let Ok(system_locale) = SystemLocale::default() {
50-
self.bytes.to_formatted_string(&system_locale)
42+
fn format_size(&self, number: f64, separator: SizeSeparator) -> String {
43+
let formatted = if let Some(locale) = separator.get_locale() {
44+
// Convert to integer for thousands separator formatting
45+
let int_number = number as u64;
46+
int_number.to_formatted_string(&locale)
5147
} else {
52-
self.bytes.to_formatted_string(&Locale::en)
53-
}
48+
format!("{0:.1$}", number, if number < 10.0 { 1 } else { 0 })
49+
};
50+
51+
formatted
5452
}
5553

56-
fn format_bytes(&self, flags: &Flags) -> String {
57-
if flags.size == SizeFlag::BytesWithSeparator {
58-
self.format_bytes_with_separator()
54+
fn format_bytes(&self, separator: SizeSeparator) -> String {
55+
if let Some(locale) = separator.get_locale() {
56+
self.bytes.to_formatted_string(&locale)
5957
} else {
6058
self.bytes.to_string()
6159
}
6260
}
6361

64-
fn format_size(&self, number: f64) -> String {
65-
format!("{0:.1$}", number, if number < 10.0 { 1 } else { 0 })
66-
}
67-
6862
fn get_unit(&self, flags: &Flags) -> Unit {
69-
if matches!(flags.size, SizeFlag::Bytes | SizeFlag::BytesWithSeparator) {
63+
if flags.size == SizeFlag::Bytes {
7064
return Unit::Byte;
7165
}
7266

@@ -135,11 +129,29 @@ impl Size {
135129
let unit = self.get_unit(flags);
136130

137131
match unit {
138-
Unit::Byte => self.format_bytes(flags),
139-
Unit::Kilo => self.format_size(((self.bytes as f64 / KB as f64) * 10.0).round() / 10.0),
140-
Unit::Mega => self.format_size(((self.bytes as f64 / MB as f64) * 10.0).round() / 10.0),
141-
Unit::Giga => self.format_size(((self.bytes as f64 / GB as f64) * 10.0).round() / 10.0),
142-
Unit::Tera => self.format_size(((self.bytes as f64 / TB as f64) * 10.0).round() / 10.0),
132+
Unit::Byte => {
133+
if let Some(separator) = &flags.size_separator {
134+
self.format_bytes(separator.clone())
135+
} else {
136+
self.format_bytes(SizeSeparator::default())
137+
}
138+
}
139+
Unit::Kilo => self.format_size(
140+
((self.bytes as f64 / KB as f64) * 10.0).round() / 10.0,
141+
SizeSeparator::default(),
142+
),
143+
Unit::Mega => self.format_size(
144+
((self.bytes as f64 / MB as f64) * 10.0).round() / 10.0,
145+
SizeSeparator::default(),
146+
),
147+
Unit::Giga => self.format_size(
148+
((self.bytes as f64 / GB as f64) * 10.0).round() / 10.0,
149+
SizeSeparator::default(),
150+
),
151+
Unit::Tera => self.format_size(
152+
((self.bytes as f64 / TB as f64) * 10.0).round() / 10.0,
153+
SizeSeparator::default(),
154+
),
143155
}
144156
}
145157

@@ -167,7 +179,7 @@ impl Size {
167179
Unit::Giga => String::from('G'),
168180
Unit::Tera => String::from('T'),
169181
},
170-
SizeFlag::Bytes | SizeFlag::BytesWithSeparator => String::from(""),
182+
SizeFlag::Bytes => String::from(""),
171183
}
172184
}
173185
}
@@ -176,6 +188,7 @@ impl Size {
176188
mod test {
177189
use super::{Size, GB, KB, MB, TB};
178190
use crate::color::{Colors, ThemeOption};
191+
use crate::flags::size_separator::SizeSeparator;
179192
use crate::flags::{Flags, SizeFlag};
180193

181194
#[test]
@@ -204,7 +217,8 @@ mod test {
204217
assert_eq!(size.value_string(&flags).as_str(), "44040192");
205218
assert_eq!(size.unit_string(&flags).as_str(), "");
206219

207-
flags.size = SizeFlag::BytesWithSeparator;
220+
flags.size = SizeFlag::Bytes;
221+
flags.size_separator = Some(SizeSeparator::new(Some("en".to_string())));
208222
assert_eq!(size.value_string(&flags).as_str(), "44,040,192");
209223
assert_eq!(size.unit_string(&flags).as_str(), "");
210224
}
@@ -224,7 +238,8 @@ mod test {
224238
assert_eq!(size.value_string(&flags).as_str(), "44040192");
225239
assert_eq!(size.unit_string(&flags).as_str(), "");
226240

227-
flags.size = SizeFlag::BytesWithSeparator;
241+
flags.size = SizeFlag::Bytes;
242+
flags.size_separator = Some(SizeSeparator::new(Some("en".to_string())));
228243
assert_eq!(size.value_string(&flags).as_str(), "44,040,192");
229244
assert_eq!(size.unit_string(&flags).as_str(), "");
230245
}

0 commit comments

Comments
 (0)