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
5 changes: 4 additions & 1 deletion src/uu/fmt/src/fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,10 @@ fn process_file(
let p_stream = ParagraphStream::new(fmt_opts, &mut fp);
for para_result in p_stream {
match para_result {
Err(s) => {
Err(parasplit::ParagraphStreamError::IoError(e)) => {
return Err(e).map_err_context(|| translate!("fmt-error-read"));
}
Err(parasplit::ParagraphStreamError::NoFormatLine(s)) => {
ostream
.write_all(&s)
.map_err_context(|| translate!("fmt-error-failed-to-write-output"))?;
Expand Down
45 changes: 28 additions & 17 deletions src/uu/fmt/src/parasplit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,14 +251,14 @@ impl FileLines<'_> {
}

impl Iterator for FileLines<'_> {
type Item = Line;
type Item = Result<Line, std::io::Error>;

fn next(&mut self) -> Option<Line> {
fn next(&mut self) -> Option<Result<Line, std::io::Error>> {
let mut buf = Vec::new();
match self.reader.read_until(b'\n', &mut buf) {
Ok(0) => return None,
Ok(_) => {}
Err(_) => return None,
Err(e) => return Some(Err(e)),
}
if buf.ends_with(b"\n") {
buf.pop();
Expand All @@ -273,15 +273,15 @@ impl Iterator for FileLines<'_> {
// Err(true) indicates that this was a linebreak,
// which is important to know when detecting mail headers
if n.iter().all(|&b| is_fmt_whitespace_byte(b)) {
return Some(Line::NoFormatLine(Vec::new(), true));
return Some(Ok(Line::NoFormatLine(Vec::new(), true)));
}

let (pmatch, poffset) = self.match_prefix(&n[..]);

// if this line does not match the prefix,
// emit the line unprocessed and iterate again
if !pmatch {
return Some(Line::NoFormatLine(n, false));
return Some(Ok(Line::NoFormatLine(n, false)));
}

// if the line matches the prefix, but is blank after,
Expand All @@ -294,26 +294,26 @@ impl Iterator for FileLines<'_> {
.iter()
.all(|&b| is_fmt_whitespace_byte(b))
{
return Some(Line::NoFormatLine(n, false));
return Some(Ok(Line::NoFormatLine(n, false)));
}

// skip if this line matches the anti_prefix
// (NOTE definition of match_anti_prefix is TRUE if we should process)
if !self.match_anti_prefix(&n[..]) {
return Some(Line::NoFormatLine(n, false));
return Some(Ok(Line::NoFormatLine(n, false)));
}

// figure out the indent, prefix, and prefixindent ending points
let prefix_end = poffset + self.opts.prefix.as_ref().map_or(0, String::len);
let (indent_end, prefix_len, indent_len) = self.compute_indent(&n[..], prefix_end);

Some(Line::FormatLine(FileLine {
Some(Ok(Line::FormatLine(FileLine {
line: n,
indent_end,
prefix_indent_end: poffset,
indent_len,
prefix_len,
}))
})))
}
}

Expand Down Expand Up @@ -390,22 +390,33 @@ impl ParagraphStream<'_> {
}
}

pub enum ParagraphStreamError {
/// A line that should be passed through unformatted
NoFormatLine(Vec<u8>),
/// An IO error encountered while reading
IoError(std::io::Error),
}

impl Iterator for ParagraphStream<'_> {
type Item = Result<Paragraph, Vec<u8>>;
type Item = Result<Paragraph, ParagraphStreamError>;

#[allow(clippy::cognitive_complexity)]
fn next(&mut self) -> Option<Result<Paragraph, Vec<u8>>> {
fn next(&mut self) -> Option<Result<Paragraph, ParagraphStreamError>> {
// return a NoFormatLine in an Err; it should immediately be output
let noformat = match self.lines.peek()? {
Line::FormatLine(_) => false,
Line::NoFormatLine(_, _) => true,
Err(_) => {
let e = self.lines.next().unwrap().unwrap_err();
return Some(Err(ParagraphStreamError::IoError(e)));
}
Ok(Line::FormatLine(_)) => false,
Ok(Line::NoFormatLine(_, _)) => true,
};

// found a NoFormatLine, immediately dump it out
if noformat {
let (s, nm) = self.lines.next().unwrap().get_noformatline();
let (s, nm) = self.lines.next().unwrap().unwrap().get_noformatline();
self.next_mail = nm;
return Some(Err(s));
return Some(Err(ParagraphStreamError::NoFormatLine(s)));
}

// found a FormatLine, now build a paragraph
Expand All @@ -421,7 +432,7 @@ impl Iterator for ParagraphStream<'_> {

let mut in_mail = false;
let mut second_done = false; // for when we use crown or tagged mode
while let Some(Line::FormatLine(fl)) = self.lines.peek() {
while let Some(Ok(Line::FormatLine(fl))) = self.lines.peek() {
// peek ahead
// need to explicitly force fl out of scope before we can call self.lines.next()
if p_lines.is_empty() {
Expand Down Expand Up @@ -503,7 +514,7 @@ impl Iterator for ParagraphStream<'_> {
}
}

p_lines.push(self.lines.next().unwrap().get_formatline().line);
p_lines.push(self.lines.next().unwrap().unwrap().get_formatline().line);

// when we're in split-only mode, we never join lines, so stop here
if self.opts.split_only {
Expand Down
6 changes: 6 additions & 0 deletions tests/by-util/test_fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,12 @@ fn test_fmt_non_existent_file() {
.stderr_is("fmt: cannot open 'non-existing' for reading: No such file or directory\n");
}

#[test]
#[cfg(target_os = "linux")]
fn test_fmt_read_error() {
new_ucmd!().arg("/proc/self/mem").fails_with_code(1);
}

#[test]
fn test_fmt_invalid_goal() {
for param in ["-g", "--goal"] {
Expand Down
Loading