Skip to content

Commit 995c6dc

Browse files
FidelSchcakebaker
authored andcommitted
fix(numfmt): update line termination handling for consistency with GNU behavior
1 parent 8a7a778 commit 995c6dc

3 files changed

Lines changed: 97 additions & 88 deletions

File tree

src/uu/numfmt/src/format.rs

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,7 @@ pub fn write_formatted_with_delimiter<W: std::io::Write>(
473473
writer: &mut W,
474474
input: &[u8],
475475
options: &NumfmtOptions,
476+
eol: Option<u8>,
476477
) -> Result<()> {
477478
let delimiter = options.delimiter.as_deref().unwrap();
478479

@@ -497,12 +498,9 @@ pub fn write_formatted_with_delimiter<W: std::io::Write>(
497498
}
498499
}
499500

500-
let eol = if options.zero_terminated {
501-
b"\0"
502-
} else {
503-
b"\n"
504-
};
505-
writer.write_all(eol).unwrap();
501+
if let Some(eol) = eol {
502+
writer.write_all(&[eol]).unwrap();
503+
}
506504

507505
Ok(())
508506
}
@@ -511,6 +509,7 @@ pub fn write_formatted_with_whitespace<W: std::io::Write>(
511509
writer: &mut W,
512510
s: &str,
513511
options: &NumfmtOptions,
512+
eol: Option<u8>,
514513
) -> Result<()> {
515514
for (n, (prefix, field)) in (1..).zip(WhitespaceSplitter { s: Some(s) }) {
516515
let field_selected = uucore::ranges::contain(&options.fields, n);
@@ -548,12 +547,9 @@ pub fn write_formatted_with_whitespace<W: std::io::Write>(
548547
}
549548
}
550549

551-
let eol = if options.zero_terminated {
552-
b"\0"
553-
} else {
554-
b"\n"
555-
};
556-
writer.write_all(eol).unwrap();
550+
if let Some(eol) = eol {
551+
writer.write_all(&[eol]).unwrap();
552+
}
557553

558554
Ok(())
559555
}

src/uu/numfmt/src/numfmt.rs

Lines changed: 43 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ use crate::options::{
1414
use crate::units::{Result, Unit};
1515
use clap::{Arg, ArgAction, ArgMatches, Command, builder::ValueParser, parser::ValueSource};
1616
use std::ffi::OsString;
17-
use std::io::{BufRead, Error, Write as _, stderr};
18-
use std::result::Result as StdResult;
17+
use std::io::{BufRead, Write as _, stderr};
1918
use std::str::FromStr;
2019

2120
use units::{IEC_BASES, SI_BASES};
@@ -33,44 +32,59 @@ mod units;
3332

3433
fn handle_args<'a>(args: impl Iterator<Item = &'a [u8]>, options: &NumfmtOptions) -> UResult<()> {
3534
let mut stdout = std::io::stdout().lock();
35+
let terminator = if options.zero_terminated { 0u8 } else { b'\n' };
3636
for l in args {
37-
write_line(&mut stdout, l, options)?;
37+
write_line(&mut stdout, l, options, Some(terminator))?;
3838
}
3939
Ok(())
4040
}
4141

42-
fn handle_buffer<R>(input: R, options: &NumfmtOptions) -> UResult<()>
43-
where
44-
R: BufRead,
45-
{
42+
fn handle_buffer<R: BufRead>(mut input: R, options: &NumfmtOptions) -> UResult<()> {
4643
let terminator = if options.zero_terminated { 0u8 } else { b'\n' };
47-
handle_buffer_iterator(input.split(terminator), options, terminator)
48-
}
49-
50-
fn handle_buffer_iterator(
51-
iter: impl Iterator<Item = StdResult<Vec<u8>, Error>>,
52-
options: &NumfmtOptions,
53-
terminator: u8,
54-
) -> UResult<()> {
5544
let mut stdout = std::io::stdout().lock();
56-
for (idx, line_result) in iter.enumerate() {
57-
match line_result {
58-
Ok(line) if idx < options.header => {
59-
stdout.write_all(&line)?;
60-
stdout.write_all(&[terminator])?;
61-
Ok(())
45+
let mut buf = Vec::new();
46+
let mut idx = 0;
47+
48+
loop {
49+
buf.clear();
50+
let n = input
51+
.read_until(terminator, &mut buf)
52+
.map_err(|e| NumfmtError::IoError(e.to_string()))?;
53+
if n == 0 {
54+
break;
55+
}
56+
57+
let has_terminator = buf.last() == Some(&terminator);
58+
let line = if has_terminator {
59+
&buf[..buf.len() - 1]
60+
} else {
61+
&buf[..]
62+
};
63+
64+
// Emit the terminator only if the input line had one.
65+
// i.e. if the last line of the input does not end with a newline, we should not add one.
66+
let eol = has_terminator.then_some(terminator);
67+
68+
if idx < options.header {
69+
stdout.write_all(line)?;
70+
if let Some(t) = eol {
71+
stdout.write_all(&[t])?;
6272
}
63-
Ok(line) => write_line(&mut stdout, &line, options),
64-
Err(err) => return Err(Box::new(NumfmtError::IoError(err.to_string()))),
65-
}?;
73+
} else {
74+
write_line(&mut stdout, line, options, eol)?;
75+
}
76+
77+
idx += 1;
6678
}
79+
6780
Ok(())
6881
}
6982

7083
fn write_line<W: std::io::Write>(
7184
writer: &mut W,
7285
input_line: &[u8],
7386
options: &NumfmtOptions,
87+
eol: Option<u8>,
7488
) -> UResult<()> {
7589
// Read lines only up to null byte (as GNU does)
7690
let line = input_line
@@ -80,11 +94,11 @@ fn write_line<W: std::io::Write>(
8094
.collect::<Vec<u8>>();
8195

8296
let handled_line = if options.delimiter.is_some() {
83-
write_formatted_with_delimiter(writer, &line, options)
97+
write_formatted_with_delimiter(writer, &line, options, eol)
8498
} else {
8599
// Whitespace mode requires valid UTF-8
86100
match std::str::from_utf8(&line) {
87-
Ok(s) => write_formatted_with_whitespace(writer, s, options),
101+
Ok(s) => write_formatted_with_whitespace(writer, s, options, eol),
88102
Err(_) => Err(translate!("numfmt-error-invalid-input")),
89103
}
90104
};
@@ -104,12 +118,9 @@ fn write_line<W: std::io::Write>(
104118
}
105119
writer.write_all(input_line)?;
106120

107-
let eol = if options.zero_terminated {
108-
b"\0"
109-
} else {
110-
b"\n"
111-
};
112-
writer.write_all(eol)?;
121+
if let Some(eol) = eol {
122+
writer.write_all(&[eol])?;
123+
}
113124
}
114125

115126
Ok(())

0 commit comments

Comments
 (0)