mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-27 19:17:43 +00:00
Merge branch 'main' into tail_notify
This commit is contained in:
commit
e8834597f3
13 changed files with 301 additions and 70 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -178,9 +178,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytecount"
|
name = "bytecount"
|
||||||
version = "0.6.2"
|
version = "0.6.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72feb31ffc86498dacdbd0fcebb56138e7177a8cc5cea4516031d15ae85a742e"
|
checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
|
|
|
@ -87,6 +87,9 @@ struct Options {
|
||||||
/// types will *not* be listed.
|
/// types will *not* be listed.
|
||||||
exclude: Option<Vec<String>>,
|
exclude: Option<Vec<String>>,
|
||||||
|
|
||||||
|
/// Whether to sync before operating.
|
||||||
|
sync: bool,
|
||||||
|
|
||||||
/// Whether to show a final row comprising the totals for each column.
|
/// Whether to show a final row comprising the totals for each column.
|
||||||
show_total: bool,
|
show_total: bool,
|
||||||
|
|
||||||
|
@ -104,6 +107,7 @@ impl Default for Options {
|
||||||
header_mode: Default::default(),
|
header_mode: Default::default(),
|
||||||
include: Default::default(),
|
include: Default::default(),
|
||||||
exclude: Default::default(),
|
exclude: Default::default(),
|
||||||
|
sync: Default::default(),
|
||||||
show_total: Default::default(),
|
show_total: Default::default(),
|
||||||
columns: vec![
|
columns: vec![
|
||||||
Column::Source,
|
Column::Source,
|
||||||
|
@ -178,6 +182,7 @@ impl Options {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
show_local_fs: matches.is_present(OPT_LOCAL),
|
show_local_fs: matches.is_present(OPT_LOCAL),
|
||||||
show_all_fs: matches.is_present(OPT_ALL),
|
show_all_fs: matches.is_present(OPT_ALL),
|
||||||
|
sync: matches.is_present(OPT_SYNC),
|
||||||
block_size: read_block_size(matches).map_err(|e| match e {
|
block_size: read_block_size(matches).map_err(|e| match e {
|
||||||
ParseSizeError::InvalidSuffix(s) => OptionsError::InvalidSuffix(s),
|
ParseSizeError::InvalidSuffix(s) => OptionsError::InvalidSuffix(s),
|
||||||
ParseSizeError::SizeTooBig(_) => OptionsError::BlockSizeTooLarge(
|
ParseSizeError::SizeTooBig(_) => OptionsError::BlockSizeTooLarge(
|
||||||
|
@ -325,9 +330,21 @@ fn filter_mount_list(vmi: Vec<MountInfo>, opt: &Options) -> Vec<MountInfo> {
|
||||||
|
|
||||||
/// Get all currently mounted filesystems.
|
/// Get all currently mounted filesystems.
|
||||||
///
|
///
|
||||||
/// `opt` excludes certain filesystems from consideration; see
|
/// `opt` excludes certain filesystems from consideration and allows for the synchronization of filesystems before running; see
|
||||||
/// [`Options`] for more information.
|
/// [`Options`] for more information.
|
||||||
|
|
||||||
fn get_all_filesystems(opt: &Options) -> Vec<Filesystem> {
|
fn get_all_filesystems(opt: &Options) -> Vec<Filesystem> {
|
||||||
|
// Run a sync call before any operation if so instructed.
|
||||||
|
if opt.sync {
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
unsafe {
|
||||||
|
#[cfg(not(target_os = "android"))]
|
||||||
|
uucore::libc::sync();
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
uucore::libc::syscall(uucore::libc::SYS_sync);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The list of all mounted filesystems.
|
// The list of all mounted filesystems.
|
||||||
//
|
//
|
||||||
// Filesystems excluded by the command-line options are
|
// Filesystems excluded by the command-line options are
|
||||||
|
@ -559,7 +576,7 @@ pub fn uu_app<'a>() -> Command<'a> {
|
||||||
Arg::new(OPT_SYNC)
|
Arg::new(OPT_SYNC)
|
||||||
.long("sync")
|
.long("sync")
|
||||||
.overrides_with_all(&[OPT_NO_SYNC, OPT_SYNC])
|
.overrides_with_all(&[OPT_NO_SYNC, OPT_SYNC])
|
||||||
.help("invoke sync before getting usage info"),
|
.help("invoke sync before getting usage info (non-windows only)"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new(OPT_TYPE)
|
Arg::new(OPT_TYPE)
|
||||||
|
|
|
@ -135,13 +135,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let result;
|
let result;
|
||||||
if files.is_empty() {
|
if files.is_empty() {
|
||||||
result = parse(INTERNAL_DB.lines(), &out_format, "");
|
result = parse(INTERNAL_DB.lines(), &out_format, "");
|
||||||
|
} else if files.len() > 1 {
|
||||||
|
return Err(UUsageError::new(
|
||||||
|
1,
|
||||||
|
format!("extra operand {}", files[1].quote()),
|
||||||
|
));
|
||||||
|
} else if files[0].eq("-") {
|
||||||
|
let fin = BufReader::new(std::io::stdin());
|
||||||
|
result = parse(fin.lines().filter_map(Result::ok), &out_format, files[0]);
|
||||||
} else {
|
} else {
|
||||||
if files.len() > 1 {
|
|
||||||
return Err(UUsageError::new(
|
|
||||||
1,
|
|
||||||
format!("extra operand {}", files[1].quote()),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
match File::open(files[0]) {
|
match File::open(files[0]) {
|
||||||
Ok(f) => {
|
Ok(f) => {
|
||||||
let fin = BufReader::new(f);
|
let fin = BufReader::new(f);
|
||||||
|
|
|
@ -13,12 +13,15 @@
|
||||||
extern crate uucore;
|
extern crate uucore;
|
||||||
|
|
||||||
use clap::{crate_version, Arg, ArgMatches, Command};
|
use clap::{crate_version, Arg, ArgMatches, Command};
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fmt;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write};
|
use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write};
|
||||||
|
use std::num::IntErrorKind;
|
||||||
use std::str::from_utf8;
|
use std::str::from_utf8;
|
||||||
use unicode_width::UnicodeWidthChar;
|
use unicode_width::UnicodeWidthChar;
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::{FromIo, UResult};
|
use uucore::error::{FromIo, UError, UResult};
|
||||||
use uucore::format_usage;
|
use uucore::format_usage;
|
||||||
|
|
||||||
static ABOUT: &str = "Convert tabs in each FILE to spaces, writing to standard output.
|
static ABOUT: &str = "Convert tabs in each FILE to spaces, writing to standard output.
|
||||||
|
@ -57,6 +60,38 @@ fn is_space_or_comma(c: char) -> bool {
|
||||||
c == ' ' || c == ','
|
c == ' ' || c == ','
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Errors that can occur when parsing a `--tabs` argument.
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum ParseError {
|
||||||
|
InvalidCharacter(String),
|
||||||
|
SpecifierNotAtStartOfNumber(String, String),
|
||||||
|
TabSizeCannotBeZero,
|
||||||
|
TabSizeTooLarge(String),
|
||||||
|
TabSizesMustBeAscending,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for ParseError {}
|
||||||
|
impl UError for ParseError {}
|
||||||
|
|
||||||
|
impl fmt::Display for ParseError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::InvalidCharacter(s) => {
|
||||||
|
write!(f, "tab size contains invalid character(s): {}", s.quote())
|
||||||
|
}
|
||||||
|
Self::SpecifierNotAtStartOfNumber(specifier, s) => write!(
|
||||||
|
f,
|
||||||
|
"{} specifier not at start of number: {}",
|
||||||
|
specifier.quote(),
|
||||||
|
s.quote(),
|
||||||
|
),
|
||||||
|
Self::TabSizeCannotBeZero => write!(f, "tab size cannot be 0"),
|
||||||
|
Self::TabSizeTooLarge(s) => write!(f, "tab stop is too large {}", s.quote()),
|
||||||
|
Self::TabSizesMustBeAscending => write!(f, "tab sizes must be ascending"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse a list of tabstops from a `--tabs` argument.
|
/// Parse a list of tabstops from a `--tabs` argument.
|
||||||
///
|
///
|
||||||
/// This function returns both the vector of numbers appearing in the
|
/// This function returns both the vector of numbers appearing in the
|
||||||
|
@ -65,14 +100,14 @@ fn is_space_or_comma(c: char) -> bool {
|
||||||
/// in the list. This mode defines the strategy to use for computing the
|
/// in the list. This mode defines the strategy to use for computing the
|
||||||
/// number of spaces to use for columns beyond the end of the tab stop
|
/// number of spaces to use for columns beyond the end of the tab stop
|
||||||
/// list specified here.
|
/// list specified here.
|
||||||
fn tabstops_parse(s: &str) -> (RemainingMode, Vec<usize>) {
|
fn tabstops_parse(s: &str) -> Result<(RemainingMode, Vec<usize>), ParseError> {
|
||||||
// Leading commas and spaces are ignored.
|
// Leading commas and spaces are ignored.
|
||||||
let s = s.trim_start_matches(is_space_or_comma);
|
let s = s.trim_start_matches(is_space_or_comma);
|
||||||
|
|
||||||
// If there were only commas and spaces in the string, just use the
|
// If there were only commas and spaces in the string, just use the
|
||||||
// default tabstops.
|
// default tabstops.
|
||||||
if s.is_empty() {
|
if s.is_empty() {
|
||||||
return (RemainingMode::None, vec![DEFAULT_TABSTOP]);
|
return Ok((RemainingMode::None, vec![DEFAULT_TABSTOP]));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut nums = vec![];
|
let mut nums = vec![];
|
||||||
|
@ -89,23 +124,41 @@ fn tabstops_parse(s: &str) -> (RemainingMode, Vec<usize>) {
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Parse a number from the byte sequence.
|
// Parse a number from the byte sequence.
|
||||||
let num = from_utf8(&bytes[i..]).unwrap().parse::<usize>().unwrap();
|
let s = from_utf8(&bytes[i..]).unwrap();
|
||||||
|
match s.parse::<usize>() {
|
||||||
|
Ok(num) => {
|
||||||
|
// Tab size must be positive.
|
||||||
|
if num == 0 {
|
||||||
|
return Err(ParseError::TabSizeCannotBeZero);
|
||||||
|
}
|
||||||
|
|
||||||
// Tab size must be positive.
|
// Tab sizes must be ascending.
|
||||||
if num == 0 {
|
if let Some(last_stop) = nums.last() {
|
||||||
crash!(1, "tab size cannot be 0");
|
if *last_stop >= num {
|
||||||
}
|
return Err(ParseError::TabSizesMustBeAscending);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Tab sizes must be ascending.
|
// Append this tab stop to the list of all tabstops.
|
||||||
if let Some(last_stop) = nums.last() {
|
nums.push(num);
|
||||||
if *last_stop >= num {
|
break;
|
||||||
crash!(1, "tab sizes must be ascending");
|
}
|
||||||
|
Err(e) => {
|
||||||
|
if *e.kind() == IntErrorKind::PosOverflow {
|
||||||
|
return Err(ParseError::TabSizeTooLarge(s.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let s = s.trim_start_matches(char::is_numeric);
|
||||||
|
if s.starts_with('/') || s.starts_with('+') {
|
||||||
|
return Err(ParseError::SpecifierNotAtStartOfNumber(
|
||||||
|
s[0..1].to_string(),
|
||||||
|
s.to_string(),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
return Err(ParseError::InvalidCharacter(s.to_string()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append this tab stop to the list of all tabstops.
|
|
||||||
nums.push(num);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,7 +168,7 @@ fn tabstops_parse(s: &str) -> (RemainingMode, Vec<usize>) {
|
||||||
if nums.is_empty() {
|
if nums.is_empty() {
|
||||||
nums = vec![DEFAULT_TABSTOP];
|
nums = vec![DEFAULT_TABSTOP];
|
||||||
}
|
}
|
||||||
(remaining_mode, nums)
|
Ok((remaining_mode, nums))
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Options {
|
struct Options {
|
||||||
|
@ -131,9 +184,9 @@ struct Options {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Options {
|
impl Options {
|
||||||
fn new(matches: &ArgMatches) -> Self {
|
fn new(matches: &ArgMatches) -> Result<Self, ParseError> {
|
||||||
let (remaining_mode, tabstops) = match matches.values_of(options::TABS) {
|
let (remaining_mode, tabstops) = match matches.values_of(options::TABS) {
|
||||||
Some(s) => tabstops_parse(&s.collect::<Vec<&str>>().join(",")),
|
Some(s) => tabstops_parse(&s.collect::<Vec<&str>>().join(","))?,
|
||||||
None => (RemainingMode::None, vec![DEFAULT_TABSTOP]),
|
None => (RemainingMode::None, vec![DEFAULT_TABSTOP]),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -158,14 +211,14 @@ impl Options {
|
||||||
None => vec!["-".to_owned()],
|
None => vec!["-".to_owned()],
|
||||||
};
|
};
|
||||||
|
|
||||||
Self {
|
Ok(Self {
|
||||||
files,
|
files,
|
||||||
tabstops,
|
tabstops,
|
||||||
tspaces,
|
tspaces,
|
||||||
iflag,
|
iflag,
|
||||||
uflag,
|
uflag,
|
||||||
remaining_mode,
|
remaining_mode,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,7 +226,7 @@ impl Options {
|
||||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let matches = uu_app().get_matches_from(args);
|
let matches = uu_app().get_matches_from(args);
|
||||||
|
|
||||||
expand(&Options::new(&matches)).map_err_context(|| "failed to write output".to_string())
|
expand(&Options::new(&matches)?).map_err_context(|| "failed to write output".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uu_app<'a>() -> Command<'a> {
|
pub fn uu_app<'a>() -> Command<'a> {
|
||||||
|
|
|
@ -2505,9 +2505,12 @@ fn display_file_name(
|
||||||
let mut width = name.width();
|
let mut width = name.width();
|
||||||
|
|
||||||
if let Some(ls_colors) = &config.color {
|
if let Some(ls_colors) = &config.color {
|
||||||
if let Ok(metadata) = path.p_buf.symlink_metadata() {
|
name = color_name(
|
||||||
name = color_name(ls_colors, &path.p_buf, &name, &metadata, config);
|
name,
|
||||||
}
|
&path.p_buf,
|
||||||
|
path.p_buf.symlink_metadata().ok().as_ref(),
|
||||||
|
ls_colors,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.format != Format::Long && !more_info.is_empty() {
|
if config.format != Format::Long && !more_info.is_empty() {
|
||||||
|
@ -2588,11 +2591,10 @@ fn display_file_name(
|
||||||
};
|
};
|
||||||
|
|
||||||
name.push_str(&color_name(
|
name.push_str(&color_name(
|
||||||
ls_colors,
|
escape_name(target.as_os_str(), &config.quoting_style),
|
||||||
&target_data.p_buf,
|
&target_data.p_buf,
|
||||||
&target.to_string_lossy(),
|
Some(&target_metadata),
|
||||||
&target_metadata,
|
ls_colors,
|
||||||
config,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -2623,19 +2625,12 @@ fn display_file_name(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn color_name(
|
fn color_name(name: String, path: &Path, md: Option<&Metadata>, ls_colors: &LsColors) -> String {
|
||||||
ls_colors: &LsColors,
|
match ls_colors.style_for_path_with_metadata(path, md) {
|
||||||
path: &Path,
|
|
||||||
name: &str,
|
|
||||||
md: &Metadata,
|
|
||||||
config: &Config,
|
|
||||||
) -> String {
|
|
||||||
match ls_colors.style_for_path_with_metadata(path, Some(md)) {
|
|
||||||
Some(style) => {
|
Some(style) => {
|
||||||
let p = escape_name(OsStr::new(&name), &config.quoting_style);
|
return style.to_ansi_term_style().paint(name).to_string();
|
||||||
return style.to_ansi_term_style().paint(p).to_string();
|
|
||||||
}
|
}
|
||||||
None => escape_name(OsStr::new(&name), &config.quoting_style),
|
None => name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
// For the full copyright and license information, please view the LICENSE
|
// For the full copyright and license information, please view the LICENSE
|
||||||
// file that was distributed with this source code.
|
// file that was distributed with this source code.
|
||||||
|
|
||||||
// spell-checker:ignore (paths) GPGHome
|
// spell-checker:ignore (paths) GPGHome findxs
|
||||||
|
|
||||||
use clap::{crate_version, Arg, ArgMatches, Command};
|
use clap::{crate_version, Arg, ArgMatches, Command};
|
||||||
use uucore::display::{println_verbatim, Quotable};
|
use uucore::display::{println_verbatim, Quotable};
|
||||||
|
@ -205,12 +205,29 @@ struct Params {
|
||||||
suffix: String,
|
suffix: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Find the start and end indices of the last contiguous block of Xs.
|
||||||
|
///
|
||||||
|
/// If no contiguous block of at least three Xs could be found, this
|
||||||
|
/// function returns `None`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// assert_eq!(find_last_contiguous_block_of_xs("XXX_XXX"), Some((4, 7)));
|
||||||
|
/// assert_eq!(find_last_contiguous_block_of_xs("aXbXcX"), None);
|
||||||
|
/// ```
|
||||||
|
fn find_last_contiguous_block_of_xs(s: &str) -> Option<(usize, usize)> {
|
||||||
|
let j = s.rfind("XXX")? + 3;
|
||||||
|
let i = s[..j].rfind(|c| c != 'X').map_or(0, |i| i + 1);
|
||||||
|
Some((i, j))
|
||||||
|
}
|
||||||
|
|
||||||
impl Params {
|
impl Params {
|
||||||
fn from(options: Options) -> Result<Self, MkTempError> {
|
fn from(options: Options) -> Result<Self, MkTempError> {
|
||||||
// Get the start and end indices of the randomized part of the template.
|
// Get the start and end indices of the randomized part of the template.
|
||||||
//
|
//
|
||||||
// For example, if the template is "abcXXXXyz", then `i` is 3 and `j` is 7.
|
// For example, if the template is "abcXXXXyz", then `i` is 3 and `j` is 7.
|
||||||
let i = match options.template.find("XXX") {
|
let (i, j) = match find_last_contiguous_block_of_xs(&options.template) {
|
||||||
None => {
|
None => {
|
||||||
let s = match options.suffix {
|
let s = match options.suffix {
|
||||||
None => options.template,
|
None => options.template,
|
||||||
|
@ -218,9 +235,8 @@ impl Params {
|
||||||
};
|
};
|
||||||
return Err(MkTempError::TooFewXs(s));
|
return Err(MkTempError::TooFewXs(s));
|
||||||
}
|
}
|
||||||
Some(i) => i,
|
Some(indices) => indices,
|
||||||
};
|
};
|
||||||
let j = options.template.rfind("XXX").unwrap() + 3;
|
|
||||||
|
|
||||||
// Combine the directory given as an option and the prefix of the template.
|
// Combine the directory given as an option and the prefix of the template.
|
||||||
//
|
//
|
||||||
|
@ -450,3 +466,21 @@ fn exec(dir: &str, prefix: &str, rand: usize, suffix: &str, make_dir: bool) -> U
|
||||||
|
|
||||||
println_verbatim(path).map_err_context(|| "failed to print directory name".to_owned())
|
println_verbatim(path).map_err_context(|| "failed to print directory name".to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::find_last_contiguous_block_of_xs as findxs;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_find_last_contiguous_block_of_xs() {
|
||||||
|
assert_eq!(findxs("XXX"), Some((0, 3)));
|
||||||
|
assert_eq!(findxs("XXX_XXX"), Some((4, 7)));
|
||||||
|
assert_eq!(findxs("XXX_XXX_XXX"), Some((8, 11)));
|
||||||
|
assert_eq!(findxs("aaXXXbb"), Some((2, 5)));
|
||||||
|
assert_eq!(findxs(""), None);
|
||||||
|
assert_eq!(findxs("X"), None);
|
||||||
|
assert_eq!(findxs("XX"), None);
|
||||||
|
assert_eq!(findxs("aXbXcX"), None);
|
||||||
|
assert_eq!(findxs("aXXbXXcXX"), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ path = "src/wc.rs"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
|
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
|
||||||
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["pipes"] }
|
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["pipes"] }
|
||||||
bytecount = "0.6.2"
|
bytecount = "0.6.3"
|
||||||
utf-8 = "0.7.6"
|
utf-8 = "0.7.6"
|
||||||
unicode-width = "0.1.8"
|
unicode-width = "0.1.8"
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,11 @@ fn test_df_compatible_si() {
|
||||||
new_ucmd!().arg("-aH").succeeds();
|
new_ucmd!().arg("-aH").succeeds();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_df_compatible_sync() {
|
||||||
|
new_ucmd!().arg("--sync").succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_df_arguments_override_themselves() {
|
fn test_df_arguments_override_themselves() {
|
||||||
new_ucmd!().args(&["--help", "--help"]).succeeds();
|
new_ucmd!().args(&["--help", "--help"]).succeeds();
|
||||||
|
|
|
@ -131,6 +131,25 @@ fn test_exclusive_option() {
|
||||||
.stderr_contains("mutually exclusive");
|
.stderr_contains("mutually exclusive");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stdin() {
|
||||||
|
new_ucmd!()
|
||||||
|
.pipe_in("owt 40;33\n")
|
||||||
|
.args(&["-b", "-"])
|
||||||
|
.succeeds()
|
||||||
|
.stdout_is("LS_COLORS='tw=40;33:';\nexport LS_COLORS\n")
|
||||||
|
.no_stderr();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extra_operand() {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["-c", "file1", "file2"])
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("dircolors: extra operand 'file2'\n")
|
||||||
|
.no_stdout();
|
||||||
|
}
|
||||||
|
|
||||||
fn test_helper(file_name: &str, term: &str) {
|
fn test_helper(file_name: &str, term: &str) {
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.env("TERM", term)
|
.env("TERM", term)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::common::util::*;
|
use crate::common::util::*;
|
||||||
|
use uucore::display::Quotable;
|
||||||
// spell-checker:ignore (ToDO) taaaa tbbbb tcccc
|
// spell-checker:ignore (ToDO) taaaa tbbbb tcccc
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -179,6 +180,14 @@ fn test_tabs_must_be_ascending() {
|
||||||
.stderr_contains("tab sizes must be ascending");
|
.stderr_contains("tab sizes must be ascending");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tabs_cannot_be_zero() {
|
||||||
|
new_ucmd!()
|
||||||
|
.arg("--tabs=0")
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("tab size cannot be 0");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_tabs_keep_last_trailing_specifier() {
|
fn test_tabs_keep_last_trailing_specifier() {
|
||||||
// If there are multiple trailing specifiers, use only the last one
|
// If there are multiple trailing specifiers, use only the last one
|
||||||
|
@ -200,3 +209,39 @@ fn test_tabs_comma_separated_no_numbers() {
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is(" a b c");
|
.stdout_is(" a b c");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tabs_with_specifier_not_at_start() {
|
||||||
|
fn run_cmd(arg: &str, expected_prefix: &str, expected_suffix: &str) {
|
||||||
|
let expected_msg = format!(
|
||||||
|
"{} specifier not at start of number: {}",
|
||||||
|
expected_prefix.quote(),
|
||||||
|
expected_suffix.quote()
|
||||||
|
);
|
||||||
|
new_ucmd!().arg(arg).fails().stderr_contains(expected_msg);
|
||||||
|
}
|
||||||
|
run_cmd("--tabs=1/", "/", "/");
|
||||||
|
run_cmd("--tabs=1/2", "/", "/2");
|
||||||
|
run_cmd("--tabs=1+", "+", "+");
|
||||||
|
run_cmd("--tabs=1+2", "+", "+2");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tabs_with_invalid_chars() {
|
||||||
|
new_ucmd!()
|
||||||
|
.arg("--tabs=x")
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("tab size contains invalid character(s): 'x'");
|
||||||
|
new_ucmd!()
|
||||||
|
.arg("--tabs=1x2")
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("tab size contains invalid character(s): 'x2'");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tabs_with_too_large_size() {
|
||||||
|
let arg = format!("--tabs={}", u128::MAX);
|
||||||
|
let expected_error = format!("tab stop is too large '{}'", u128::MAX);
|
||||||
|
|
||||||
|
new_ucmd!().arg(arg).fails().stderr_contains(expected_error);
|
||||||
|
}
|
||||||
|
|
|
@ -2324,6 +2324,20 @@ fn test_ls_quoting_style() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ls_quoting_and_color() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
let at = &scene.fixtures;
|
||||||
|
|
||||||
|
at.touch("one two");
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("--color")
|
||||||
|
.arg("one two")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("'one two'\n");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_ls_ignore_hide() {
|
fn test_ls_ignore_hide() {
|
||||||
let scene = TestScenario::new(util_name!());
|
let scene = TestScenario::new(util_name!());
|
||||||
|
|
|
@ -27,6 +27,18 @@ const TMPDIR: &str = "TMPDIR";
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
const TMPDIR: &str = "TMP";
|
const TMPDIR: &str = "TMP";
|
||||||
|
|
||||||
|
/// An assertion that uses [`matches_template`] and adds a helpful error message.
|
||||||
|
macro_rules! assert_matches_template {
|
||||||
|
($template:expr, $s:expr) => {{
|
||||||
|
assert!(
|
||||||
|
matches_template($template, $s),
|
||||||
|
"\"{}\" != \"{}\"",
|
||||||
|
$template,
|
||||||
|
$s
|
||||||
|
);
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_mktemp_mktemp() {
|
fn test_mktemp_mktemp() {
|
||||||
let scene = TestScenario::new(util_name!());
|
let scene = TestScenario::new(util_name!());
|
||||||
|
@ -417,6 +429,21 @@ fn test_mktemp_directory_tmpdir() {
|
||||||
assert!(PathBuf::from(result.stdout_str().trim()).is_dir());
|
assert!(PathBuf::from(result.stdout_str().trim()).is_dir());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test for combining `--tmpdir` and a template with a subdirectory.
|
||||||
|
#[test]
|
||||||
|
fn test_tmpdir_template_has_subdirectory() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
at.mkdir("a");
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let (template, joined) = ("a/bXXXX", "./a/bXXXX");
|
||||||
|
#[cfg(windows)]
|
||||||
|
let (template, joined) = (r"a\bXXXX", r".\a\bXXXX");
|
||||||
|
let result = ucmd.args(&["--tmpdir=.", template]).succeeds();
|
||||||
|
let filename = result.no_stderr().stdout_str().trim_end();
|
||||||
|
assert_matches_template!(joined, filename);
|
||||||
|
assert!(at.file_exists(filename));
|
||||||
|
}
|
||||||
|
|
||||||
/// Test that an absolute path is disallowed when --tmpdir is provided.
|
/// Test that an absolute path is disallowed when --tmpdir is provided.
|
||||||
#[test]
|
#[test]
|
||||||
fn test_tmpdir_absolute_path() {
|
fn test_tmpdir_absolute_path() {
|
||||||
|
@ -466,18 +493,6 @@ fn matches_template(template: &str, s: &str) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An assertion that uses [`matches_template`] and adds a helpful error message.
|
|
||||||
macro_rules! assert_matches_template {
|
|
||||||
($template:expr, $s:expr) => {{
|
|
||||||
assert!(
|
|
||||||
matches_template($template, $s),
|
|
||||||
"\"{}\" != \"{}\"",
|
|
||||||
$template,
|
|
||||||
$s
|
|
||||||
);
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Test that the file is created in the directory given by the template.
|
/// Test that the file is created in the directory given by the template.
|
||||||
#[test]
|
#[test]
|
||||||
fn test_respect_template() {
|
fn test_respect_template() {
|
||||||
|
@ -550,6 +565,16 @@ fn test_suffix_path_separator() {
|
||||||
.arg(r"aXXX\b")
|
.arg(r"aXXX\b")
|
||||||
.fails()
|
.fails()
|
||||||
.stderr_only("mktemp: invalid suffix '\\b', contains directory separator\n");
|
.stderr_only("mktemp: invalid suffix '\\b', contains directory separator\n");
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
new_ucmd!()
|
||||||
|
.arg("XXX/..")
|
||||||
|
.fails()
|
||||||
|
.stderr_only("mktemp: invalid suffix '/..', contains directory separator\n");
|
||||||
|
#[cfg(windows)]
|
||||||
|
new_ucmd!()
|
||||||
|
.arg(r"XXX\..")
|
||||||
|
.fails()
|
||||||
|
.stderr_only("mktemp: invalid suffix '\\..', contains directory separator\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -572,3 +597,25 @@ fn test_too_few_xs_suffix_directory() {
|
||||||
fn test_too_many_arguments() {
|
fn test_too_many_arguments() {
|
||||||
new_ucmd!().args(&["-q", "a", "b"]).fails().code_is(1);
|
new_ucmd!().args(&["-q", "a", "b"]).fails().code_is(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_two_contiguous_wildcard_blocks() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
let template = "XXX_XXX";
|
||||||
|
let result = ucmd.arg(template).succeeds();
|
||||||
|
let filename = result.no_stderr().stdout_str().trim_end();
|
||||||
|
assert_eq!(&filename[..4], "XXX_");
|
||||||
|
assert_matches_template!(template, filename);
|
||||||
|
assert!(at.file_exists(filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_three_contiguous_wildcard_blocks() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
let template = "XXX_XXX_XXX";
|
||||||
|
let result = ucmd.arg(template).succeeds();
|
||||||
|
let filename = result.no_stderr().stdout_str().trim_end();
|
||||||
|
assert_eq!(&filename[..8], "XXX_XXX_");
|
||||||
|
assert_matches_template!(template, filename);
|
||||||
|
assert!(at.file_exists(filename));
|
||||||
|
}
|
||||||
|
|
|
@ -1738,7 +1738,7 @@ fn test_follow_name_truncate4() {
|
||||||
|
|
||||||
let mut args = vec!["-s.1", "--max-unchanged-stats=1", "-F", "file"];
|
let mut args = vec!["-s.1", "--max-unchanged-stats=1", "-F", "file"];
|
||||||
|
|
||||||
let delay = 100;
|
let delay = 300;
|
||||||
for _ in 0..2 {
|
for _ in 0..2 {
|
||||||
at.append("file", "foobar\n");
|
at.append("file", "foobar\n");
|
||||||
|
|
||||||
|
@ -1761,7 +1761,7 @@ fn test_follow_name_truncate4() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(unix)]
|
#[cfg(all(unix, not(target_os = "android")))] // NOTE: Should work on Android but CI VM is too slow.
|
||||||
fn test_follow_truncate_fast() {
|
fn test_follow_truncate_fast() {
|
||||||
// inspired by: "gnu/tests/tail-2/truncate.sh"
|
// inspired by: "gnu/tests/tail-2/truncate.sh"
|
||||||
// Ensure all logs are output upon file truncation
|
// Ensure all logs are output upon file truncation
|
||||||
|
@ -1775,7 +1775,7 @@ fn test_follow_truncate_fast() {
|
||||||
let mut args = vec!["-s.1", "--max-unchanged-stats=1", "f", "---disable-inotify"];
|
let mut args = vec!["-s.1", "--max-unchanged-stats=1", "f", "---disable-inotify"];
|
||||||
let follow = vec!["-f", "-F"];
|
let follow = vec!["-f", "-F"];
|
||||||
|
|
||||||
let delay = 100;
|
let delay = 150;
|
||||||
for _ in 0..2 {
|
for _ in 0..2 {
|
||||||
for mode in &follow {
|
for mode in &follow {
|
||||||
args.push(mode);
|
args.push(mode);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue