mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 03:27:44 +00:00
Merge pull request #7622 from blyxxyz/flush-bufwriters
Flush `BufWriter`s, clean up error reporting
This commit is contained in:
commit
8040305715
19 changed files with 173 additions and 63 deletions
|
@ -62,14 +62,6 @@ impl<'a> From<&'a OsString> for Delimiter<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stdout_writer() -> Box<dyn Write> {
|
|
||||||
if std::io::stdout().is_terminal() {
|
|
||||||
Box::new(stdout())
|
|
||||||
} else {
|
|
||||||
Box::new(BufWriter::new(stdout())) as Box<dyn Write>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn list_to_ranges(list: &str, complement: bool) -> Result<Vec<Range>, String> {
|
fn list_to_ranges(list: &str, complement: bool) -> Result<Vec<Range>, String> {
|
||||||
if complement {
|
if complement {
|
||||||
Range::from_list(list).map(|r| uucore::ranges::complement(&r))
|
Range::from_list(list).map(|r| uucore::ranges::complement(&r))
|
||||||
|
@ -78,10 +70,14 @@ fn list_to_ranges(list: &str, complement: bool) -> Result<Vec<Range>, String> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cut_bytes<R: Read>(reader: R, ranges: &[Range], opts: &Options) -> UResult<()> {
|
fn cut_bytes<R: Read, W: Write>(
|
||||||
|
reader: R,
|
||||||
|
out: &mut W,
|
||||||
|
ranges: &[Range],
|
||||||
|
opts: &Options,
|
||||||
|
) -> UResult<()> {
|
||||||
let newline_char = opts.line_ending.into();
|
let newline_char = opts.line_ending.into();
|
||||||
let mut buf_in = BufReader::new(reader);
|
let mut buf_in = BufReader::new(reader);
|
||||||
let mut out = stdout_writer();
|
|
||||||
let out_delim = opts.out_delimiter.unwrap_or(b"\t");
|
let out_delim = opts.out_delimiter.unwrap_or(b"\t");
|
||||||
|
|
||||||
let result = buf_in.for_byte_record(newline_char, |line| {
|
let result = buf_in.for_byte_record(newline_char, |line| {
|
||||||
|
@ -112,8 +108,9 @@ fn cut_bytes<R: Read>(reader: R, ranges: &[Range], opts: &Options) -> UResult<()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output delimiter is explicitly specified
|
// Output delimiter is explicitly specified
|
||||||
fn cut_fields_explicit_out_delim<R: Read, M: Matcher>(
|
fn cut_fields_explicit_out_delim<R: Read, W: Write, M: Matcher>(
|
||||||
reader: R,
|
reader: R,
|
||||||
|
out: &mut W,
|
||||||
matcher: &M,
|
matcher: &M,
|
||||||
ranges: &[Range],
|
ranges: &[Range],
|
||||||
only_delimited: bool,
|
only_delimited: bool,
|
||||||
|
@ -121,7 +118,6 @@ fn cut_fields_explicit_out_delim<R: Read, M: Matcher>(
|
||||||
out_delim: &[u8],
|
out_delim: &[u8],
|
||||||
) -> UResult<()> {
|
) -> UResult<()> {
|
||||||
let mut buf_in = BufReader::new(reader);
|
let mut buf_in = BufReader::new(reader);
|
||||||
let mut out = stdout_writer();
|
|
||||||
|
|
||||||
let result = buf_in.for_byte_record_with_terminator(newline_char, |line| {
|
let result = buf_in.for_byte_record_with_terminator(newline_char, |line| {
|
||||||
let mut fields_pos = 1;
|
let mut fields_pos = 1;
|
||||||
|
@ -197,15 +193,15 @@ fn cut_fields_explicit_out_delim<R: Read, M: Matcher>(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output delimiter is the same as input delimiter
|
// Output delimiter is the same as input delimiter
|
||||||
fn cut_fields_implicit_out_delim<R: Read, M: Matcher>(
|
fn cut_fields_implicit_out_delim<R: Read, W: Write, M: Matcher>(
|
||||||
reader: R,
|
reader: R,
|
||||||
|
out: &mut W,
|
||||||
matcher: &M,
|
matcher: &M,
|
||||||
ranges: &[Range],
|
ranges: &[Range],
|
||||||
only_delimited: bool,
|
only_delimited: bool,
|
||||||
newline_char: u8,
|
newline_char: u8,
|
||||||
) -> UResult<()> {
|
) -> UResult<()> {
|
||||||
let mut buf_in = BufReader::new(reader);
|
let mut buf_in = BufReader::new(reader);
|
||||||
let mut out = stdout_writer();
|
|
||||||
|
|
||||||
let result = buf_in.for_byte_record_with_terminator(newline_char, |line| {
|
let result = buf_in.for_byte_record_with_terminator(newline_char, |line| {
|
||||||
let mut fields_pos = 1;
|
let mut fields_pos = 1;
|
||||||
|
@ -268,14 +264,14 @@ fn cut_fields_implicit_out_delim<R: Read, M: Matcher>(
|
||||||
}
|
}
|
||||||
|
|
||||||
// The input delimiter is identical to `newline_char`
|
// The input delimiter is identical to `newline_char`
|
||||||
fn cut_fields_newline_char_delim<R: Read>(
|
fn cut_fields_newline_char_delim<R: Read, W: Write>(
|
||||||
reader: R,
|
reader: R,
|
||||||
|
out: &mut W,
|
||||||
ranges: &[Range],
|
ranges: &[Range],
|
||||||
newline_char: u8,
|
newline_char: u8,
|
||||||
out_delim: &[u8],
|
out_delim: &[u8],
|
||||||
) -> UResult<()> {
|
) -> UResult<()> {
|
||||||
let buf_in = BufReader::new(reader);
|
let buf_in = BufReader::new(reader);
|
||||||
let mut out = stdout_writer();
|
|
||||||
|
|
||||||
let segments: Vec<_> = buf_in.split(newline_char).filter_map(|x| x.ok()).collect();
|
let segments: Vec<_> = buf_in.split(newline_char).filter_map(|x| x.ok()).collect();
|
||||||
let mut print_delim = false;
|
let mut print_delim = false;
|
||||||
|
@ -299,19 +295,25 @@ fn cut_fields_newline_char_delim<R: Read>(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cut_fields<R: Read>(reader: R, ranges: &[Range], opts: &Options) -> UResult<()> {
|
fn cut_fields<R: Read, W: Write>(
|
||||||
|
reader: R,
|
||||||
|
out: &mut W,
|
||||||
|
ranges: &[Range],
|
||||||
|
opts: &Options,
|
||||||
|
) -> UResult<()> {
|
||||||
let newline_char = opts.line_ending.into();
|
let newline_char = opts.line_ending.into();
|
||||||
let field_opts = opts.field_opts.as_ref().unwrap(); // it is safe to unwrap() here - field_opts will always be Some() for cut_fields() call
|
let field_opts = opts.field_opts.as_ref().unwrap(); // it is safe to unwrap() here - field_opts will always be Some() for cut_fields() call
|
||||||
match field_opts.delimiter {
|
match field_opts.delimiter {
|
||||||
Delimiter::Slice(delim) if delim == [newline_char] => {
|
Delimiter::Slice(delim) if delim == [newline_char] => {
|
||||||
let out_delim = opts.out_delimiter.unwrap_or(delim);
|
let out_delim = opts.out_delimiter.unwrap_or(delim);
|
||||||
cut_fields_newline_char_delim(reader, ranges, newline_char, out_delim)
|
cut_fields_newline_char_delim(reader, out, ranges, newline_char, out_delim)
|
||||||
}
|
}
|
||||||
Delimiter::Slice(delim) => {
|
Delimiter::Slice(delim) => {
|
||||||
let matcher = ExactMatcher::new(delim);
|
let matcher = ExactMatcher::new(delim);
|
||||||
match opts.out_delimiter {
|
match opts.out_delimiter {
|
||||||
Some(out_delim) => cut_fields_explicit_out_delim(
|
Some(out_delim) => cut_fields_explicit_out_delim(
|
||||||
reader,
|
reader,
|
||||||
|
out,
|
||||||
&matcher,
|
&matcher,
|
||||||
ranges,
|
ranges,
|
||||||
field_opts.only_delimited,
|
field_opts.only_delimited,
|
||||||
|
@ -320,6 +322,7 @@ fn cut_fields<R: Read>(reader: R, ranges: &[Range], opts: &Options) -> UResult<(
|
||||||
),
|
),
|
||||||
None => cut_fields_implicit_out_delim(
|
None => cut_fields_implicit_out_delim(
|
||||||
reader,
|
reader,
|
||||||
|
out,
|
||||||
&matcher,
|
&matcher,
|
||||||
ranges,
|
ranges,
|
||||||
field_opts.only_delimited,
|
field_opts.only_delimited,
|
||||||
|
@ -331,6 +334,7 @@ fn cut_fields<R: Read>(reader: R, ranges: &[Range], opts: &Options) -> UResult<(
|
||||||
let matcher = WhitespaceMatcher {};
|
let matcher = WhitespaceMatcher {};
|
||||||
cut_fields_explicit_out_delim(
|
cut_fields_explicit_out_delim(
|
||||||
reader,
|
reader,
|
||||||
|
out,
|
||||||
&matcher,
|
&matcher,
|
||||||
ranges,
|
ranges,
|
||||||
field_opts.only_delimited,
|
field_opts.only_delimited,
|
||||||
|
@ -348,6 +352,12 @@ fn cut_files(mut filenames: Vec<String>, mode: &Mode) {
|
||||||
filenames.push("-".to_owned());
|
filenames.push("-".to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut out: Box<dyn Write> = if std::io::stdout().is_terminal() {
|
||||||
|
Box::new(stdout())
|
||||||
|
} else {
|
||||||
|
Box::new(BufWriter::new(stdout())) as Box<dyn Write>
|
||||||
|
};
|
||||||
|
|
||||||
for filename in &filenames {
|
for filename in &filenames {
|
||||||
if filename == "-" {
|
if filename == "-" {
|
||||||
if stdin_read {
|
if stdin_read {
|
||||||
|
@ -355,9 +365,9 @@ fn cut_files(mut filenames: Vec<String>, mode: &Mode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
show_if_err!(match mode {
|
show_if_err!(match mode {
|
||||||
Mode::Bytes(ranges, opts) => cut_bytes(stdin(), ranges, opts),
|
Mode::Bytes(ranges, opts) => cut_bytes(stdin(), &mut out, ranges, opts),
|
||||||
Mode::Characters(ranges, opts) => cut_bytes(stdin(), ranges, opts),
|
Mode::Characters(ranges, opts) => cut_bytes(stdin(), &mut out, ranges, opts),
|
||||||
Mode::Fields(ranges, opts) => cut_fields(stdin(), ranges, opts),
|
Mode::Fields(ranges, opts) => cut_fields(stdin(), &mut out, ranges, opts),
|
||||||
});
|
});
|
||||||
|
|
||||||
stdin_read = true;
|
stdin_read = true;
|
||||||
|
@ -376,14 +386,16 @@ fn cut_files(mut filenames: Vec<String>, mode: &Mode) {
|
||||||
.and_then(|file| {
|
.and_then(|file| {
|
||||||
match &mode {
|
match &mode {
|
||||||
Mode::Bytes(ranges, opts) | Mode::Characters(ranges, opts) => {
|
Mode::Bytes(ranges, opts) | Mode::Characters(ranges, opts) => {
|
||||||
cut_bytes(file, ranges, opts)
|
cut_bytes(file, &mut out, ranges, opts)
|
||||||
}
|
}
|
||||||
Mode::Fields(ranges, opts) => cut_fields(file, ranges, opts),
|
Mode::Fields(ranges, opts) => cut_fields(file, &mut out, ranges, opts),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
show_if_err!(out.flush().map_err_context(|| "write error".into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get delimiter and output delimiter from `-d`/`--delimiter` and `--output-delimiter` options respectively
|
// Get delimiter and output delimiter from `-d`/`--delimiter` and `--output-delimiter` options respectively
|
||||||
|
|
|
@ -633,7 +633,8 @@ fn write_traditional_output(
|
||||||
let mut writer: BufWriter<Box<dyn Write>> = BufWriter::new(if output_filename == "-" {
|
let mut writer: BufWriter<Box<dyn Write>> = BufWriter::new(if output_filename == "-" {
|
||||||
Box::new(stdout())
|
Box::new(stdout())
|
||||||
} else {
|
} else {
|
||||||
let file = File::create(output_filename).map_err_context(String::new)?;
|
let file = File::create(output_filename)
|
||||||
|
.map_err_context(|| output_filename.maybe_quote().to_string())?;
|
||||||
Box::new(file)
|
Box::new(file)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -673,8 +674,11 @@ fn write_traditional_output(
|
||||||
return Err(PtxError::DumbFormat.into());
|
return Err(PtxError::DumbFormat.into());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
writeln!(writer, "{output_line}").map_err_context(String::new)?;
|
writeln!(writer, "{output_line}").map_err_context(|| "write failed".into())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
writer.flush().map_err_context(|| "write failed".into())?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -115,9 +115,9 @@ fn reader_writer<
|
||||||
}),
|
}),
|
||||||
settings,
|
settings,
|
||||||
output,
|
output,
|
||||||
);
|
)?;
|
||||||
} else {
|
} else {
|
||||||
print_sorted(chunk.lines().iter(), settings, output);
|
print_sorted(chunk.lines().iter(), settings, output)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ReadResult::SortedTwoChunks([a, b]) => {
|
ReadResult::SortedTwoChunks([a, b]) => {
|
||||||
|
@ -138,9 +138,9 @@ fn reader_writer<
|
||||||
.map(|(line, _)| line),
|
.map(|(line, _)| line),
|
||||||
settings,
|
settings,
|
||||||
output,
|
output,
|
||||||
);
|
)?;
|
||||||
} else {
|
} else {
|
||||||
print_sorted(merged_iter.map(|(line, _)| line), settings, output);
|
print_sorted(merged_iter.map(|(line, _)| line), settings, output)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ReadResult::EmptyInput => {
|
ReadResult::EmptyInput => {
|
||||||
|
|
|
@ -25,7 +25,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use compare::Compare;
|
use compare::Compare;
|
||||||
use uucore::error::UResult;
|
use uucore::error::{FromIo, UResult};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
GlobalSettings, Output, SortError,
|
GlobalSettings, Output, SortError,
|
||||||
|
@ -278,12 +278,19 @@ impl FileMerger<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_all_to(mut self, settings: &GlobalSettings, out: &mut impl Write) -> UResult<()> {
|
fn write_all_to(mut self, settings: &GlobalSettings, out: &mut impl Write) -> UResult<()> {
|
||||||
while self.write_next(settings, out) {}
|
while self
|
||||||
|
.write_next(settings, out)
|
||||||
|
.map_err_context(|| "write failed".into())?
|
||||||
|
{}
|
||||||
drop(self.request_sender);
|
drop(self.request_sender);
|
||||||
self.reader_join_handle.join().unwrap()
|
self.reader_join_handle.join().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_next(&mut self, settings: &GlobalSettings, out: &mut impl Write) -> bool {
|
fn write_next(
|
||||||
|
&mut self,
|
||||||
|
settings: &GlobalSettings,
|
||||||
|
out: &mut impl Write,
|
||||||
|
) -> std::io::Result<bool> {
|
||||||
if let Some(file) = self.heap.peek() {
|
if let Some(file) = self.heap.peek() {
|
||||||
let prev = self.prev.replace(PreviousLine {
|
let prev = self.prev.replace(PreviousLine {
|
||||||
chunk: file.current_chunk.clone(),
|
chunk: file.current_chunk.clone(),
|
||||||
|
@ -303,12 +310,12 @@ impl FileMerger<'_> {
|
||||||
file.current_chunk.line_data(),
|
file.current_chunk.line_data(),
|
||||||
);
|
);
|
||||||
if cmp == Ordering::Equal {
|
if cmp == Ordering::Equal {
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
current_line.print(out, settings);
|
current_line.print(out, settings)
|
||||||
});
|
})?;
|
||||||
|
|
||||||
let was_last_line_for_file = file.current_chunk.lines().len() == file.line_idx + 1;
|
let was_last_line_for_file = file.current_chunk.lines().len() == file.line_idx + 1;
|
||||||
|
|
||||||
|
@ -335,7 +342,7 @@ impl FileMerger<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
!self.heap.is_empty()
|
Ok(!self.heap.is_empty())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ use std::str::Utf8Error;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::strip_errno;
|
use uucore::error::{FromIo, strip_errno};
|
||||||
use uucore::error::{UError, UResult, USimpleError, UUsageError, set_exit_code};
|
use uucore::error::{UError, UResult, USimpleError, UUsageError, set_exit_code};
|
||||||
use uucore::line_ending::LineEnding;
|
use uucore::line_ending::LineEnding;
|
||||||
use uucore::parser::parse_size::{ParseSizeError, Parser};
|
use uucore::parser::parse_size::{ParseSizeError, Parser};
|
||||||
|
@ -488,13 +488,14 @@ impl<'a> Line<'a> {
|
||||||
Self { line, index }
|
Self { line, index }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print(&self, writer: &mut impl Write, settings: &GlobalSettings) {
|
fn print(&self, writer: &mut impl Write, settings: &GlobalSettings) -> std::io::Result<()> {
|
||||||
if settings.debug {
|
if settings.debug {
|
||||||
self.print_debug(settings, writer).unwrap();
|
self.print_debug(settings, writer)?;
|
||||||
} else {
|
} else {
|
||||||
writer.write_all(self.line.as_bytes()).unwrap();
|
writer.write_all(self.line.as_bytes())?;
|
||||||
writer.write_all(&[settings.line_ending.into()]).unwrap();
|
writer.write_all(&[settings.line_ending.into()])?;
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes indicators for the selections this line matched. The original line content is NOT expected
|
/// Writes indicators for the selections this line matched. The original line content is NOT expected
|
||||||
|
@ -1852,11 +1853,19 @@ fn print_sorted<'a, T: Iterator<Item = &'a Line<'a>>>(
|
||||||
iter: T,
|
iter: T,
|
||||||
settings: &GlobalSettings,
|
settings: &GlobalSettings,
|
||||||
output: Output,
|
output: Output,
|
||||||
) {
|
) -> UResult<()> {
|
||||||
|
let output_name = output
|
||||||
|
.as_output_name()
|
||||||
|
.unwrap_or("standard output")
|
||||||
|
.to_owned();
|
||||||
|
let ctx = || format!("write failed: {}", output_name.maybe_quote());
|
||||||
|
|
||||||
let mut writer = output.into_write();
|
let mut writer = output.into_write();
|
||||||
for line in iter {
|
for line in iter {
|
||||||
line.print(&mut writer, settings);
|
line.print(&mut writer, settings).map_err_context(ctx)?;
|
||||||
}
|
}
|
||||||
|
writer.flush().map_err_context(ctx)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open(path: impl AsRef<OsStr>) -> UResult<Box<dyn Read + Send>> {
|
fn open(path: impl AsRef<OsStr>) -> UResult<Box<dyn Read + Send>> {
|
||||||
|
|
|
@ -164,6 +164,7 @@ fn buffer_tac_regex(
|
||||||
// After the loop terminates, write whatever bytes are remaining at
|
// After the loop terminates, write whatever bytes are remaining at
|
||||||
// the beginning of the buffer.
|
// the beginning of the buffer.
|
||||||
out.write_all(&data[0..following_line_start])?;
|
out.write_all(&data[0..following_line_start])?;
|
||||||
|
out.flush()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,6 +216,7 @@ fn buffer_tac(data: &[u8], before: bool, separator: &str) -> std::io::Result<()>
|
||||||
// After the loop terminates, write whatever bytes are remaining at
|
// After the loop terminates, write whatever bytes are remaining at
|
||||||
// the beginning of the buffer.
|
// the beginning of the buffer.
|
||||||
out.write_all(&data[0..following_line_start])?;
|
out.write_all(&data[0..following_line_start])?;
|
||||||
|
out.flush()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -319,7 +319,7 @@ impl BytesChunkBuffer {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn print(&self, mut writer: impl Write) -> UResult<()> {
|
pub fn print(&self, writer: &mut impl Write) -> UResult<()> {
|
||||||
for chunk in &self.chunks {
|
for chunk in &self.chunks {
|
||||||
writer.write_all(chunk.get_buffer())?;
|
writer.write_all(chunk.get_buffer())?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ use crate::text;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::collections::hash_map::Keys;
|
use std::collections::hash_map::Keys;
|
||||||
use std::fs::{File, Metadata};
|
use std::fs::{File, Metadata};
|
||||||
use std::io::{BufRead, BufReader, BufWriter, stdout};
|
use std::io::{BufRead, BufReader, BufWriter, Write, stdout};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use uucore::error::UResult;
|
use uucore::error::UResult;
|
||||||
|
|
||||||
|
@ -146,9 +146,9 @@ impl FileHandling {
|
||||||
self.header_printer.print(display_name.as_str());
|
self.header_printer.print(display_name.as_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
let stdout = stdout();
|
let mut writer = BufWriter::new(stdout().lock());
|
||||||
let writer = BufWriter::new(stdout.lock());
|
chunks.print(&mut writer)?;
|
||||||
chunks.print(writer)?;
|
writer.flush()?;
|
||||||
|
|
||||||
self.last.replace(path.to_owned());
|
self.last.replace(path.to_owned());
|
||||||
self.update_metadata(path, None);
|
self.update_metadata(path, None);
|
||||||
|
|
|
@ -410,8 +410,7 @@ fn bounded_tail(file: &mut File, settings: &Settings) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unbounded_tail<T: Read>(reader: &mut BufReader<T>, settings: &Settings) -> UResult<()> {
|
fn unbounded_tail<T: Read>(reader: &mut BufReader<T>, settings: &Settings) -> UResult<()> {
|
||||||
let stdout = stdout();
|
let mut writer = BufWriter::new(stdout().lock());
|
||||||
let mut writer = BufWriter::new(stdout.lock());
|
|
||||||
match &settings.mode {
|
match &settings.mode {
|
||||||
FilterMode::Lines(Signum::Negative(count), sep) => {
|
FilterMode::Lines(Signum::Negative(count), sep) => {
|
||||||
let mut chunks = chunks::LinesChunkBuffer::new(*sep, *count);
|
let mut chunks = chunks::LinesChunkBuffer::new(*sep, *count);
|
||||||
|
@ -470,6 +469,7 @@ fn unbounded_tail<T: Read>(reader: &mut BufReader<T>, settings: &Settings) -> UR
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
writer.flush()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ use std::{
|
||||||
io::{BufRead, Write},
|
io::{BufRead, Write},
|
||||||
ops::Not,
|
ops::Not,
|
||||||
};
|
};
|
||||||
use uucore::error::{UError, UResult, USimpleError};
|
use uucore::error::{FromIo, UError, UResult};
|
||||||
use uucore::show_warning;
|
use uucore::show_warning;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -669,12 +669,9 @@ where
|
||||||
let filtered = buf.iter().filter_map(|&c| translator.translate(c));
|
let filtered = buf.iter().filter_map(|&c| translator.translate(c));
|
||||||
output_buf.extend(filtered);
|
output_buf.extend(filtered);
|
||||||
|
|
||||||
if let Err(e) = output.write_all(&output_buf) {
|
output
|
||||||
return Err(USimpleError::new(
|
.write_all(&output_buf)
|
||||||
1,
|
.map_err_context(|| "write error".into())?;
|
||||||
format!("{}: write error: {}", uucore::util_name(), e),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.clear();
|
buf.clear();
|
||||||
output_buf.clear();
|
output_buf.clear();
|
||||||
|
|
|
@ -14,9 +14,9 @@ use operation::{
|
||||||
Sequence, SqueezeOperation, SymbolTranslator, TranslateOperation, translate_input,
|
Sequence, SqueezeOperation, SymbolTranslator, TranslateOperation, translate_input,
|
||||||
};
|
};
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::io::{BufWriter, stdin, stdout};
|
use std::io::{BufWriter, Write, stdin, stdout};
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::{UResult, USimpleError, UUsageError};
|
use uucore::error::{FromIo, UResult, USimpleError, UUsageError};
|
||||||
use uucore::fs::is_stdin_directory;
|
use uucore::fs::is_stdin_directory;
|
||||||
use uucore::{format_usage, help_about, help_section, help_usage, os_str_as_bytes, show};
|
use uucore::{format_usage, help_about, help_section, help_usage, os_str_as_bytes, show};
|
||||||
|
|
||||||
|
@ -110,9 +110,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
|
|
||||||
let stdin = stdin();
|
let stdin = stdin();
|
||||||
let mut locked_stdin = stdin.lock();
|
let mut locked_stdin = stdin.lock();
|
||||||
let stdout = stdout();
|
let mut buffered_stdout = BufWriter::new(stdout().lock());
|
||||||
let locked_stdout = stdout.lock();
|
|
||||||
let mut buffered_stdout = BufWriter::new(locked_stdout);
|
|
||||||
|
|
||||||
// According to the man page: translating only happens if deleting or if a second set is given
|
// According to the man page: translating only happens if deleting or if a second set is given
|
||||||
let translating = !delete_flag && sets.len() > 1;
|
let translating = !delete_flag && sets.len() > 1;
|
||||||
|
@ -155,6 +153,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let op = TranslateOperation::new(set1, set2)?;
|
let op = TranslateOperation::new(set1, set2)?;
|
||||||
translate_input(&mut locked_stdin, &mut buffered_stdout, op)?;
|
translate_input(&mut locked_stdin, &mut buffered_stdout, op)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buffered_stdout
|
||||||
|
.flush()
|
||||||
|
.map_err_context(|| "write error".into())?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -110,6 +110,7 @@ impl Uniq {
|
||||||
{
|
{
|
||||||
write_line_terminator!(writer, line_terminator)?;
|
write_line_terminator!(writer, line_terminator)?;
|
||||||
}
|
}
|
||||||
|
writer.flush().map_err_context(|| "write error".into())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,7 +227,7 @@ impl Uniq {
|
||||||
} else {
|
} else {
|
||||||
writer.write_all(line)
|
writer.write_all(line)
|
||||||
}
|
}
|
||||||
.map_err_context(|| "Failed to write line".to_string())?;
|
.map_err_context(|| "write error".to_string())?;
|
||||||
|
|
||||||
write_line_terminator!(writer, line_terminator)
|
write_line_terminator!(writer, line_terminator)
|
||||||
}
|
}
|
||||||
|
|
|
@ -375,3 +375,15 @@ fn test_output_delimiter_with_adjacent_ranges() {
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_only("ab:cd\n");
|
.stdout_only("ab:cd\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[test]
|
||||||
|
fn test_failed_write_is_reported() {
|
||||||
|
new_ucmd!()
|
||||||
|
.arg("-d=")
|
||||||
|
.arg("-f1")
|
||||||
|
.pipe_in("key=value")
|
||||||
|
.set_stdout(std::fs::File::create("/dev/full").unwrap())
|
||||||
|
.fails()
|
||||||
|
.stderr_is("cut: write error: No space left on device\n");
|
||||||
|
}
|
||||||
|
|
|
@ -163,3 +163,14 @@ fn test_format() {
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_only("\\xx {}{}{a}{}{}\n");
|
.stdout_only("\\xx {}{}{a}{}{}\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[test]
|
||||||
|
fn test_failed_write_is_reported() {
|
||||||
|
new_ucmd!()
|
||||||
|
.arg("-G")
|
||||||
|
.pipe_in("hello")
|
||||||
|
.set_stdout(std::fs::File::create("/dev/full").unwrap())
|
||||||
|
.fails()
|
||||||
|
.stderr_is("ptx: write failed: No space left on device\n");
|
||||||
|
}
|
||||||
|
|
|
@ -1335,3 +1335,13 @@ fn test_human_blocks_r_and_q() {
|
||||||
fn test_args_check_conflict() {
|
fn test_args_check_conflict() {
|
||||||
new_ucmd!().arg("-c").arg("-C").fails();
|
new_ucmd!().arg("-c").arg("-C").fails();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[test]
|
||||||
|
fn test_failed_write_is_reported() {
|
||||||
|
new_ucmd!()
|
||||||
|
.pipe_in("hello")
|
||||||
|
.set_stdout(std::fs::File::create("/dev/full").unwrap())
|
||||||
|
.fails()
|
||||||
|
.stderr_is("sort: write failed: 'standard output': No space left on device\n");
|
||||||
|
}
|
||||||
|
|
|
@ -309,3 +309,13 @@ fn test_regex_before() {
|
||||||
// |--------||----||---|
|
// |--------||----||---|
|
||||||
.stdout_is("+---+c+d-e+--++b+-+a+");
|
.stdout_is("+---+c+d-e+--++b+-+a+");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[test]
|
||||||
|
fn test_failed_write_is_reported() {
|
||||||
|
new_ucmd!()
|
||||||
|
.pipe_in("hello")
|
||||||
|
.set_stdout(std::fs::File::create("/dev/full").unwrap())
|
||||||
|
.fails()
|
||||||
|
.stderr_is("tac: failed to write to stdout: No space left on device (os error 28)\n");
|
||||||
|
}
|
||||||
|
|
|
@ -4843,3 +4843,13 @@ fn test_child_when_run_with_stderr_to_stdout() {
|
||||||
.fails()
|
.fails()
|
||||||
.stdout_only(expected_stdout);
|
.stdout_only(expected_stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[test]
|
||||||
|
fn test_failed_write_is_reported() {
|
||||||
|
new_ucmd!()
|
||||||
|
.pipe_in("hello")
|
||||||
|
.set_stdout(std::fs::File::create("/dev/full").unwrap())
|
||||||
|
.fails()
|
||||||
|
.stderr_is("tail: No space left on device\n");
|
||||||
|
}
|
||||||
|
|
|
@ -1545,3 +1545,14 @@ fn test_non_digit_repeat() {
|
||||||
.fails()
|
.fails()
|
||||||
.stderr_only("tr: invalid repeat count 'c' in [c*n] construct\n");
|
.stderr_only("tr: invalid repeat count 'c' in [c*n] construct\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[test]
|
||||||
|
fn test_failed_write_is_reported() {
|
||||||
|
new_ucmd!()
|
||||||
|
.pipe_in("hello")
|
||||||
|
.args(&["e", "a"])
|
||||||
|
.set_stdout(std::fs::File::create("/dev/full").unwrap())
|
||||||
|
.fails()
|
||||||
|
.stderr_is("tr: write error: No space left on device\n");
|
||||||
|
}
|
||||||
|
|
|
@ -1181,3 +1181,14 @@ fn test_stdin_w1_multibyte() {
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is("à\ná\n");
|
.stdout_is("à\ná\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[test]
|
||||||
|
fn test_failed_write_is_reported() {
|
||||||
|
new_ucmd!()
|
||||||
|
.pipe_in("hello")
|
||||||
|
.args(&["-z"])
|
||||||
|
.set_stdout(std::fs::File::create("/dev/full").unwrap())
|
||||||
|
.fails()
|
||||||
|
.stderr_is("uniq: write error: No space left on device\n");
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue