Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 52 additions & 11 deletions src/uu/unexpand/src/unexpand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,16 @@
}

impl PrintState {
fn new() -> Self {
Self {
col: 0,
scol: 0,
leading: true,
pctype: CharType::Other,
pending_wide: Vec::new(),
}
}

// reinitializes the PrintState struct to beginning of line values
fn new_line(&mut self) {
self.col = 0;
Expand Down Expand Up @@ -643,31 +653,49 @@
options: &Options,
lastcol: usize,
tab_config: &TabConfig,
line_state: &mut PrintState,
) -> UResult<()> {
let mut input = open(file)?;
let mut print_state = PrintState {
col: 0,
scol: 0,
leading: true,
pctype: CharType::Other,
pending_wide: Vec::new(),
};
// Work on a local copy of the line state, so it can stay in registers
// while unexpanding, and hand it back when done with this file.

Check warning on line 660 in src/uu/unexpand/src/unexpand.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

WARNING: `cspell`: Unknown word 'unexpanding' (file:'src/uu/unexpand/src/unexpand.rs', line:660)
let mut print_state = std::mem::replace(line_state, PrintState::new());
let result = unexpand_input(
&mut input,
file,
output,
options,
lastcol,
tab_config,
&mut print_state,
);
*line_state = print_state;
result
}

/// Unexpands everything `input` yields, carrying the line state in `print_state`.

Check warning on line 675 in src/uu/unexpand/src/unexpand.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

WARNING: `cspell`: Unknown word 'Unexpands' (file:'src/uu/unexpand/src/unexpand.rs', line:675)
fn unexpand_input(
input: &mut impl BufRead,
file: &OsString,
output: &mut BufWriter<Stdout>,
options: &Options,
lastcol: usize,
tab_config: &TabConfig,
print_state: &mut PrintState,
) -> UResult<()> {
while let buf @ [_, ..] = input
.fill_buf()
.map_err(|e| e.map_err_context(|| file.maybe_quote().to_string()) as Box<dyn UError>)?
{
for line in buf.split_inclusive(|b| *b == b'\n') {
unexpand_buf(line, output, options, lastcol, tab_config, &mut print_state)?;
unexpand_buf(line, output, options, lastcol, tab_config, print_state)?;
if let Some(b'\n') = line.last() {
print_state.new_line();
}
}
let len = buf.len();
input.consume(len);
}
// write out anything remaining
write_tabs(output, tab_config, &mut print_state, options.aflag)
Ok(())
}

fn unexpand(options: &Options) -> UResult<()> {
Expand All @@ -682,12 +710,25 @@
0
};

// Input files form a single stream: a line that does not end in a
// newline continues in the next file.
let mut print_state = PrintState::new();

for file in &options.files {
if let Err(e) = unexpand_file(file, &mut output, options, lastcol, tab_config) {
if let Err(e) = unexpand_file(
file,
&mut output,
options,
lastcol,
tab_config,
&mut print_state,
) {
show!(e);
set_exit_code(1);
}
}
// write out anything remaining
write_tabs(&mut output, tab_config, &mut print_state, options.aflag)?;
output.flush()?;
Ok(())
}
Expand Down
38 changes: 38 additions & 0 deletions tests/by-util/test_unexpand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,3 +450,41 @@ fn test_buffered_read_edgecase_behaviour() {
.stdout_only(String::from_utf8(expected).unwrap());
}
}

#[test]
fn test_line_continues_into_next_file() {
// Neither first file ends in a newline, so its blanks and text continue
// the line in the next file.
let (at, mut ucmd) = at_and_ucmd!();
at.write("a", " ");
at.write("b", " x\n");
ucmd.args(&["a", "b"]).succeeds().stdout_is("\tx\n");

let (at, mut ucmd) = at_and_ucmd!();
at.write("c", "abcdef");
at.write("d", " x\n");
ucmd.args(&["-a", "c", "d"])
.succeeds()
.stdout_is("abcdef\tx\n");
}

#[test]
fn test_trailing_blanks_of_last_file_are_kept() {
let (at, mut ucmd) = at_and_ucmd!();
at.write("a", "x\n");
at.write("b", " ");
ucmd.args(&["a", "b"]).succeeds().stdout_is("x\n ");
}

#[test]
fn test_line_continues_past_unreadable_file() {
// A file that cannot be read does not end the line started before it.
let (at, mut ucmd) = at_and_ucmd!();
at.write("a", " ");
at.mkdir("dir");
at.write("b", " x\n");
ucmd.args(&["a", "dir", "b"])
.fails()
.stdout_is("\tx\n")
.stderr_contains("unexpand: dir: Is a directory");
}
Loading