1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

Merge branch 'master' into who_fix_runlevel

This commit is contained in:
Sylvestre Ledru 2021-05-23 09:32:37 +02:00 committed by GitHub
commit 7bf076505f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 558 additions and 235 deletions

View file

@ -16,7 +16,7 @@ path = "src/head.rs"
[dependencies] [dependencies]
clap = "2.33" clap = "2.33"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["ringbuffer"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]] [[bin]]

View file

@ -27,8 +27,12 @@ mod options {
pub const ZERO_NAME: &str = "ZERO"; pub const ZERO_NAME: &str = "ZERO";
pub const FILES_NAME: &str = "FILE"; pub const FILES_NAME: &str = "FILE";
} }
mod lines;
mod parse; mod parse;
mod split; mod split;
mod take;
use lines::zlines;
use take::take_all_but;
fn app<'a>() -> App<'a, 'a> { fn app<'a>() -> App<'a, 'a> {
App::new(executable!()) App::new(executable!())
@ -293,36 +297,22 @@ fn rbuf_but_last_n_bytes(input: &mut impl std::io::BufRead, n: usize) -> std::io
} }
fn rbuf_but_last_n_lines( fn rbuf_but_last_n_lines(
input: &mut impl std::io::BufRead, input: impl std::io::BufRead,
n: usize, n: usize,
zero: bool, zero: bool,
) -> std::io::Result<()> { ) -> std::io::Result<()> {
if n == 0 { if zero {
//prints everything
return rbuf_n_bytes(input, std::usize::MAX);
}
let mut ringbuf = vec![Vec::new(); n];
let stdout = std::io::stdout(); let stdout = std::io::stdout();
let mut stdout = stdout.lock(); let mut stdout = stdout.lock();
let mut line = Vec::new(); for bytes in take_all_but(zlines(input), n) {
let mut lines = 0usize; stdout.write_all(&bytes?)?;
split::walk_lines(input, zero, |e| match e {
split::Event::Data(dat) => {
line.extend_from_slice(dat);
Ok(true)
} }
split::Event::Line => {
if lines < n {
ringbuf[lines] = std::mem::replace(&mut line, Vec::new());
lines += 1;
} else { } else {
stdout.write_all(&ringbuf[0])?; for line in take_all_but(input.lines(), n) {
ringbuf.rotate_left(1); println!("{}", line?);
ringbuf[n - 1] = std::mem::replace(&mut line, Vec::new());
} }
Ok(true)
} }
}) Ok(())
} }
fn head_backwards_file(input: &mut std::fs::File, options: &HeadOptions) -> std::io::Result<()> { fn head_backwards_file(input: &mut std::fs::File, options: &HeadOptions) -> std::io::Result<()> {

73
src/uu/head/src/lines.rs Normal file
View file

@ -0,0 +1,73 @@
//! Iterate over zero-terminated lines.
use std::io::BufRead;
/// The zero byte, representing the null character.
const ZERO: u8 = 0;
/// Returns an iterator over the lines of the given reader.
///
/// The iterator returned from this function will yield instances of
/// [`io::Result`]<[`Vec`]<[`u8`]>>, representing the bytes of the line
/// *including* the null character (with the possible exception of the
/// last line, which may not have one).
///
/// # Examples
///
/// ```rust,ignore
/// use std::io::Cursor;
///
/// let cursor = Cursor::new(b"x\0y\0z\0");
/// let mut iter = zlines(cursor).map(|l| l.unwrap());
/// assert_eq!(iter.next(), Some(b"x\0".to_vec()));
/// assert_eq!(iter.next(), Some(b"y\0".to_vec()));
/// assert_eq!(iter.next(), Some(b"z\0".to_vec()));
/// assert_eq!(iter.next(), None);
/// ```
pub fn zlines<B>(buf: B) -> ZLines<B> {
ZLines { buf }
}
/// An iterator over the zero-terminated lines of an instance of `BufRead`.
pub struct ZLines<B> {
buf: B,
}
impl<B: BufRead> Iterator for ZLines<B> {
type Item = std::io::Result<Vec<u8>>;
fn next(&mut self) -> Option<std::io::Result<Vec<u8>>> {
let mut buf = Vec::new();
match self.buf.read_until(ZERO, &mut buf) {
Ok(0) => None,
Ok(_) => Some(Ok(buf)),
Err(e) => Some(Err(e)),
}
}
}
#[cfg(test)]
mod tests {
use crate::lines::zlines;
use std::io::Cursor;
#[test]
fn test_null_terminated() {
let cursor = Cursor::new(b"x\0y\0z\0");
let mut iter = zlines(cursor).map(|l| l.unwrap());
assert_eq!(iter.next(), Some(b"x\0".to_vec()));
assert_eq!(iter.next(), Some(b"y\0".to_vec()));
assert_eq!(iter.next(), Some(b"z\0".to_vec()));
assert_eq!(iter.next(), None);
}
#[test]
fn test_not_null_terminated() {
let cursor = Cursor::new(b"x\0y\0z");
let mut iter = zlines(cursor).map(|l| l.unwrap());
assert_eq!(iter.next(), Some(b"x\0".to_vec()));
assert_eq!(iter.next(), Some(b"y\0".to_vec()));
assert_eq!(iter.next(), Some(b"z".to_vec()));
assert_eq!(iter.next(), None);
}
}

93
src/uu/head/src/take.rs Normal file
View file

@ -0,0 +1,93 @@
//! Take all but the last elements of an iterator.
use uucore::ringbuffer::RingBuffer;
/// Create an iterator over all but the last `n` elements of `iter`.
///
/// # Examples
///
/// ```rust,ignore
/// let data = [1, 2, 3, 4, 5];
/// let n = 2;
/// let mut iter = take_all_but(data.iter(), n);
/// assert_eq!(Some(4), iter.next());
/// assert_eq!(Some(5), iter.next());
/// assert_eq!(None, iter.next());
/// ```
pub fn take_all_but<I: Iterator>(iter: I, n: usize) -> TakeAllBut<I> {
TakeAllBut::new(iter, n)
}
/// An iterator that only iterates over the last elements of another iterator.
pub struct TakeAllBut<I: Iterator> {
iter: I,
buf: RingBuffer<<I as Iterator>::Item>,
}
impl<I: Iterator> TakeAllBut<I> {
pub fn new(mut iter: I, n: usize) -> TakeAllBut<I> {
// Create a new ring buffer and fill it up.
//
// If there are fewer than `n` elements in `iter`, then we
// exhaust the iterator so that whenever `TakeAllBut::next()` is
// called, it will return `None`, as expected.
let mut buf = RingBuffer::new(n);
for _ in 0..n {
let value = match iter.next() {
None => {
break;
}
Some(x) => x,
};
buf.push_back(value);
}
TakeAllBut { iter, buf }
}
}
impl<I: Iterator> Iterator for TakeAllBut<I>
where
I: Iterator,
{
type Item = <I as Iterator>::Item;
fn next(&mut self) -> Option<<I as Iterator>::Item> {
match self.iter.next() {
Some(value) => self.buf.push_back(value),
None => None,
}
}
}
#[cfg(test)]
mod tests {
use crate::take::take_all_but;
#[test]
fn test_fewer_elements() {
let mut iter = take_all_but([0, 1, 2].iter(), 2);
assert_eq!(Some(&0), iter.next());
assert_eq!(None, iter.next());
}
#[test]
fn test_same_number_of_elements() {
let mut iter = take_all_but([0, 1].iter(), 2);
assert_eq!(None, iter.next());
}
#[test]
fn test_more_elements() {
let mut iter = take_all_but([0].iter(), 2);
assert_eq!(None, iter.next());
}
#[test]
fn test_zero_elements() {
let mut iter = take_all_but([0, 1, 2].iter(), 0);
assert_eq!(Some(&0), iter.next());
assert_eq!(Some(&1), iter.next());
assert_eq!(Some(&2), iter.next());
assert_eq!(None, iter.next());
}
}

View file

@ -223,9 +223,7 @@ fn read_to_buffer(
Err(e) if e.kind() == ErrorKind::Interrupted => { Err(e) if e.kind() == ErrorKind::Interrupted => {
// retry // retry
} }
Err(e) => { Err(e) => crash!(1, "{}", e),
crash!(1, "{}", e)
}
} }
} }
} }

View file

@ -64,6 +64,17 @@ static OPT_NUMERIC_SORT: &str = "numeric-sort";
static OPT_GENERAL_NUMERIC_SORT: &str = "general-numeric-sort"; static OPT_GENERAL_NUMERIC_SORT: &str = "general-numeric-sort";
static OPT_VERSION_SORT: &str = "version-sort"; static OPT_VERSION_SORT: &str = "version-sort";
static OPT_SORT: &str = "sort";
static ALL_SORT_MODES: &[&str] = &[
OPT_GENERAL_NUMERIC_SORT,
OPT_HUMAN_NUMERIC_SORT,
OPT_MONTH_SORT,
OPT_NUMERIC_SORT,
OPT_VERSION_SORT,
OPT_RANDOM,
];
static OPT_DICTIONARY_ORDER: &str = "dictionary-order"; static OPT_DICTIONARY_ORDER: &str = "dictionary-order";
static OPT_MERGE: &str = "merge"; static OPT_MERGE: &str = "merge";
static OPT_CHECK: &str = "check"; static OPT_CHECK: &str = "check";
@ -105,6 +116,7 @@ enum SortMode {
GeneralNumeric, GeneralNumeric,
Month, Month,
Version, Version,
Random,
Default, Default,
} }
#[derive(Clone)] #[derive(Clone)]
@ -122,7 +134,6 @@ pub struct GlobalSettings {
unique: bool, unique: bool,
check: bool, check: bool,
check_silent: bool, check_silent: bool,
random: bool,
salt: String, salt: String,
selectors: Vec<FieldSelector>, selectors: Vec<FieldSelector>,
separator: Option<char>, separator: Option<char>,
@ -191,7 +202,6 @@ impl Default for GlobalSettings {
unique: false, unique: false,
check: false, check: false,
check_silent: false, check_silent: false,
random: false,
salt: String::new(), salt: String::new(),
selectors: vec![], selectors: vec![],
separator: None, separator: None,
@ -209,7 +219,6 @@ struct KeySettings {
ignore_case: bool, ignore_case: bool,
dictionary_order: bool, dictionary_order: bool,
ignore_non_printing: bool, ignore_non_printing: bool,
random: bool,
reverse: bool, reverse: bool,
} }
@ -220,7 +229,6 @@ impl From<&GlobalSettings> for KeySettings {
ignore_blanks: settings.ignore_blanks, ignore_blanks: settings.ignore_blanks,
ignore_case: settings.ignore_case, ignore_case: settings.ignore_case,
ignore_non_printing: settings.ignore_non_printing, ignore_non_printing: settings.ignore_non_printing,
random: settings.random,
reverse: settings.reverse, reverse: settings.reverse,
dictionary_order: settings.dictionary_order, dictionary_order: settings.dictionary_order,
} }
@ -398,7 +406,7 @@ impl<'a> Line<'a> {
} }
} }
} }
if !(settings.random if !(settings.mode == SortMode::Random
|| settings.stable || settings.stable
|| settings.unique || settings.unique
|| !(settings.dictionary_order || !(settings.dictionary_order
@ -502,12 +510,10 @@ impl KeyPosition {
'h' => settings.mode = SortMode::HumanNumeric, 'h' => settings.mode = SortMode::HumanNumeric,
'i' => settings.ignore_non_printing = true, 'i' => settings.ignore_non_printing = true,
'n' => settings.mode = SortMode::Numeric, 'n' => settings.mode = SortMode::Numeric,
'R' => settings.random = true, 'R' => settings.mode = SortMode::Random,
'r' => settings.reverse = true, 'r' => settings.reverse = true,
'V' => settings.mode = SortMode::Version, 'V' => settings.mode = SortMode::Version,
c => { c => crash!(1, "invalid option for key: `{}`", c),
crash!(1, "invalid option for key: `{}`", c)
}
} }
// All numeric sorts and month sort conflict with dictionary_order and ignore_non_printing. // All numeric sorts and month sort conflict with dictionary_order and ignore_non_printing.
// Instad of reporting an error, let them overwrite each other. // Instad of reporting an error, let them overwrite each other.
@ -526,7 +532,9 @@ impl KeyPosition {
| SortMode::GeneralNumeric | SortMode::GeneralNumeric
| SortMode::Month => SortMode::Default, | SortMode::Month => SortMode::Default,
// Only SortMode::Default and SortMode::Version work with dictionary_order and ignore_non_printing // Only SortMode::Default and SortMode::Version work with dictionary_order and ignore_non_printing
m @ SortMode::Default | m @ SortMode::Version => m, m @ SortMode::Default
| m @ SortMode::Version
| m @ SortMode::Random => m,
} }
} }
_ => {} _ => {}
@ -720,6 +728,16 @@ With no FILE, or when FILE is -, read standard input.",
) )
} }
fn make_sort_mode_arg<'a, 'b>(mode: &'a str, short: &'b str, help: &'b str) -> Arg<'a, 'b> {
let mut arg = Arg::with_name(mode).short(short).long(mode).help(help);
for possible_mode in ALL_SORT_MODES {
if *possible_mode != mode {
arg = arg.conflicts_with(possible_mode);
}
}
arg
}
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args let args = args
.collect_str(InvalidEncodingHandling::Ignore) .collect_str(InvalidEncodingHandling::Ignore)
@ -732,34 +750,62 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.about(ABOUT) .about(ABOUT)
.usage(&usage[..]) .usage(&usage[..])
.arg( .arg(
Arg::with_name(OPT_HUMAN_NUMERIC_SORT) Arg::with_name(OPT_SORT)
.short("h") .long(OPT_SORT)
.long(OPT_HUMAN_NUMERIC_SORT) .takes_value(true)
.help("compare according to human readable sizes, eg 1M > 100k"), .possible_values(
&[
"general-numeric",
"human-numeric",
"month",
"numeric",
"version",
"random",
]
)
.conflicts_with_all(ALL_SORT_MODES)
) )
.arg( .arg(
Arg::with_name(OPT_MONTH_SORT) make_sort_mode_arg(
.short("M") OPT_HUMAN_NUMERIC_SORT,
.long(OPT_MONTH_SORT) "h",
.help("compare according to month name abbreviation"), "compare according to human readable sizes, eg 1M > 100k"
),
) )
.arg( .arg(
Arg::with_name(OPT_NUMERIC_SORT) make_sort_mode_arg(
.short("n") OPT_MONTH_SORT,
.long(OPT_NUMERIC_SORT) "M",
.help("compare according to string numerical value"), "compare according to month name abbreviation"
),
) )
.arg( .arg(
Arg::with_name(OPT_GENERAL_NUMERIC_SORT) make_sort_mode_arg(
.short("g") OPT_NUMERIC_SORT,
.long(OPT_GENERAL_NUMERIC_SORT) "n",
.help("compare according to string general numerical value"), "compare according to string numerical value"
),
) )
.arg( .arg(
Arg::with_name(OPT_VERSION_SORT) make_sort_mode_arg(
.short("V") OPT_GENERAL_NUMERIC_SORT,
.long(OPT_VERSION_SORT) "g",
.help("Sort by SemVer version number, eg 1.12.2 > 1.1.2"), "compare according to string general numerical value"
),
)
.arg(
make_sort_mode_arg(
OPT_VERSION_SORT,
"V",
"Sort by SemVer version number, eg 1.12.2 > 1.1.2",
),
)
.arg(
make_sort_mode_arg(
OPT_RANDOM,
"R",
"shuffle in random order",
),
) )
.arg( .arg(
Arg::with_name(OPT_DICTIONARY_ORDER) Arg::with_name(OPT_DICTIONARY_ORDER)
@ -813,12 +859,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.takes_value(true) .takes_value(true)
.value_name("FILENAME"), .value_name("FILENAME"),
) )
.arg(
Arg::with_name(OPT_RANDOM)
.short("R")
.long(OPT_RANDOM)
.help("shuffle in random order"),
)
.arg( .arg(
Arg::with_name(OPT_REVERSE) Arg::with_name(OPT_REVERSE)
.short("r") .short("r")
@ -925,16 +965,25 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.unwrap_or_default() .unwrap_or_default()
}; };
settings.mode = if matches.is_present(OPT_HUMAN_NUMERIC_SORT) { settings.mode = if matches.is_present(OPT_HUMAN_NUMERIC_SORT)
|| matches.value_of(OPT_SORT) == Some("human-numeric")
{
SortMode::HumanNumeric SortMode::HumanNumeric
} else if matches.is_present(OPT_MONTH_SORT) { } else if matches.is_present(OPT_MONTH_SORT) || matches.value_of(OPT_SORT) == Some("month") {
SortMode::Month SortMode::Month
} else if matches.is_present(OPT_GENERAL_NUMERIC_SORT) { } else if matches.is_present(OPT_GENERAL_NUMERIC_SORT)
|| matches.value_of(OPT_SORT) == Some("general-numeric")
{
SortMode::GeneralNumeric SortMode::GeneralNumeric
} else if matches.is_present(OPT_NUMERIC_SORT) { } else if matches.is_present(OPT_NUMERIC_SORT) || matches.value_of(OPT_SORT) == Some("numeric")
{
SortMode::Numeric SortMode::Numeric
} else if matches.is_present(OPT_VERSION_SORT) { } else if matches.is_present(OPT_VERSION_SORT) || matches.value_of(OPT_SORT) == Some("version")
{
SortMode::Version SortMode::Version
} else if matches.is_present(OPT_RANDOM) || matches.value_of(OPT_SORT) == Some("random") {
settings.salt = get_rand_string();
SortMode::Random
} else { } else {
SortMode::Default SortMode::Default
}; };
@ -978,11 +1027,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
settings.stable = matches.is_present(OPT_STABLE); settings.stable = matches.is_present(OPT_STABLE);
settings.unique = matches.is_present(OPT_UNIQUE); settings.unique = matches.is_present(OPT_UNIQUE);
if matches.is_present(OPT_RANDOM) {
settings.random = matches.is_present(OPT_RANDOM);
settings.salt = get_rand_string();
}
if files.is_empty() { if files.is_empty() {
/* if no file, default to stdin */ /* if no file, default to stdin */
files.push("-".to_owned()); files.push("-".to_owned());
@ -1110,10 +1154,8 @@ fn compare_by<'a>(a: &Line<'a>, b: &Line<'a>, global_settings: &GlobalSettings)
let b_str = b_selection.slice; let b_str = b_selection.slice;
let settings = &selector.settings; let settings = &selector.settings;
let cmp: Ordering = if settings.random { let cmp: Ordering = match settings.mode {
random_shuffle(a_str, b_str, &global_settings.salt) SortMode::Random => random_shuffle(a_str, b_str, &global_settings.salt),
} else {
match settings.mode {
SortMode::Numeric | SortMode::HumanNumeric => numeric_str_cmp( SortMode::Numeric | SortMode::HumanNumeric => numeric_str_cmp(
(a_str, a_selection.num_cache.as_ref().unwrap().as_num_info()), (a_str, a_selection.num_cache.as_ref().unwrap().as_num_info()),
(b_str, b_selection.num_cache.as_ref().unwrap().as_num_info()), (b_str, b_selection.num_cache.as_ref().unwrap().as_num_info()),
@ -1131,7 +1173,6 @@ fn compare_by<'a>(a: &Line<'a>, b: &Line<'a>, global_settings: &GlobalSettings)
settings.dictionary_order, settings.dictionary_order,
settings.ignore_case, settings.ignore_case,
), ),
}
}; };
if cmp != Ordering::Equal { if cmp != Ordering::Equal {
return if settings.reverse { cmp.reverse() } else { cmp }; return if settings.reverse { cmp.reverse() } else { cmp };
@ -1139,7 +1180,10 @@ fn compare_by<'a>(a: &Line<'a>, b: &Line<'a>, global_settings: &GlobalSettings)
} }
// Call "last resort compare" if all selectors returned Equal // Call "last resort compare" if all selectors returned Equal
let cmp = if global_settings.random || global_settings.stable || global_settings.unique { let cmp = if global_settings.mode == SortMode::Random
|| global_settings.stable
|| global_settings.unique
{
Ordering::Equal Ordering::Equal
} else { } else {
a.line.cmp(b.line) a.line.cmp(b.line)

View file

@ -25,7 +25,8 @@ static VERSION: &str = env!("CARGO_PKG_VERSION");
static ABOUT: &str = static ABOUT: &str =
"Run COMMAND, with modified buffering operations for its standard streams.\n\n\ "Run COMMAND, with modified buffering operations for its standard streams.\n\n\
Mandatory arguments to long options are mandatory for short options too."; Mandatory arguments to long options are mandatory for short options too.";
static LONG_HELP: &str = "If MODE is 'L' the corresponding stream will be line buffered.\n\ static LONG_HELP: &str =
"If MODE is 'L' the corresponding stream will be line buffered.\n\
This option is invalid with standard input.\n\n\ This option is invalid with standard input.\n\n\
If MODE is '0' the corresponding stream will be unbuffered.\n\n\ If MODE is '0' the corresponding stream will be unbuffered.\n\n\
Otherwise MODE is a number which may be followed by one of the following:\n\n\ Otherwise MODE is a number which may be followed by one of the following:\n\n\

View file

@ -17,7 +17,7 @@ path = "src/tail.rs"
[dependencies] [dependencies]
clap = "2.33" clap = "2.33"
libc = "0.2.42" libc = "0.2.42"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["ringbuffer"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
winapi = { version="0.3", features=["fileapi", "handleapi", "processthreadsapi", "synchapi", "winbase"] } winapi = { version="0.3", features=["fileapi", "handleapi", "processthreadsapi", "synchapi", "winbase"] }

View file

@ -1,61 +0,0 @@
//! A fixed-size ring buffer.
use std::collections::VecDeque;
/// A fixed-size ring buffer backed by a `VecDeque`.
///
/// If the ring buffer is not full, then calling the [`push_back`]
/// method appends elements, as in a [`VecDeque`]. If the ring buffer
/// is full, then calling [`push_back`] removes the element at the
/// front of the buffer (in a first-in, first-out manner) before
/// appending the new element to the back of the buffer.
///
/// Use [`from_iter`] to take the last `size` elements from an
/// iterator.
///
/// # Examples
///
/// After exceeding the size limit, the oldest elements are dropped in
/// favor of the newest element:
///
/// ```rust,ignore
/// let buffer: RingBuffer<u8> = RingBuffer::new(2);
/// buffer.push_back(0);
/// buffer.push_back(1);
/// buffer.push_back(2);
/// assert_eq!(vec![1, 2], buffer.data);
/// ```
///
/// Take the last `n` elements from an iterator:
///
/// ```rust,ignore
/// let iter = vec![0, 1, 2, 3].iter();
/// assert_eq!(vec![2, 3], RingBuffer::from_iter(iter, 2).data);
/// ```
pub struct RingBuffer<T> {
pub data: VecDeque<T>,
size: usize,
}
impl<T> RingBuffer<T> {
pub fn new(size: usize) -> RingBuffer<T> {
RingBuffer {
data: VecDeque::new(),
size,
}
}
pub fn from_iter(iter: impl Iterator<Item = T>, size: usize) -> RingBuffer<T> {
let mut ringbuf = RingBuffer::new(size);
for value in iter {
ringbuf.push_back(value);
}
ringbuf
}
pub fn push_back(&mut self, value: T) {
if self.size <= self.data.len() {
self.data.pop_front();
}
self.data.push_back(value)
}
}

View file

@ -17,9 +17,7 @@ extern crate uucore;
mod chunks; mod chunks;
mod platform; mod platform;
mod ringbuffer;
use chunks::ReverseChunks; use chunks::ReverseChunks;
use ringbuffer::RingBuffer;
use clap::{App, Arg}; use clap::{App, Arg};
use std::collections::VecDeque; use std::collections::VecDeque;
@ -30,6 +28,7 @@ use std::io::{stdin, stdout, BufRead, BufReader, Read, Seek, SeekFrom, Write};
use std::path::Path; use std::path::Path;
use std::thread::sleep; use std::thread::sleep;
use std::time::Duration; use std::time::Duration;
use uucore::ringbuffer::RingBuffer;
pub mod options { pub mod options {
pub mod verbosity { pub mod verbosity {

View file

@ -47,6 +47,7 @@ mode = ["libc"]
parse_time = [] parse_time = []
perms = ["libc"] perms = ["libc"]
process = ["libc"] process = ["libc"]
ringbuffer = []
signals = [] signals = []
utf8 = [] utf8 = []
utmpx = ["time", "libc"] utmpx = ["time", "libc"]

View file

@ -8,6 +8,8 @@ pub mod fs;
pub mod fsext; pub mod fsext;
#[cfg(feature = "parse_time")] #[cfg(feature = "parse_time")]
pub mod parse_time; pub mod parse_time;
#[cfg(feature = "ringbuffer")]
pub mod ringbuffer;
#[cfg(feature = "zero-copy")] #[cfg(feature = "zero-copy")]
pub mod zero_copy; pub mod zero_copy;

View file

@ -0,0 +1,134 @@
//! A fixed-size ring buffer.
use std::collections::VecDeque;
/// A fixed-size ring buffer backed by a `VecDeque`.
///
/// If the ring buffer is not full, then calling the [`push_back`]
/// method appends elements, as in a [`VecDeque`]. If the ring buffer
/// is full, then calling [`push_back`] removes the element at the
/// front of the buffer (in a first-in, first-out manner) before
/// appending the new element to the back of the buffer.
///
/// Use [`from_iter`] to take the last `size` elements from an
/// iterator.
///
/// # Examples
///
/// After exceeding the size limit, the oldest elements are dropped in
/// favor of the newest element:
///
/// ```rust,ignore
/// let mut buffer: RingBuffer<u8> = RingBuffer::new(2);
/// buffer.push_back(0);
/// buffer.push_back(1);
/// buffer.push_back(2);
/// assert_eq!(vec![1, 2], buffer.data);
/// ```
///
/// Take the last `n` elements from an iterator:
///
/// ```rust,ignore
/// let iter = [0, 1, 2].iter();
/// let actual = RingBuffer::from_iter(iter, 2).data;
/// let expected = VecDeque::from_iter([1, 2].iter());
/// assert_eq!(expected, actual);
/// ```
pub struct RingBuffer<T> {
pub data: VecDeque<T>,
size: usize,
}
impl<T> RingBuffer<T> {
pub fn new(size: usize) -> RingBuffer<T> {
RingBuffer {
data: VecDeque::new(),
size,
}
}
pub fn from_iter(iter: impl Iterator<Item = T>, size: usize) -> RingBuffer<T> {
let mut ringbuf = RingBuffer::new(size);
for value in iter {
ringbuf.push_back(value);
}
ringbuf
}
/// Append a value to the end of the ring buffer.
///
/// If the ring buffer is not full, this method return [`None`]. If
/// the ring buffer is full, appending a new element will cause the
/// oldest element to be evicted. In that case this method returns
/// that element, or `None`.
///
/// In the special case where the size limit is zero, each call to
/// this method with input `value` returns `Some(value)`, because
/// the input is immediately evicted.
///
/// # Examples
///
/// Appending an element when the buffer is full returns the oldest
/// element:
///
/// ```rust,ignore
/// let mut buf = RingBuffer::new(3);
/// assert_eq!(None, buf.push_back(0));
/// assert_eq!(None, buf.push_back(1));
/// assert_eq!(None, buf.push_back(2));
/// assert_eq!(Some(0), buf.push_back(3));
/// ```
///
/// If the size limit is zero, then this method always returns the
/// input value:
///
/// ```rust,ignore
/// let mut buf = RingBuffer::new(0);
/// assert_eq!(Some(0), buf.push_back(0));
/// assert_eq!(Some(1), buf.push_back(1));
/// assert_eq!(Some(2), buf.push_back(2));
/// ```
pub fn push_back(&mut self, value: T) -> Option<T> {
if self.size == 0 {
return Some(value);
}
let result = if self.size <= self.data.len() {
self.data.pop_front()
} else {
None
};
self.data.push_back(value);
result
}
}
#[cfg(test)]
mod tests {
use crate::ringbuffer::RingBuffer;
use std::collections::VecDeque;
use std::iter::FromIterator;
#[test]
fn test_size_limit_zero() {
let mut buf = RingBuffer::new(0);
assert_eq!(Some(0), buf.push_back(0));
assert_eq!(Some(1), buf.push_back(1));
assert_eq!(Some(2), buf.push_back(2));
}
#[test]
fn test_evict_oldest() {
let mut buf = RingBuffer::new(2);
assert_eq!(None, buf.push_back(0));
assert_eq!(None, buf.push_back(1));
assert_eq!(Some(0), buf.push_back(2));
}
#[test]
fn test_from_iter() {
let iter = [0, 1, 2].iter();
let actual = RingBuffer::from_iter(iter, 2).data;
let expected = VecDeque::from_iter([1, 2].iter());
assert_eq!(expected, actual);
}
}

View file

@ -39,6 +39,8 @@ pub use crate::features::fs;
pub use crate::features::fsext; pub use crate::features::fsext;
#[cfg(feature = "parse_time")] #[cfg(feature = "parse_time")]
pub use crate::features::parse_time; pub use crate::features::parse_time;
#[cfg(feature = "ringbuffer")]
pub use crate::features::ringbuffer;
#[cfg(feature = "zero-copy")] #[cfg(feature = "zero-copy")]
pub use crate::features::zero_copy; pub use crate::features::zero_copy;

View file

@ -129,6 +129,15 @@ fn test_zero_terminated_syntax_2() {
.stdout_is("x\0y"); .stdout_is("x\0y");
} }
#[test]
fn test_zero_terminated_negative_lines() {
new_ucmd!()
.args(&["-z", "-n", "-1"])
.pipe_in("x\0y\0z\0")
.run()
.stdout_is("x\0y\0");
}
#[test] #[test]
fn test_negative_byte_syntax() { fn test_negative_byte_syntax() {
new_ucmd!() new_ucmd!()

View file

@ -281,6 +281,7 @@ fn test_leading_whitespace_in_free_argument_should_imply_padding() {
} }
#[test] #[test]
#[ignore]
fn test_should_calculate_implicit_padding_per_free_argument() { fn test_should_calculate_implicit_padding_per_free_argument() {
new_ucmd!() new_ucmd!()
.args(&["--from=auto", " 1Ki", " 2K"]) .args(&["--from=auto", " 1Ki", " 2K"])

View file

@ -1,6 +1,7 @@
use crate::common::util::*; use crate::common::util::*;
fn test_helper(file_name: &str, args: &str) { fn test_helper(file_name: &str, possible_args: &[&str]) {
for args in possible_args {
new_ucmd!() new_ucmd!()
.arg(format!("{}.txt", file_name)) .arg(format!("{}.txt", file_name))
.args(&args.split(' ').collect::<Vec<&str>>()) .args(&args.split(' ').collect::<Vec<&str>>())
@ -13,6 +14,7 @@ fn test_helper(file_name: &str, args: &str) {
.args(&args.split(' ').collect::<Vec<&str>>()) .args(&args.split(' ').collect::<Vec<&str>>())
.succeeds() .succeeds()
.stdout_is_fixture(format!("{}.expected.debug", file_name)); .stdout_is_fixture(format!("{}.expected.debug", file_name));
}
} }
#[test] #[test]
@ -71,7 +73,7 @@ fn test_extsort_zero_terminated() {
#[test] #[test]
fn test_months_whitespace() { fn test_months_whitespace() {
test_helper("months-whitespace", "-M"); test_helper("months-whitespace", &["-M", "--month-sort", "--sort=month"]);
} }
#[test] #[test]
@ -85,7 +87,10 @@ fn test_version_empty_lines() {
#[test] #[test]
fn test_human_numeric_whitespace() { fn test_human_numeric_whitespace() {
test_helper("human-numeric-whitespace", "-h"); test_helper(
"human-numeric-whitespace",
&["-h", "--human-numeric-sort", "--sort=human-numeric"],
);
} }
// This tests where serde often fails when reading back JSON // This tests where serde often fails when reading back JSON
@ -102,12 +107,18 @@ fn test_extsort_as64_bailout() {
#[test] #[test]
fn test_multiple_decimals_general() { fn test_multiple_decimals_general() {
test_helper("multiple_decimals_general", "-g") test_helper(
"multiple_decimals_general",
&["-g", "--general-numeric-sort", "--sort=general-numeric"],
)
} }
#[test] #[test]
fn test_multiple_decimals_numeric() { fn test_multiple_decimals_numeric() {
test_helper("multiple_decimals_numeric", "-n") test_helper(
"multiple_decimals_numeric",
&["-n", "--numeric-sort", "--sort=numeric"],
)
} }
#[test] #[test]
@ -186,72 +197,93 @@ fn test_random_shuffle_contains_two_runs_not_the_same() {
#[test] #[test]
fn test_numeric_floats_and_ints() { fn test_numeric_floats_and_ints() {
test_helper("numeric_floats_and_ints", "-n"); test_helper(
"numeric_floats_and_ints",
&["-n", "--numeric-sort", "--sort=numeric"],
);
} }
#[test] #[test]
fn test_numeric_floats() { fn test_numeric_floats() {
test_helper("numeric_floats", "-n"); test_helper(
"numeric_floats",
&["-n", "--numeric-sort", "--sort=numeric"],
);
} }
#[test] #[test]
fn test_numeric_floats_with_nan() { fn test_numeric_floats_with_nan() {
test_helper("numeric_floats_with_nan", "-n"); test_helper(
"numeric_floats_with_nan",
&["-n", "--numeric-sort", "--sort=numeric"],
);
} }
#[test] #[test]
fn test_numeric_unfixed_floats() { fn test_numeric_unfixed_floats() {
test_helper("numeric_unfixed_floats", "-n"); test_helper(
"numeric_unfixed_floats",
&["-n", "--numeric-sort", "--sort=numeric"],
);
} }
#[test] #[test]
fn test_numeric_fixed_floats() { fn test_numeric_fixed_floats() {
test_helper("numeric_fixed_floats", "-n"); test_helper(
"numeric_fixed_floats",
&["-n", "--numeric-sort", "--sort=numeric"],
);
} }
#[test] #[test]
fn test_numeric_unsorted_ints() { fn test_numeric_unsorted_ints() {
test_helper("numeric_unsorted_ints", "-n"); test_helper(
"numeric_unsorted_ints",
&["-n", "--numeric-sort", "--sort=numeric"],
);
} }
#[test] #[test]
fn test_human_block_sizes() { fn test_human_block_sizes() {
test_helper("human_block_sizes", "-h"); test_helper(
"human_block_sizes",
&["-h", "--human-numeric-sort", "--sort=human-numeric"],
);
} }
#[test] #[test]
fn test_month_default() { fn test_month_default() {
test_helper("month_default", "-M"); test_helper("month_default", &["-M", "--month-sort", "--sort=month"]);
} }
#[test] #[test]
fn test_month_stable() { fn test_month_stable() {
test_helper("month_stable", "-Ms"); test_helper("month_stable", &["-Ms"]);
} }
#[test] #[test]
fn test_default_unsorted_ints() { fn test_default_unsorted_ints() {
test_helper("default_unsorted_ints", ""); test_helper("default_unsorted_ints", &[""]);
} }
#[test] #[test]
fn test_numeric_unique_ints() { fn test_numeric_unique_ints() {
test_helper("numeric_unsorted_ints_unique", "-nu"); test_helper("numeric_unsorted_ints_unique", &["-nu"]);
} }
#[test] #[test]
fn test_version() { fn test_version() {
test_helper("version", "-V"); test_helper("version", &["-V"]);
} }
#[test] #[test]
fn test_ignore_case() { fn test_ignore_case() {
test_helper("ignore_case", "-f"); test_helper("ignore_case", &["-f"]);
} }
#[test] #[test]
fn test_dictionary_order() { fn test_dictionary_order() {
test_helper("dictionary_order", "-d"); test_helper("dictionary_order", &["-d"]);
} }
#[test] #[test]
@ -278,47 +310,53 @@ fn test_non_printing_chars() {
#[test] #[test]
fn test_exponents_positive_general_fixed() { fn test_exponents_positive_general_fixed() {
test_helper("exponents_general", "-g"); test_helper("exponents_general", &["-g"]);
} }
#[test] #[test]
fn test_exponents_positive_numeric() { fn test_exponents_positive_numeric() {
test_helper("exponents-positive-numeric", "-n"); test_helper(
"exponents-positive-numeric",
&["-n", "--numeric-sort", "--sort=numeric"],
);
} }
#[test] #[test]
fn test_months_dedup() { fn test_months_dedup() {
test_helper("months-dedup", "-Mu"); test_helper("months-dedup", &["-Mu"]);
} }
#[test] #[test]
fn test_mixed_floats_ints_chars_numeric() { fn test_mixed_floats_ints_chars_numeric() {
test_helper("mixed_floats_ints_chars_numeric", "-n"); test_helper(
"mixed_floats_ints_chars_numeric",
&["-n", "--numeric-sort", "--sort=numeric"],
);
} }
#[test] #[test]
fn test_mixed_floats_ints_chars_numeric_unique() { fn test_mixed_floats_ints_chars_numeric_unique() {
test_helper("mixed_floats_ints_chars_numeric_unique", "-nu"); test_helper("mixed_floats_ints_chars_numeric_unique", &["-nu"]);
} }
#[test] #[test]
fn test_words_unique() { fn test_words_unique() {
test_helper("words_unique", "-u"); test_helper("words_unique", &["-u"]);
} }
#[test] #[test]
fn test_numeric_unique() { fn test_numeric_unique() {
test_helper("numeric_unique", "-nu"); test_helper("numeric_unique", &["-nu"]);
} }
#[test] #[test]
fn test_mixed_floats_ints_chars_numeric_reverse() { fn test_mixed_floats_ints_chars_numeric_reverse() {
test_helper("mixed_floats_ints_chars_numeric_unique_reverse", "-nur"); test_helper("mixed_floats_ints_chars_numeric_unique_reverse", &["-nur"]);
} }
#[test] #[test]
fn test_mixed_floats_ints_chars_numeric_stable() { fn test_mixed_floats_ints_chars_numeric_stable() {
test_helper("mixed_floats_ints_chars_numeric_stable", "-ns"); test_helper("mixed_floats_ints_chars_numeric_stable", &["-ns"]);
} }
#[test] #[test]
@ -347,12 +385,15 @@ fn test_numeric_floats2() {
#[test] #[test]
fn test_numeric_floats_with_nan2() { fn test_numeric_floats_with_nan2() {
test_helper("numeric-floats-with-nan2", "-n"); test_helper(
"numeric-floats-with-nan2",
&["-n", "--numeric-sort", "--sort=numeric"],
);
} }
#[test] #[test]
fn test_human_block_sizes2() { fn test_human_block_sizes2() {
for human_numeric_sort_param in vec!["-h", "--human-numeric-sort"] { for human_numeric_sort_param in &["-h", "--human-numeric-sort", "--sort=human-numeric"] {
let input = "8981K\n909991M\n-8T\n21G\n0.8M"; let input = "8981K\n909991M\n-8T\n21G\n0.8M";
new_ucmd!() new_ucmd!()
.arg(human_numeric_sort_param) .arg(human_numeric_sort_param)
@ -364,7 +405,7 @@ fn test_human_block_sizes2() {
#[test] #[test]
fn test_month_default2() { fn test_month_default2() {
for month_sort_param in vec!["-M", "--month-sort"] { for month_sort_param in &["-M", "--month-sort", "--sort=month"] {
let input = "JAn\nMAY\n000may\nJun\nFeb"; let input = "JAn\nMAY\n000may\nJun\nFeb";
new_ucmd!() new_ucmd!()
.arg(month_sort_param) .arg(month_sort_param)
@ -397,32 +438,32 @@ fn test_numeric_unique_ints2() {
#[test] #[test]
fn test_keys_open_ended() { fn test_keys_open_ended() {
test_helper("keys_open_ended", "-k 2.3"); test_helper("keys_open_ended", &["-k 2.3"]);
} }
#[test] #[test]
fn test_keys_closed_range() { fn test_keys_closed_range() {
test_helper("keys_closed_range", "-k 2.2,2.2"); test_helper("keys_closed_range", &["-k 2.2,2.2"]);
} }
#[test] #[test]
fn test_keys_multiple_ranges() { fn test_keys_multiple_ranges() {
test_helper("keys_multiple_ranges", "-k 2,2 -k 3,3"); test_helper("keys_multiple_ranges", &["-k 2,2 -k 3,3"]);
} }
#[test] #[test]
fn test_keys_no_field_match() { fn test_keys_no_field_match() {
test_helper("keys_no_field_match", "-k 4,4"); test_helper("keys_no_field_match", &["-k 4,4"]);
} }
#[test] #[test]
fn test_keys_no_char_match() { fn test_keys_no_char_match() {
test_helper("keys_no_char_match", "-k 1.2"); test_helper("keys_no_char_match", &["-k 1.2"]);
} }
#[test] #[test]
fn test_keys_custom_separator() { fn test_keys_custom_separator() {
test_helper("keys_custom_separator", "-k 2.2,2.2 -t x"); test_helper("keys_custom_separator", &["-k 2.2,2.2 -t x"]);
} }
#[test] #[test]
@ -534,7 +575,7 @@ aaaa
#[test] #[test]
fn test_zero_terminated() { fn test_zero_terminated() {
test_helper("zero-terminated", "-z"); test_helper("zero-terminated", &["-z"]);
} }
#[test] #[test]

View file

@ -349,7 +349,6 @@ fn test_sleep_interval() {
new_ucmd!().arg("-s").arg("10").arg(FOOBAR_TXT).succeeds(); new_ucmd!().arg("-s").arg("10").arg(FOOBAR_TXT).succeeds();
} }
/// Test for reading all but the first NUM bytes: `tail -c +3`. /// Test for reading all but the first NUM bytes: `tail -c +3`.
#[test] #[test]
fn test_positive_bytes() { fn test_positive_bytes() {
@ -360,7 +359,6 @@ fn test_positive_bytes() {
.stdout_is("cde"); .stdout_is("cde");
} }
/// Test for reading all bytes, specified by `tail -c +0`. /// Test for reading all bytes, specified by `tail -c +0`.
#[test] #[test]
fn test_positive_zero_bytes() { fn test_positive_zero_bytes() {
@ -371,7 +369,6 @@ fn test_positive_zero_bytes() {
.stdout_is("abcde"); .stdout_is("abcde");
} }
/// Test for reading all but the first NUM lines: `tail -n +3`. /// Test for reading all but the first NUM lines: `tail -n +3`.
#[test] #[test]
fn test_positive_lines() { fn test_positive_lines() {
@ -382,7 +379,6 @@ fn test_positive_lines() {
.stdout_is("c\nd\ne\n"); .stdout_is("c\nd\ne\n");
} }
/// Test for reading all lines, specified by `tail -n +0`. /// Test for reading all lines, specified by `tail -n +0`.
#[test] #[test]
fn test_positive_zero_lines() { fn test_positive_zero_lines() {