mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 03:27:44 +00:00
numfmt: add --zero-terminated option
This commit is contained in:
parent
f1f3a5d9d2
commit
81911f9f6a
4 changed files with 90 additions and 4 deletions
|
@ -392,12 +392,20 @@ fn format_and_print_whitespace(s: &str, options: &NumfmtOptions) -> Result<()> {
|
||||||
|
|
||||||
print!("{}", format_string(field, options, implicit_padding)?);
|
print!("{}", format_string(field, options, implicit_padding)?);
|
||||||
} else {
|
} else {
|
||||||
|
// the -z option converts an initial \n into a space
|
||||||
|
let prefix = if options.zero_terminated && prefix.starts_with('\n') {
|
||||||
|
print!(" ");
|
||||||
|
&prefix[1..]
|
||||||
|
} else {
|
||||||
|
prefix
|
||||||
|
};
|
||||||
// print unselected field without conversion
|
// print unselected field without conversion
|
||||||
print!("{prefix}{field}");
|
print!("{prefix}{field}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println!();
|
let eol = if options.zero_terminated { '\0' } else { '\n' };
|
||||||
|
print!("{}", eol);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,8 @@ use crate::format::format_and_print;
|
||||||
use crate::options::*;
|
use crate::options::*;
|
||||||
use crate::units::{Result, Unit};
|
use crate::units::{Result, Unit};
|
||||||
use clap::{Arg, ArgAction, ArgMatches, Command, parser::ValueSource};
|
use clap::{Arg, ArgAction, ArgMatches, Command, parser::ValueSource};
|
||||||
use std::io::{BufRead, Write};
|
use std::io::{BufRead, Error, Write};
|
||||||
|
use std::result::Result as StdResult;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use units::{IEC_BASES, SI_BASES};
|
use units::{IEC_BASES, SI_BASES};
|
||||||
|
@ -38,10 +39,29 @@ fn handle_buffer<R>(input: R, options: &NumfmtOptions) -> UResult<()>
|
||||||
where
|
where
|
||||||
R: BufRead,
|
R: BufRead,
|
||||||
{
|
{
|
||||||
for (idx, line_result) in input.lines().by_ref().enumerate() {
|
if options.zero_terminated {
|
||||||
|
handle_buffer_iterator(
|
||||||
|
input
|
||||||
|
.split(0)
|
||||||
|
// FIXME: This panics on UTF8 decoding, but this util in general doesn't handle
|
||||||
|
// invalid UTF8
|
||||||
|
.map(|bytes| Ok(String::from_utf8(bytes?).unwrap())),
|
||||||
|
options,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
handle_buffer_iterator(input.lines(), options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_buffer_iterator(
|
||||||
|
iter: impl Iterator<Item = StdResult<String, Error>>,
|
||||||
|
options: &NumfmtOptions,
|
||||||
|
) -> UResult<()> {
|
||||||
|
let eol = if options.zero_terminated { '\0' } else { '\n' };
|
||||||
|
for (idx, line_result) in iter.enumerate() {
|
||||||
match line_result {
|
match line_result {
|
||||||
Ok(line) if idx < options.header => {
|
Ok(line) if idx < options.header => {
|
||||||
println!("{line}");
|
print!("{line}{eol}");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Ok(line) => format_and_handle_validation(line.as_ref(), options),
|
Ok(line) => format_and_handle_validation(line.as_ref(), options),
|
||||||
|
@ -217,6 +237,8 @@ fn parse_options(args: &ArgMatches) -> Result<NumfmtOptions> {
|
||||||
let invalid =
|
let invalid =
|
||||||
InvalidModes::from_str(args.get_one::<String>(options::INVALID).unwrap()).unwrap();
|
InvalidModes::from_str(args.get_one::<String>(options::INVALID).unwrap()).unwrap();
|
||||||
|
|
||||||
|
let zero_terminated = args.get_flag(options::ZERO_TERMINATED);
|
||||||
|
|
||||||
Ok(NumfmtOptions {
|
Ok(NumfmtOptions {
|
||||||
transform,
|
transform,
|
||||||
padding,
|
padding,
|
||||||
|
@ -227,6 +249,7 @@ fn parse_options(args: &ArgMatches) -> Result<NumfmtOptions> {
|
||||||
suffix,
|
suffix,
|
||||||
format,
|
format,
|
||||||
invalid,
|
invalid,
|
||||||
|
zero_terminated,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -366,6 +389,13 @@ pub fn uu_app() -> Command {
|
||||||
.value_parser(["abort", "fail", "warn", "ignore"])
|
.value_parser(["abort", "fail", "warn", "ignore"])
|
||||||
.value_name("INVALID"),
|
.value_name("INVALID"),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new(options::ZERO_TERMINATED)
|
||||||
|
.long(options::ZERO_TERMINATED)
|
||||||
|
.short('z')
|
||||||
|
.help("line delimiter is NUL, not newline")
|
||||||
|
.action(ArgAction::SetTrue),
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new(options::NUMBER)
|
Arg::new(options::NUMBER)
|
||||||
.hide(true)
|
.hide(true)
|
||||||
|
@ -406,6 +436,7 @@ mod tests {
|
||||||
suffix: None,
|
suffix: None,
|
||||||
format: FormatOptions::default(),
|
format: FormatOptions::default(),
|
||||||
invalid: InvalidModes::Abort,
|
invalid: InvalidModes::Abort,
|
||||||
|
zero_terminated: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ pub const TO: &str = "to";
|
||||||
pub const TO_DEFAULT: &str = "none";
|
pub const TO_DEFAULT: &str = "none";
|
||||||
pub const TO_UNIT: &str = "to-unit";
|
pub const TO_UNIT: &str = "to-unit";
|
||||||
pub const TO_UNIT_DEFAULT: &str = "1";
|
pub const TO_UNIT_DEFAULT: &str = "1";
|
||||||
|
pub const ZERO_TERMINATED: &str = "zero-terminated";
|
||||||
|
|
||||||
pub struct TransformOptions {
|
pub struct TransformOptions {
|
||||||
pub from: Unit,
|
pub from: Unit,
|
||||||
|
@ -52,6 +53,7 @@ pub struct NumfmtOptions {
|
||||||
pub suffix: Option<String>,
|
pub suffix: Option<String>,
|
||||||
pub format: FormatOptions,
|
pub format: FormatOptions,
|
||||||
pub invalid: InvalidModes,
|
pub invalid: InvalidModes,
|
||||||
|
pub zero_terminated: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
|
|
@ -1073,3 +1073,48 @@ fn test_format_grouping_conflicts_with_to_option() {
|
||||||
.fails_with_code(1)
|
.fails_with_code(1)
|
||||||
.stderr_contains("grouping cannot be combined with --to");
|
.stderr_contains("grouping cannot be combined with --to");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_zero_terminated_command_line_args() {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["--zero-terminated", "--to=si", "1000"])
|
||||||
|
.succeeds()
|
||||||
|
.stdout_is("1.0k\x00");
|
||||||
|
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["-z", "--to=si", "1000"])
|
||||||
|
.succeeds()
|
||||||
|
.stdout_is("1.0k\x00");
|
||||||
|
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["-z", "--to=si", "1000", "2000"])
|
||||||
|
.succeeds()
|
||||||
|
.stdout_is("1.0k\x002.0k\x00");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_zero_terminated_input() {
|
||||||
|
let values = vec![
|
||||||
|
("1000", "1.0k\x00"),
|
||||||
|
("1000\x00", "1.0k\x00"),
|
||||||
|
("1000\x002000\x00", "1.0k\x002.0k\x00"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (input, expected) in values {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["-z", "--to=si"])
|
||||||
|
.pipe_in(input)
|
||||||
|
.succeeds()
|
||||||
|
.stdout_is(expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_zero_terminated_embedded_newline() {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["-z", "--from=si", "--field=-"])
|
||||||
|
.pipe_in("1K\n2K\x003K\n4K\x00")
|
||||||
|
.succeeds()
|
||||||
|
// Newlines get replaced by a single space
|
||||||
|
.stdout_is("1000 2000\x003000 4000\x00");
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue