mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 03:27:44 +00:00
parent
02e9ffecdd
commit
52f2ab6898
4 changed files with 150 additions and 6 deletions
|
@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue