mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
Merge pull request #1026 from evestera/cat-numlines-notrailing
cat: fix for numbered lines w/ no trailing newline
This commit is contained in:
commit
daba29b832
3 changed files with 42 additions and 33 deletions
|
@ -292,6 +292,14 @@ fn write_fast(files: Vec<String>) -> CatResult<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// State that persists between output of each file
|
||||||
|
struct OutputState {
|
||||||
|
/// The current line number
|
||||||
|
line_number: usize,
|
||||||
|
|
||||||
|
/// Whether the output cursor is at the beginning of a new line
|
||||||
|
at_line_start: bool,
|
||||||
|
}
|
||||||
|
|
||||||
/// Writes files to stdout with `options` as configuration. Returns
|
/// Writes files to stdout with `options` as configuration. Returns
|
||||||
/// `Ok(())` if no errors were encountered, or an error with the
|
/// `Ok(())` if no errors were encountered, or an error with the
|
||||||
|
@ -302,18 +310,16 @@ fn write_fast(files: Vec<String>) -> CatResult<()> {
|
||||||
/// * `files` - There is no short circuit when encountiner an error
|
/// * `files` - There is no short circuit when encountiner an error
|
||||||
/// reading a file in this vector
|
/// reading a file in this vector
|
||||||
fn write_lines(files: Vec<String>, options: &OutputOptions) -> CatResult<()> {
|
fn write_lines(files: Vec<String>, options: &OutputOptions) -> CatResult<()> {
|
||||||
let mut line_counter: usize = 1;
|
|
||||||
let mut error_count = 0;
|
let mut error_count = 0;
|
||||||
|
let mut state = OutputState {
|
||||||
|
line_number: 1,
|
||||||
|
at_line_start: true,
|
||||||
|
};
|
||||||
|
|
||||||
for file in files {
|
for file in files {
|
||||||
let written = write_file_lines(&file[..], options, line_counter);
|
if let Err(error) = write_file_lines(&file, options, &mut state) {
|
||||||
line_counter += match written {
|
writeln!(&mut stderr(), "{}", error).context(&file[..])?;
|
||||||
Ok(lines) => lines,
|
error_count += 1;
|
||||||
Err(error) => {
|
|
||||||
writeln!(&mut stderr(), "{}", error).context(&file[..])?;
|
|
||||||
error_count += 1;
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -323,23 +329,16 @@ fn write_lines(files: Vec<String>, options: &OutputOptions) -> CatResult<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Outputs file and returns result with the number of lines to stdout
|
/// Outputs file contents to stdout in a linewise fashion,
|
||||||
/// from `file`. If line numbering is enabled, then start output
|
/// propagating any errors that might occur.
|
||||||
/// numbering at `line_number`.
|
fn write_file_lines(file: &str,
|
||||||
///
|
options: &OutputOptions,
|
||||||
/// # Arguments
|
state: &mut OutputState) -> CatResult<()> {
|
||||||
///
|
let mut handle = open(file)?;
|
||||||
/// * `files` - There is no short circuit when encountiner an error
|
|
||||||
/// reading a file in this vector
|
|
||||||
fn write_file_lines(file: &str, options: &OutputOptions, line_number: usize) -> CatResult<usize> {
|
|
||||||
let mut handle = open(&file[..])?;
|
|
||||||
let mut in_buf = [0; 1024 * 31];
|
let mut in_buf = [0; 1024 * 31];
|
||||||
let mut writer = BufWriter::with_capacity(1024 * 64, stdout());
|
let mut writer = BufWriter::with_capacity(1024 * 64, stdout());
|
||||||
let mut at_line_start = true;
|
|
||||||
let mut one_blank_kept = false;
|
let mut one_blank_kept = false;
|
||||||
|
|
||||||
let mut lines = 0;
|
|
||||||
|
|
||||||
while let Ok(n) = handle.reader.read(&mut in_buf) {
|
while let Ok(n) = handle.reader.read(&mut in_buf) {
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
break;
|
break;
|
||||||
|
@ -349,25 +348,25 @@ fn write_file_lines(file: &str, options: &OutputOptions, line_number: usize) ->
|
||||||
while pos < n {
|
while pos < n {
|
||||||
// skip empty line_number enumerating them if needed
|
// skip empty line_number enumerating them if needed
|
||||||
if in_buf[pos] == '\n' as u8 {
|
if in_buf[pos] == '\n' as u8 {
|
||||||
if !at_line_start || ! options.squeeze_blank || !one_blank_kept {
|
if !state.at_line_start || !options.squeeze_blank || !one_blank_kept {
|
||||||
one_blank_kept = true;
|
one_blank_kept = true;
|
||||||
if at_line_start && options.number == NumberingMode::NumberAll {
|
if state.at_line_start && options.number == NumberingMode::NumberAll {
|
||||||
write!(&mut writer, "{0:6}\t", line_number + lines)?;
|
write!(&mut writer, "{0:6}\t", state.line_number)?;
|
||||||
lines += 1;
|
state.line_number += 1;
|
||||||
}
|
}
|
||||||
writer.write_all(options.end_of_line.as_bytes())?;
|
writer.write_all(options.end_of_line.as_bytes())?;
|
||||||
if handle.is_interactive {
|
if handle.is_interactive {
|
||||||
writer.flush().context(&file[..])?;
|
writer.flush().context(&file[..])?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
at_line_start = true;
|
state.at_line_start = true;
|
||||||
pos += 1;
|
pos += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
one_blank_kept = false;
|
one_blank_kept = false;
|
||||||
if at_line_start && options.number != NumberingMode::NumberNone {
|
if state.at_line_start && options.number != NumberingMode::NumberNone {
|
||||||
write!(&mut writer, "{0:6}\t", line_number + lines)?;
|
write!(&mut writer, "{0:6}\t", state.line_number)?;
|
||||||
lines += 1;
|
state.line_number += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// print to end of line or end of buffer
|
// print to end of line or end of buffer
|
||||||
|
@ -380,7 +379,7 @@ fn write_file_lines(file: &str, options: &OutputOptions, line_number: usize) ->
|
||||||
};
|
};
|
||||||
// end of buffer?
|
// end of buffer?
|
||||||
if offset == 0 {
|
if offset == 0 {
|
||||||
at_line_start = false;
|
state.at_line_start = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// print suitable end of line
|
// print suitable end of line
|
||||||
|
@ -388,12 +387,12 @@ fn write_file_lines(file: &str, options: &OutputOptions, line_number: usize) ->
|
||||||
if handle.is_interactive {
|
if handle.is_interactive {
|
||||||
writer.flush()?;
|
writer.flush()?;
|
||||||
}
|
}
|
||||||
at_line_start = true;
|
state.at_line_start = true;
|
||||||
pos += offset;
|
pos += offset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(lines)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// write***_to_end methods
|
// write***_to_end methods
|
||||||
|
|
1
tests/fixtures/cat/nonewline.txt
vendored
Normal file
1
tests/fixtures/cat/nonewline.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
text without a trailing newline
|
|
@ -22,6 +22,15 @@ fn test_output_multi_files_print_all_chars() {
|
||||||
pM-qM-rM-sM-tM-uM-vM-wM-xM-yM-zM-{M-|M-}M-~M-^?");
|
pM-qM-rM-sM-tM-uM-vM-wM-xM-yM-zM-{M-|M-}M-~M-^?");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_numbered_lines_no_trailing_newline() {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["nonewline.txt", "alpha.txt", "-n"])
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only(" 1\ttext without a trailing newlineabcde\n 2\tfghij\n \
|
||||||
|
3\tklmno\n 4\tpqrst\n 5\tuvwxyz\n");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_stdin_show_nonprinting() {
|
fn test_stdin_show_nonprinting() {
|
||||||
for same_param in vec!["-v", "--show-nonprinting"] {
|
for same_param in vec!["-v", "--show-nonprinting"] {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue