1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 03:27:44 +00:00

numfmt: implement --delimiter

closes #1454
This commit is contained in:
Daniel Rocco 2021-03-15 11:20:33 -04:00
parent 02e9ffecdd
commit 52f2ab6898
4 changed files with 150 additions and 6 deletions

View file

@ -226,12 +226,31 @@ fn format_string(
}) })
} }
/// Format a line of text according to the selected options. fn format_and_print_delimited(s: &str, options: &NumfmtOptions) -> Result<()> {
/// let delimiter = options.delimiter.as_ref().unwrap();
/// Given a line of text `s`, split the line into fields, transform and format
/// any selected numeric fields, and print the result to stdout. Fields not for (n, field) in (1..).zip(s.split(delimiter)) {
/// selected for conversion are passed through unmodified. let field_selected = uucore::ranges::contain(&options.fields, n);
pub fn format_and_print(s: &str, options: &NumfmtOptions) -> Result<()> {
// print delimiter before second and subsequent fields
if n > 1 {
print!("{}", delimiter);
}
if field_selected {
print!("{}", format_string(&field.trim_start(), options, None)?);
} else {
// print unselected field without conversion
print!("{}", field);
}
}
println!();
Ok(())
}
fn format_and_print_whitespace(s: &str, options: &NumfmtOptions) -> Result<()> {
for (n, (prefix, field)) in (1..).zip(WhitespaceSplitter { s: Some(s) }) { for (n, (prefix, field)) in (1..).zip(WhitespaceSplitter { s: Some(s) }) {
let field_selected = uucore::ranges::contain(&options.fields, n); let field_selected = uucore::ranges::contain(&options.fields, n);
@ -263,3 +282,15 @@ pub fn format_and_print(s: &str, options: &NumfmtOptions) -> Result<()> {
Ok(()) Ok(())
} }
/// Format a line of text according to the selected options.
///
/// Given a line of text `s`, split the line into fields, transform and format
/// any selected numeric fields, and print the result to stdout. Fields not
/// selected for conversion are passed through unmodified.
pub fn format_and_print(s: &str, options: &NumfmtOptions) -> Result<()> {
match &options.delimiter {
Some(_) => format_and_print_delimited(s, options),
None => format_and_print_whitespace(s, options),
}
}

View file

@ -128,11 +128,20 @@ fn parse_options(args: &ArgMatches) -> Result<NumfmtOptions> {
None => unreachable!(), None => unreachable!(),
}; };
let delimiter = args.value_of(options::DELIMITER).map_or(Ok(None), |arg| {
if arg.len() == 1 {
Ok(Some(arg.to_string()))
} else {
Err("the delimiter must be a single character".to_string())
}
})?;
Ok(NumfmtOptions { Ok(NumfmtOptions {
transform, transform,
padding, padding,
header, header,
fields, fields,
delimiter,
}) })
} }
@ -145,6 +154,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.usage(&usage[..]) .usage(&usage[..])
.after_help(LONG_HELP) .after_help(LONG_HELP)
.setting(AppSettings::AllowNegativeNumbers) .setting(AppSettings::AllowNegativeNumbers)
.arg(
Arg::with_name(options::DELIMITER)
.short("d")
.long(options::DELIMITER)
.value_name("X")
.help("use X instead of whitespace for field delimiter"),
)
.arg( .arg(
Arg::with_name(options::FIELD) Arg::with_name(options::FIELD)
.long(options::FIELD) .long(options::FIELD)

View file

@ -1,6 +1,7 @@
use crate::units::Transform; use crate::units::Transform;
use uucore::ranges::Range; use uucore::ranges::Range;
pub const DELIMITER: &str = "delimiter";
pub const FIELD: &str = "field"; pub const FIELD: &str = "field";
pub const FIELD_DEFAULT: &str = "1"; pub const FIELD_DEFAULT: &str = "1";
pub const FROM: &str = "from"; pub const FROM: &str = "from";
@ -22,4 +23,5 @@ pub struct NumfmtOptions {
pub padding: isize, pub padding: isize,
pub header: usize, pub header: usize,
pub fields: Vec<Range>, pub fields: Vec<Range>,
pub delimiter: Option<String>,
} }

View file

@ -383,3 +383,98 @@ fn test_field_df_example() {
.succeeds() .succeeds()
.stdout_is_fixture("df_expected.txt"); .stdout_is_fixture("df_expected.txt");
} }
#[test]
fn test_delimiter_must_not_be_empty() {
new_ucmd!().args(&["-d"]).fails();
}
#[test]
fn test_delimiter_must_not_be_more_than_one_character() {
new_ucmd!()
.args(&["--delimiter", "sad"])
.fails()
.stderr_is("numfmt: the delimiter must be a single character");
}
#[test]
fn test_delimiter_only() {
new_ucmd!()
.args(&["-d", ","])
.pipe_in("1234,56")
.succeeds()
.stdout_only("1234,56\n");
}
#[test]
fn test_line_is_field_with_no_delimiter() {
new_ucmd!()
.args(&["-d,", "--to=iec"])
.pipe_in("123456")
.succeeds()
.stdout_only("121K\n");
}
#[test]
fn test_delimiter_to_si() {
new_ucmd!()
.args(&["-d=,", "--to=si"])
.pipe_in("1234,56")
.succeeds()
.stdout_only("1.3K,56\n");
}
#[test]
fn test_delimiter_skips_leading_whitespace() {
new_ucmd!()
.args(&["-d=,", "--to=si"])
.pipe_in(" \t 1234,56")
.succeeds()
.stdout_only("1.3K,56\n");
}
#[test]
fn test_delimiter_preserves_leading_whitespace_in_unselected_fields() {
new_ucmd!()
.args(&["-d=|", "--to=si"])
.pipe_in(" 1000| 2000")
.succeeds()
.stdout_only("1.0K| 2000\n");
}
#[test]
fn test_delimiter_from_si() {
new_ucmd!()
.args(&["-d=,", "--from=si"])
.pipe_in("1.2K,56")
.succeeds()
.stdout_only("1200,56\n");
}
#[test]
fn test_delimiter_overrides_whitespace_separator() {
// GNU numfmt reports this as “invalid suffix”
new_ucmd!()
.args(&["-d,"])
.pipe_in("1 234,56")
.fails()
.stderr_is("numfmt: invalid number: 1 234\n");
}
#[test]
fn test_delimiter_with_padding() {
new_ucmd!()
.args(&["-d=|", "--to=si", "--padding=5"])
.pipe_in("1000|2000")
.succeeds()
.stdout_only(" 1.0K|2000\n");
}
#[test]
fn test_delimiter_with_padding_and_fields() {
new_ucmd!()
.args(&["-d=|", "--to=si", "--padding=5", "--field=-"])
.pipe_in("1000|2000")
.succeeds()
.stdout_only(" 1.0K| 2.0K\n");
}