mirror of
https://github.com/uutils/coreutils
synced 2024-10-15 12:24:09 +00:00
Merge branch 'main' into tail_notify
This commit is contained in:
commit
8ee806a444
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -2085,9 +2085,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "users"
|
||||
version = "0.10.0"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa4227e95324a443c9fcb06e03d4d85e91aabe9a5a02aa818688b6918b6af486"
|
||||
checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
|
|
|
@ -403,7 +403,7 @@ rlimit = "0.8.3"
|
|||
|
||||
[target.'cfg(unix)'.dev-dependencies]
|
||||
nix = { version = "0.24.1", default-features = false, features = ["process", "signal", "user"] }
|
||||
rust-users = { version="0.10", package="users" }
|
||||
rust-users = { version="0.11", package="users" }
|
||||
unix_socket = "0.5.0"
|
||||
|
||||
[build-dependencies]
|
||||
|
|
|
@ -204,12 +204,18 @@ pub(crate) struct RowFormatter<'a> {
|
|||
// numbers. We could split the options up into those groups to
|
||||
// reduce the coupling between this `table.rs` module and the main
|
||||
// `df.rs` module.
|
||||
/// Whether to use the special rules for displaying the total row.
|
||||
is_total_row: bool,
|
||||
}
|
||||
|
||||
impl<'a> RowFormatter<'a> {
|
||||
/// Instantiate this struct.
|
||||
pub(crate) fn new(row: &'a Row, options: &'a Options) -> Self {
|
||||
Self { row, options }
|
||||
pub(crate) fn new(row: &'a Row, options: &'a Options, is_total_row: bool) -> Self {
|
||||
Self {
|
||||
row,
|
||||
options,
|
||||
is_total_row,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a string giving the scaled version of the input number.
|
||||
|
@ -251,13 +257,25 @@ impl<'a> RowFormatter<'a> {
|
|||
|
||||
for column in &self.options.columns {
|
||||
let string = match column {
|
||||
Column::Source => self.row.fs_device.to_string(),
|
||||
Column::Source => {
|
||||
if self.is_total_row {
|
||||
"total".to_string()
|
||||
} else {
|
||||
self.row.fs_device.to_string()
|
||||
}
|
||||
}
|
||||
Column::Size => self.scaled_bytes(self.row.bytes),
|
||||
Column::Used => self.scaled_bytes(self.row.bytes_used),
|
||||
Column::Avail => self.scaled_bytes(self.row.bytes_avail),
|
||||
Column::Pcent => Self::percentage(self.row.bytes_usage),
|
||||
|
||||
Column::Target => self.row.fs_mount.to_string(),
|
||||
Column::Target => {
|
||||
if self.is_total_row && !self.options.columns.contains(&Column::Source) {
|
||||
"total".to_string()
|
||||
} else {
|
||||
self.row.fs_mount.to_string()
|
||||
}
|
||||
}
|
||||
Column::Itotal => self.scaled_inodes(self.row.inodes),
|
||||
Column::Iused => self.scaled_inodes(self.row.inodes_used),
|
||||
Column::Iavail => self.scaled_inodes(self.row.inodes_free),
|
||||
|
@ -371,7 +389,7 @@ impl Table {
|
|||
// the output table.
|
||||
if options.show_all_fs || filesystem.usage.blocks > 0 {
|
||||
let row = Row::from(filesystem);
|
||||
let fmt = RowFormatter::new(&row, options);
|
||||
let fmt = RowFormatter::new(&row, options, false);
|
||||
let values = fmt.get_values();
|
||||
total += row;
|
||||
|
||||
|
@ -386,7 +404,7 @@ impl Table {
|
|||
}
|
||||
|
||||
if options.show_total {
|
||||
let total_row = RowFormatter::new(&total, options);
|
||||
let total_row = RowFormatter::new(&total, options, true);
|
||||
rows.push(total_row.get_values());
|
||||
}
|
||||
|
||||
|
@ -625,7 +643,7 @@ mod tests {
|
|||
|
||||
..Default::default()
|
||||
};
|
||||
let fmt = RowFormatter::new(&row, &options);
|
||||
let fmt = RowFormatter::new(&row, &options, false);
|
||||
assert_eq!(
|
||||
fmt.get_values(),
|
||||
vec!("my_device", "100", "25", "75", "25%", "my_mount")
|
||||
|
@ -651,7 +669,7 @@ mod tests {
|
|||
|
||||
..Default::default()
|
||||
};
|
||||
let fmt = RowFormatter::new(&row, &options);
|
||||
let fmt = RowFormatter::new(&row, &options, false);
|
||||
assert_eq!(
|
||||
fmt.get_values(),
|
||||
vec!("my_device", "my_type", "100", "25", "75", "25%", "my_mount")
|
||||
|
@ -676,7 +694,7 @@ mod tests {
|
|||
|
||||
..Default::default()
|
||||
};
|
||||
let fmt = RowFormatter::new(&row, &options);
|
||||
let fmt = RowFormatter::new(&row, &options, false);
|
||||
assert_eq!(
|
||||
fmt.get_values(),
|
||||
vec!("my_device", "10", "2", "8", "20%", "my_mount")
|
||||
|
@ -695,7 +713,7 @@ mod tests {
|
|||
inodes: 10,
|
||||
..Default::default()
|
||||
};
|
||||
let fmt = RowFormatter::new(&row, &options);
|
||||
let fmt = RowFormatter::new(&row, &options, false);
|
||||
assert_eq!(fmt.get_values(), vec!("1", "10"));
|
||||
}
|
||||
|
||||
|
@ -718,7 +736,7 @@ mod tests {
|
|||
|
||||
..Default::default()
|
||||
};
|
||||
let fmt = RowFormatter::new(&row, &options);
|
||||
let fmt = RowFormatter::new(&row, &options, false);
|
||||
assert_eq!(
|
||||
fmt.get_values(),
|
||||
vec!("my_device", "my_type", "4k", "1k", "3k", "25%", "my_mount")
|
||||
|
@ -744,7 +762,7 @@ mod tests {
|
|||
|
||||
..Default::default()
|
||||
};
|
||||
let fmt = RowFormatter::new(&row, &options);
|
||||
let fmt = RowFormatter::new(&row, &options, false);
|
||||
assert_eq!(
|
||||
fmt.get_values(),
|
||||
vec!("my_device", "my_type", "4K", "1K", "3K", "25%", "my_mount")
|
||||
|
@ -761,7 +779,7 @@ mod tests {
|
|||
bytes_usage: Some(0.251),
|
||||
..Default::default()
|
||||
};
|
||||
let fmt = RowFormatter::new(&row, &options);
|
||||
let fmt = RowFormatter::new(&row, &options, false);
|
||||
assert_eq!(fmt.get_values(), vec!("26%"));
|
||||
}
|
||||
|
||||
|
@ -780,7 +798,7 @@ mod tests {
|
|||
bytes_avail,
|
||||
..Default::default()
|
||||
};
|
||||
RowFormatter::new(&row, &options).get_values()
|
||||
RowFormatter::new(&row, &options, false).get_values()
|
||||
}
|
||||
|
||||
assert_eq!(get_formatted_values(100, 100, 0), vec!("1", "1", "0"));
|
||||
|
|
|
@ -4,6 +4,7 @@ Create the test fixtures by writing the output of the GNU dircolors commands to
|
|||
|
||||
```
|
||||
$ dircolors --print-database > /PATH_TO_COREUTILS/tests/fixtures/dircolors/internal.expected
|
||||
$ dircolors --print-ls-colors > /PATH_TO_COREUTILS/tests/fixtures/dircolors/ls_colors.expected
|
||||
$ dircolors -b > /PATH_TO_COREUTILS/tests/fixtures/dircolors/bash_def.expected
|
||||
$ dircolors -c > /PATH_TO_COREUTILS/tests/fixtures/dircolors/csh_def.expected
|
||||
```
|
||||
|
|
|
@ -21,6 +21,7 @@ mod options {
|
|||
pub const BOURNE_SHELL: &str = "bourne-shell";
|
||||
pub const C_SHELL: &str = "c-shell";
|
||||
pub const PRINT_DATABASE: &str = "print-database";
|
||||
pub const PRINT_LS_COLORS: &str = "print-ls-colors";
|
||||
pub const FILE: &str = "FILE";
|
||||
}
|
||||
|
||||
|
@ -39,6 +40,7 @@ use self::colors::INTERNAL_DB;
|
|||
pub enum OutputFmt {
|
||||
Shell,
|
||||
CShell,
|
||||
Display,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
|
@ -76,7 +78,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
// clap provides .conflicts_with / .conflicts_with_all, but we want to
|
||||
// manually handle conflicts so we can match the output of GNU coreutils
|
||||
if (matches.is_present(options::C_SHELL) || matches.is_present(options::BOURNE_SHELL))
|
||||
&& matches.is_present(options::PRINT_DATABASE)
|
||||
&& (matches.is_present(options::PRINT_DATABASE)
|
||||
|| matches.is_present(options::PRINT_LS_COLORS))
|
||||
{
|
||||
return Err(UUsageError::new(
|
||||
1,
|
||||
|
@ -85,6 +88,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
));
|
||||
}
|
||||
|
||||
if matches.is_present(options::PRINT_DATABASE) && matches.is_present(options::PRINT_LS_COLORS) {
|
||||
return Err(UUsageError::new(
|
||||
1,
|
||||
"options --print-database and --print-ls-colors are mutually exclusive",
|
||||
));
|
||||
}
|
||||
|
||||
if matches.is_present(options::PRINT_DATABASE) {
|
||||
if !files.is_empty() {
|
||||
return Err(UUsageError::new(
|
||||
|
@ -100,12 +110,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
let mut out_format = OutputFmt::Unknown;
|
||||
if matches.is_present(options::C_SHELL) {
|
||||
out_format = OutputFmt::CShell;
|
||||
let mut out_format = if matches.is_present(options::C_SHELL) {
|
||||
OutputFmt::CShell
|
||||
} else if matches.is_present(options::BOURNE_SHELL) {
|
||||
out_format = OutputFmt::Shell;
|
||||
}
|
||||
OutputFmt::Shell
|
||||
} else if matches.is_present(options::PRINT_LS_COLORS) {
|
||||
OutputFmt::Display
|
||||
} else {
|
||||
OutputFmt::Unknown
|
||||
};
|
||||
|
||||
if out_format == OutputFmt::Unknown {
|
||||
match guess_syntax() {
|
||||
|
@ -186,6 +199,12 @@ pub fn uu_app<'a>() -> Command<'a> {
|
|||
.help("print the byte counts")
|
||||
.display_order(3),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::PRINT_LS_COLORS)
|
||||
.long("print-ls-colors")
|
||||
.help("output fully escaped colors for display")
|
||||
.display_order(4),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::FILE)
|
||||
.hide(true)
|
||||
|
@ -254,6 +273,7 @@ enum ParseState {
|
|||
Continue,
|
||||
Pass,
|
||||
}
|
||||
|
||||
use std::collections::HashMap;
|
||||
use uucore::{format_usage, InvalidEncodingHandling};
|
||||
|
||||
|
@ -262,11 +282,12 @@ where
|
|||
T: IntoIterator,
|
||||
T::Item: Borrow<str>,
|
||||
{
|
||||
// 1440 > $(dircolors | wc -m)
|
||||
let mut result = String::with_capacity(1440);
|
||||
// 1790 > $(dircolors | wc -m)
|
||||
let mut result = String::with_capacity(1790);
|
||||
match fmt {
|
||||
OutputFmt::Shell => result.push_str("LS_COLORS='"),
|
||||
OutputFmt::CShell => result.push_str("setenv LS_COLORS '"),
|
||||
OutputFmt::Display => (),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
|
@ -345,13 +366,25 @@ where
|
|||
}
|
||||
if state != ParseState::Pass {
|
||||
if key.starts_with('.') {
|
||||
result.push_str(format!("*{}={}:", key, val).as_str());
|
||||
if *fmt == OutputFmt::Display {
|
||||
result.push_str(format!("\x1b[{1}m*{0}\t{1}\x1b[0m\n", key, val).as_str());
|
||||
} else {
|
||||
result.push_str(format!("*{}={}:", key, val).as_str());
|
||||
}
|
||||
} else if key.starts_with('*') {
|
||||
result.push_str(format!("{}={}:", key, val).as_str());
|
||||
if *fmt == OutputFmt::Display {
|
||||
result.push_str(format!("\x1b[{1}m{0}\t{1}\x1b[0m\n", key, val).as_str());
|
||||
} else {
|
||||
result.push_str(format!("{}={}:", key, val).as_str());
|
||||
}
|
||||
} else if lower == "options" || lower == "color" || lower == "eightbit" {
|
||||
// Slackware only. Ignore
|
||||
} else if let Some(s) = table.get(lower.as_str()) {
|
||||
result.push_str(format!("{}={}:", s, val).as_str());
|
||||
if *fmt == OutputFmt::Display {
|
||||
result.push_str(format!("\x1b[{1}m{0}\t{1}\x1b[0m\n", s, val).as_str());
|
||||
} else {
|
||||
result.push_str(format!("{}={}:", s, val).as_str());
|
||||
}
|
||||
} else {
|
||||
return Err(format!(
|
||||
"{}:{}: unrecognized keyword {}",
|
||||
|
@ -367,6 +400,10 @@ where
|
|||
match fmt {
|
||||
OutputFmt::Shell => result.push_str("';\nexport LS_COLORS"),
|
||||
OutputFmt::CShell => result.push('\''),
|
||||
OutputFmt::Display => {
|
||||
// remove latest "\n"
|
||||
result.pop();
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
|
|
|
@ -93,7 +93,7 @@ fn tabstops_parse(s: &str) -> (RemainingMode, Vec<usize>) {
|
|||
|
||||
// Tab size must be positive.
|
||||
if num == 0 {
|
||||
crash!(1, "{}\n", "tab size cannot be 0");
|
||||
crash!(1, "tab size cannot be 0");
|
||||
}
|
||||
|
||||
// Tab sizes must be ascending.
|
||||
|
@ -132,8 +132,8 @@ struct Options {
|
|||
|
||||
impl Options {
|
||||
fn new(matches: &ArgMatches) -> Self {
|
||||
let (remaining_mode, tabstops) = match matches.value_of(options::TABS) {
|
||||
Some(s) => tabstops_parse(s),
|
||||
let (remaining_mode, tabstops) = match matches.values_of(options::TABS) {
|
||||
Some(s) => tabstops_parse(&s.collect::<Vec<&str>>().join(",")),
|
||||
None => (RemainingMode::None, vec![DEFAULT_TABSTOP]),
|
||||
};
|
||||
|
||||
|
@ -195,6 +195,7 @@ pub fn uu_app<'a>() -> Command<'a> {
|
|||
.short('t')
|
||||
.value_name("N, LIST")
|
||||
.takes_value(true)
|
||||
.multiple_occurrences(true)
|
||||
.help("have tabs N characters apart, not 8 or use comma separated list of explicit tab positions"),
|
||||
)
|
||||
.arg(
|
||||
|
|
|
@ -36,6 +36,7 @@ pub struct Settings {
|
|||
suffix: String,
|
||||
symbolic: bool,
|
||||
relative: bool,
|
||||
logical: bool,
|
||||
target_dir: Option<String>,
|
||||
no_target_dir: bool,
|
||||
no_dereference: bool,
|
||||
|
@ -121,9 +122,12 @@ const USAGE: &str = "\
|
|||
|
||||
mod options {
|
||||
pub const FORCE: &str = "force";
|
||||
//pub const DIRECTORY: &str = "directory";
|
||||
pub const INTERACTIVE: &str = "interactive";
|
||||
pub const NO_DEREFERENCE: &str = "no-dereference";
|
||||
pub const SYMBOLIC: &str = "symbolic";
|
||||
pub const LOGICAL: &str = "logical";
|
||||
pub const PHYSICAL: &str = "physical";
|
||||
pub const TARGET_DIRECTORY: &str = "target-directory";
|
||||
pub const NO_TARGET_DIRECTORY: &str = "no-target-directory";
|
||||
pub const RELATIVE: &str = "relative";
|
||||
|
@ -152,6 +156,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
.map(PathBuf::from)
|
||||
.collect();
|
||||
|
||||
let symbolic = matches.is_present(options::SYMBOLIC);
|
||||
|
||||
let overwrite_mode = if matches.is_present(options::FORCE) {
|
||||
OverwriteMode::Force
|
||||
} else if matches.is_present(options::INTERACTIVE) {
|
||||
|
@ -163,11 +169,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
let backup_mode = backup_control::determine_backup_mode(&matches)?;
|
||||
let backup_suffix = backup_control::determine_backup_suffix(&matches);
|
||||
|
||||
// When we have "-L" or "-L -P", false otherwise
|
||||
let logical = matches.is_present(options::LOGICAL);
|
||||
|
||||
let settings = Settings {
|
||||
overwrite: overwrite_mode,
|
||||
backup: backup_mode,
|
||||
suffix: backup_suffix,
|
||||
symbolic: matches.is_present(options::SYMBOLIC),
|
||||
symbolic,
|
||||
logical,
|
||||
relative: matches.is_present(options::RELATIVE),
|
||||
target_dir: matches
|
||||
.value_of(options::TARGET_DIRECTORY)
|
||||
|
@ -188,9 +198,12 @@ pub fn uu_app<'a>() -> Command<'a> {
|
|||
.infer_long_args(true)
|
||||
.arg(backup_control::arguments::backup())
|
||||
.arg(backup_control::arguments::backup_no_args())
|
||||
// TODO: opts.arg(
|
||||
// Arg::new(("d", "directory", "allow users with appropriate privileges to attempt \
|
||||
// to make hard links to directories");
|
||||
/*.arg(
|
||||
Arg::new(options::DIRECTORY)
|
||||
.short('d')
|
||||
.long(options::DIRECTORY)
|
||||
.help("allow users with appropriate privileges to attempt to make hard links to directories")
|
||||
)*/
|
||||
.arg(
|
||||
Arg::new(options::FORCE)
|
||||
.short('f')
|
||||
|
@ -212,15 +225,24 @@ pub fn uu_app<'a>() -> Command<'a> {
|
|||
symbolic link to a directory",
|
||||
),
|
||||
)
|
||||
// TODO: opts.arg(
|
||||
// Arg::new(("L", "logical", "dereference TARGETs that are symbolic links");
|
||||
//
|
||||
// TODO: opts.arg(
|
||||
// Arg::new(("P", "physical", "make hard links directly to symbolic links");
|
||||
.arg(
|
||||
Arg::new(options::LOGICAL)
|
||||
.short('L')
|
||||
.long(options::LOGICAL)
|
||||
.help("dereference TARGETs that are symbolic links")
|
||||
.overrides_with(options::PHYSICAL),
|
||||
)
|
||||
.arg(
|
||||
// Not implemented yet
|
||||
Arg::new(options::PHYSICAL)
|
||||
.short('P')
|
||||
.long(options::PHYSICAL)
|
||||
.help("make hard links directly to symbolic links"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::SYMBOLIC)
|
||||
.short('s')
|
||||
.long("symbolic")
|
||||
.long(options::SYMBOLIC)
|
||||
.help("make symbolic links instead of hard links")
|
||||
// override added for https://github.com/uutils/coreutils/issues/2359
|
||||
.overrides_with(options::SYMBOLIC),
|
||||
|
@ -446,7 +468,15 @@ fn link(src: &Path, dst: &Path, settings: &Settings) -> UResult<()> {
|
|||
if settings.symbolic {
|
||||
symlink(&source, dst)?;
|
||||
} else {
|
||||
fs::hard_link(&source, dst)?;
|
||||
let p = if settings.logical && is_symlink(&source) {
|
||||
// if we want to have an hard link,
|
||||
// source is a symlink and -L is passed
|
||||
// we want to resolve the symlink to create the hardlink
|
||||
std::fs::canonicalize(&source)?
|
||||
} else {
|
||||
source.to_path_buf()
|
||||
};
|
||||
fs::hard_link(&p, dst)?;
|
||||
}
|
||||
|
||||
if settings.verbose {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
// spell-checker:ignore (paths) GPGHome
|
||||
|
||||
use clap::{crate_version, Arg, Command};
|
||||
use clap::{crate_version, Arg, ArgMatches, Command};
|
||||
use uucore::display::{println_verbatim, Quotable};
|
||||
use uucore::error::{FromIo, UError, UResult};
|
||||
use uucore::format_usage;
|
||||
|
@ -17,7 +17,7 @@ use std::env;
|
|||
use std::error::Error;
|
||||
use std::fmt::Display;
|
||||
use std::iter;
|
||||
use std::path::{is_separator, Path, PathBuf, MAIN_SEPARATOR};
|
||||
use std::path::{Path, PathBuf, MAIN_SEPARATOR};
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::fs;
|
||||
|
@ -89,86 +89,229 @@ impl Display for MkTempError {
|
|||
}
|
||||
}
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let matches = uu_app().get_matches_from(args);
|
||||
/// Options parsed from the command-line.
|
||||
///
|
||||
/// This provides a layer of indirection between the application logic
|
||||
/// and the argument parsing library `clap`, allowing each to vary
|
||||
/// independently.
|
||||
struct Options {
|
||||
/// Whether to create a temporary directory instead of a file.
|
||||
directory: bool,
|
||||
|
||||
let template = matches.value_of(ARG_TEMPLATE).unwrap();
|
||||
let tmpdir = matches.value_of(OPT_TMPDIR).unwrap_or_default();
|
||||
/// Whether to just print the name of a file that would have been created.
|
||||
dry_run: bool,
|
||||
|
||||
// Treat the template string as a path to get the directory
|
||||
// containing the last component.
|
||||
let path = PathBuf::from(template);
|
||||
/// Whether to suppress file creation error messages.
|
||||
quiet: bool,
|
||||
|
||||
let (template, tmpdir) = if matches.is_present(OPT_TMPDIR)
|
||||
&& !PathBuf::from(tmpdir).is_dir() // if a temp dir is provided, it must be an actual path
|
||||
&& tmpdir.contains("XXX")
|
||||
// If this is a template, it has to contain at least 3 X
|
||||
&& template == DEFAULT_TEMPLATE
|
||||
// That means that clap does not think we provided a template
|
||||
{
|
||||
// Special case to workaround a limitation of clap when doing
|
||||
// mktemp --tmpdir apt-key-gpghome.XXX
|
||||
// The behavior should be
|
||||
// mktemp --tmpdir $TMPDIR apt-key-gpghome.XX
|
||||
// As --tmpdir is empty
|
||||
/// The directory in which to create the temporary file.
|
||||
///
|
||||
/// If `None`, the file will be created in the current directory.
|
||||
tmpdir: Option<String>,
|
||||
|
||||
/// The suffix to append to the temporary file, if any.
|
||||
suffix: Option<String>,
|
||||
|
||||
/// Whether to treat the template argument as a single file path component.
|
||||
treat_as_template: bool,
|
||||
|
||||
/// The template to use for the name of the temporary file.
|
||||
template: String,
|
||||
}
|
||||
|
||||
/// Decide whether the argument to `--tmpdir` should actually be the template.
|
||||
///
|
||||
/// This function is required to work around a limitation of `clap`,
|
||||
/// the command-line argument parsing library. In case the command
|
||||
/// line is
|
||||
///
|
||||
/// ```sh
|
||||
/// mktemp --tmpdir XXX
|
||||
/// ```
|
||||
///
|
||||
/// the program should behave like
|
||||
///
|
||||
/// ```sh
|
||||
/// mktemp --tmpdir=${TMPDIR:-/tmp} XXX
|
||||
/// ```
|
||||
///
|
||||
/// However, `clap` thinks that `XXX` is the value of the `--tmpdir`
|
||||
/// option. This function returns `true` in this case and `false`
|
||||
/// in all other cases.
|
||||
fn is_tmpdir_argument_actually_the_template(matches: &ArgMatches) -> bool {
|
||||
if !matches.is_present(ARG_TEMPLATE) {
|
||||
if let Some(tmpdir) = matches.value_of(OPT_TMPDIR) {
|
||||
if !Path::new(tmpdir).is_dir() && tmpdir.contains("XXX") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
impl Options {
|
||||
fn from(matches: &ArgMatches) -> Self {
|
||||
// Special case to work around a limitation of `clap`; see
|
||||
// `is_tmpdir_argument_actually_the_template()` for more
|
||||
// information.
|
||||
//
|
||||
// Fixed in clap 3
|
||||
// See https://github.com/clap-rs/clap/pull/1587
|
||||
let tmp = env::temp_dir();
|
||||
(tmpdir, tmp)
|
||||
} else if !matches.is_present(OPT_TMPDIR) {
|
||||
// In this case, the command line was `mktemp -t XXX`, so we
|
||||
// treat the argument `XXX` as though it were a filename
|
||||
// regardless of whether it has path separators in it.
|
||||
if matches.is_present(OPT_T) {
|
||||
let tmp = env::temp_dir();
|
||||
(template, tmp)
|
||||
// In this case, the command line was `mktemp XXX`, so we need
|
||||
// to parse out the parent directory and the filename from the
|
||||
// argument `XXX`, since it may be include path separators.
|
||||
let (tmpdir, template) = if is_tmpdir_argument_actually_the_template(matches) {
|
||||
let tmpdir = Some(env::temp_dir().display().to_string());
|
||||
let template = matches.value_of(OPT_TMPDIR).unwrap().to_string();
|
||||
(tmpdir, template)
|
||||
} else {
|
||||
let tmp = match path.parent() {
|
||||
None => PathBuf::from("."),
|
||||
Some(d) => PathBuf::from(d),
|
||||
};
|
||||
let filename = path.file_name();
|
||||
let template = filename.unwrap().to_str().unwrap();
|
||||
// If the command line was `mktemp aXXX/b`, then we will
|
||||
// find that `tmp`, which is the result of getting the
|
||||
// parent when treating the argument as a path, contains
|
||||
// at least three consecutive Xs. This means that there
|
||||
// was a path separator in the suffix, which is not
|
||||
// allowed.
|
||||
if tmp.display().to_string().contains("XXX") {
|
||||
return Err(MkTempError::SuffixContainsDirSeparator(format!(
|
||||
"{}{}",
|
||||
MAIN_SEPARATOR, template
|
||||
))
|
||||
.into());
|
||||
}
|
||||
(template, tmp)
|
||||
let tmpdir = matches.value_of(OPT_TMPDIR).map(String::from);
|
||||
let template = matches
|
||||
.value_of(ARG_TEMPLATE)
|
||||
.unwrap_or(DEFAULT_TEMPLATE)
|
||||
.to_string();
|
||||
(tmpdir, template)
|
||||
};
|
||||
Self {
|
||||
directory: matches.is_present(OPT_DIRECTORY),
|
||||
dry_run: matches.is_present(OPT_DRY_RUN),
|
||||
quiet: matches.is_present(OPT_QUIET),
|
||||
tmpdir,
|
||||
suffix: matches.value_of(OPT_SUFFIX).map(String::from),
|
||||
treat_as_template: matches.is_present(OPT_T),
|
||||
template,
|
||||
}
|
||||
} else {
|
||||
(template, PathBuf::from(tmpdir))
|
||||
};
|
||||
|
||||
let make_dir = matches.is_present(OPT_DIRECTORY);
|
||||
let dry_run = matches.is_present(OPT_DRY_RUN);
|
||||
let suppress_file_err = matches.is_present(OPT_QUIET);
|
||||
|
||||
// If `--tmpdir` is given, the template cannot be an absolute
|
||||
// path. For example, `mktemp --tmpdir=a /XXX` is not allowed.
|
||||
if matches.is_present(OPT_TMPDIR) && PathBuf::from(template).is_absolute() {
|
||||
return Err(MkTempError::InvalidTemplate(template.into()).into());
|
||||
}
|
||||
}
|
||||
|
||||
let (prefix, rand, suffix) = parse_template(template, matches.value_of(OPT_SUFFIX))?;
|
||||
/// Parameters that control the path to and name of the temporary file.
|
||||
///
|
||||
/// The temporary file will be created at
|
||||
///
|
||||
/// ```text
|
||||
/// {directory}/{prefix}{XXX}{suffix}
|
||||
/// ```
|
||||
///
|
||||
/// where `{XXX}` is a sequence of random characters whose length is
|
||||
/// `num_rand_chars`.
|
||||
struct Params {
|
||||
/// The directory that will contain the temporary file.
|
||||
directory: String,
|
||||
|
||||
/// The (non-random) prefix of the temporary file.
|
||||
prefix: String,
|
||||
|
||||
/// The number of random characters in the name of the temporary file.
|
||||
num_rand_chars: usize,
|
||||
|
||||
/// The (non-random) suffix of the temporary file.
|
||||
suffix: String,
|
||||
}
|
||||
|
||||
impl Params {
|
||||
fn from(options: Options) -> Result<Self, MkTempError> {
|
||||
// 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.
|
||||
let i = match options.template.find("XXX") {
|
||||
None => {
|
||||
let s = match options.suffix {
|
||||
None => options.template,
|
||||
Some(s) => format!("{}{}", options.template, s),
|
||||
};
|
||||
return Err(MkTempError::TooFewXs(s));
|
||||
}
|
||||
Some(i) => i,
|
||||
};
|
||||
let j = options.template.rfind("XXX").unwrap() + 3;
|
||||
|
||||
// Combine the directory given as an option and the prefix of the template.
|
||||
//
|
||||
// For example, if `tmpdir` is "a/b" and the template is "c/dXXX",
|
||||
// then `prefix` is "a/b/c/d".
|
||||
let tmpdir = options.tmpdir;
|
||||
let prefix_from_option = tmpdir.clone().unwrap_or_else(|| "".to_string());
|
||||
let prefix_from_template = &options.template[..i];
|
||||
let prefix = Path::new(&prefix_from_option)
|
||||
.join(prefix_from_template)
|
||||
.display()
|
||||
.to_string();
|
||||
if options.treat_as_template && prefix.contains(MAIN_SEPARATOR) {
|
||||
return Err(MkTempError::PrefixContainsDirSeparator(options.template));
|
||||
}
|
||||
if tmpdir.is_some() && Path::new(prefix_from_template).is_absolute() {
|
||||
return Err(MkTempError::InvalidTemplate(options.template));
|
||||
}
|
||||
|
||||
// Split the parent directory from the file part of the prefix.
|
||||
//
|
||||
// For example, if `prefix` is "a/b/c/d", then `directory` is
|
||||
// "a/b/c" is `prefix` gets reassigned to "d".
|
||||
let (directory, prefix) = if prefix.ends_with(MAIN_SEPARATOR) {
|
||||
(prefix, "".to_string())
|
||||
} else {
|
||||
let path = Path::new(&prefix);
|
||||
let directory = match path.parent() {
|
||||
None => String::new(),
|
||||
Some(d) => d.display().to_string(),
|
||||
};
|
||||
let prefix = match path.file_name() {
|
||||
None => String::new(),
|
||||
Some(f) => f.to_str().unwrap().to_string(),
|
||||
};
|
||||
(directory, prefix)
|
||||
};
|
||||
|
||||
// Combine the suffix from the template with the suffix given as an option.
|
||||
//
|
||||
// For example, if the suffix command-line argument is ".txt" and
|
||||
// the template is "XXXabc", then `suffix` is "abc.txt".
|
||||
let suffix_from_option = options.suffix.unwrap_or_else(|| "".to_string());
|
||||
let suffix_from_template = &options.template[j..];
|
||||
let suffix = format!("{}{}", suffix_from_template, suffix_from_option);
|
||||
if suffix.contains(MAIN_SEPARATOR) {
|
||||
return Err(MkTempError::SuffixContainsDirSeparator(suffix));
|
||||
}
|
||||
if !suffix_from_template.is_empty() && !suffix_from_option.is_empty() {
|
||||
return Err(MkTempError::MustEndInX(options.template));
|
||||
}
|
||||
|
||||
// The number of random characters in the template.
|
||||
//
|
||||
// For example, if the template is "abcXXXXyz", then the number of
|
||||
// random characters is four.
|
||||
let num_rand_chars = j - i;
|
||||
|
||||
Ok(Self {
|
||||
directory,
|
||||
prefix,
|
||||
num_rand_chars,
|
||||
suffix,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let matches = uu_app().try_get_matches_from(args)?;
|
||||
|
||||
// Parse command-line options into a format suitable for the
|
||||
// application logic.
|
||||
let options = Options::from(&matches);
|
||||
let dry_run = options.dry_run;
|
||||
let suppress_file_err = options.quiet;
|
||||
let make_dir = options.directory;
|
||||
|
||||
// Parse file path parameters from the command-line options.
|
||||
let Params {
|
||||
directory: tmpdir,
|
||||
prefix,
|
||||
num_rand_chars: rand,
|
||||
suffix,
|
||||
} = Params::from(options)?;
|
||||
|
||||
// Create the temporary file or directory, or simulate creating it.
|
||||
let res = if dry_run {
|
||||
dry_exec(tmpdir, prefix, rand, suffix)
|
||||
dry_exec(&tmpdir, &prefix, rand, &suffix)
|
||||
} else {
|
||||
exec(&tmpdir, prefix, rand, suffix, make_dir)
|
||||
exec(&tmpdir, &prefix, rand, &suffix, make_dir)
|
||||
};
|
||||
|
||||
if suppress_file_err {
|
||||
|
@ -234,73 +377,10 @@ pub fn uu_app<'a>() -> Command<'a> {
|
|||
.multiple_occurrences(false)
|
||||
.takes_value(true)
|
||||
.max_values(1)
|
||||
.default_value(DEFAULT_TEMPLATE),
|
||||
)
|
||||
}
|
||||
|
||||
/// Parse a template string into prefix, suffix, and random components.
|
||||
///
|
||||
/// `temp` is the template string, with three or more consecutive `X`s
|
||||
/// representing a placeholder for randomly generated characters (for
|
||||
/// example, `"abc_XXX.txt"`). If `temp` ends in an `X`, then a suffix
|
||||
/// can be specified by `suffix` instead.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// * If there are fewer than three consecutive `X`s in `temp`.
|
||||
/// * If `suffix` is a [`Some`] object but `temp` does not end in `X`.
|
||||
/// * If the suffix (specified either way) contains a path separator.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// assert_eq!(parse_template("XXX", None).unwrap(), ("", 3, ""));
|
||||
/// assert_eq!(parse_template("abcXXX", None).unwrap(), ("abc", 3, ""));
|
||||
/// assert_eq!(parse_template("XXXdef", None).unwrap(), ("", 3, "def"));
|
||||
/// assert_eq!(parse_template("abcXXXdef", None).unwrap(), ("abc", 3, "def"));
|
||||
/// ```
|
||||
fn parse_template<'a>(
|
||||
temp: &'a str,
|
||||
suffix: Option<&'a str>,
|
||||
) -> Result<(&'a str, usize, &'a str), MkTempError> {
|
||||
let right = match temp.rfind('X') {
|
||||
Some(r) => r + 1,
|
||||
None => return Err(MkTempError::TooFewXs(temp.into())),
|
||||
};
|
||||
let left = temp[..right].rfind(|c| c != 'X').map_or(0, |i| i + 1);
|
||||
let prefix = &temp[..left];
|
||||
let rand = right - left;
|
||||
|
||||
if rand < 3 {
|
||||
let s = match suffix {
|
||||
None => temp.into(),
|
||||
Some(s) => format!("{}{}", temp, s),
|
||||
};
|
||||
return Err(MkTempError::TooFewXs(s));
|
||||
}
|
||||
|
||||
let mut suf = &temp[right..];
|
||||
|
||||
if let Some(s) = suffix {
|
||||
if suf.is_empty() {
|
||||
suf = s;
|
||||
} else {
|
||||
return Err(MkTempError::MustEndInX(temp.into()));
|
||||
}
|
||||
};
|
||||
|
||||
if prefix.chars().any(is_separator) {
|
||||
return Err(MkTempError::PrefixContainsDirSeparator(temp.into()));
|
||||
}
|
||||
|
||||
if suf.chars().any(is_separator) {
|
||||
return Err(MkTempError::SuffixContainsDirSeparator(suf.into()));
|
||||
}
|
||||
|
||||
Ok((prefix, rand, suf))
|
||||
}
|
||||
|
||||
pub fn dry_exec(mut tmpdir: PathBuf, prefix: &str, rand: usize, suffix: &str) -> UResult<()> {
|
||||
pub fn dry_exec(tmpdir: &str, prefix: &str, rand: usize, suffix: &str) -> UResult<()> {
|
||||
let len = prefix.len() + suffix.len() + rand;
|
||||
let mut buf = Vec::with_capacity(len);
|
||||
buf.extend(prefix.as_bytes());
|
||||
|
@ -320,11 +400,11 @@ pub fn dry_exec(mut tmpdir: PathBuf, prefix: &str, rand: usize, suffix: &str) ->
|
|||
}
|
||||
// We guarantee utf8.
|
||||
let buf = String::from_utf8(buf).unwrap();
|
||||
tmpdir.push(buf);
|
||||
let tmpdir = Path::new(tmpdir).join(buf);
|
||||
println_verbatim(tmpdir).map_err_context(|| "failed to print directory name".to_owned())
|
||||
}
|
||||
|
||||
fn exec(dir: &Path, prefix: &str, rand: usize, suffix: &str, make_dir: bool) -> UResult<()> {
|
||||
fn exec(dir: &str, prefix: &str, rand: usize, suffix: &str, make_dir: bool) -> UResult<()> {
|
||||
let context = || {
|
||||
format!(
|
||||
"failed to create file via template '{}{}{}'",
|
||||
|
@ -366,42 +446,7 @@ fn exec(dir: &Path, prefix: &str, rand: usize, suffix: &str, make_dir: bool) ->
|
|||
// the absolute path and we need to return a filename that matches
|
||||
// the template given on the command-line which might be a
|
||||
// relative path.
|
||||
let mut path = dir.to_path_buf();
|
||||
path.push(filename);
|
||||
let path = Path::new(dir).join(filename);
|
||||
|
||||
println_verbatim(path).map_err_context(|| "failed to print directory name".to_owned())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::parse_template;
|
||||
|
||||
#[test]
|
||||
fn test_parse_template_no_suffix() {
|
||||
assert_eq!(parse_template("XXX", None).unwrap(), ("", 3, ""));
|
||||
assert_eq!(parse_template("abcXXX", None).unwrap(), ("abc", 3, ""));
|
||||
assert_eq!(parse_template("XXXdef", None).unwrap(), ("", 3, "def"));
|
||||
assert_eq!(
|
||||
parse_template("abcXXXdef", None).unwrap(),
|
||||
("abc", 3, "def")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_template_suffix() {
|
||||
assert_eq!(parse_template("XXX", Some("def")).unwrap(), ("", 3, "def"));
|
||||
assert_eq!(
|
||||
parse_template("abcXXX", Some("def")).unwrap(),
|
||||
("abc", 3, "def")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_template_errors() {
|
||||
assert!(parse_template("a/bXXX", None).is_err());
|
||||
assert!(parse_template("XXXa/b", None).is_err());
|
||||
assert!(parse_template("XX", None).is_err());
|
||||
assert!(parse_template("XXXabc", Some("def")).is_err());
|
||||
assert!(parse_template("XXX", Some("a/b")).is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
// * For the full copyright and license information, please view the LICENSE
|
||||
// * file that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore (ToDO) PREFIXaa nbbbb ncccc
|
||||
// spell-checker:ignore (ToDO) PREFIXaa PREFIXab nbbbb ncccc
|
||||
|
||||
mod filenames;
|
||||
mod number;
|
||||
|
@ -47,7 +47,7 @@ static ARG_PREFIX: &str = "prefix";
|
|||
|
||||
const USAGE: &str = "{} [OPTION]... [INPUT [PREFIX]]";
|
||||
const AFTER_HELP: &str = "\
|
||||
Output fixed-size pieces of INPUT to PREFIXaa, PREFIX ab, ...; default \
|
||||
Output fixed-size pieces of INPUT to PREFIXaa, PREFIXab, ...; default \
|
||||
size is 1000, and default PREFIX is 'x'. With no INPUT, or when INPUT is \
|
||||
-, read standard input.";
|
||||
|
||||
|
@ -74,6 +74,7 @@ pub fn uu_app<'a>() -> Command<'a> {
|
|||
.short('b')
|
||||
.long(OPT_BYTES)
|
||||
.takes_value(true)
|
||||
.value_name("SIZE")
|
||||
.help("put SIZE bytes per output file"),
|
||||
)
|
||||
.arg(
|
||||
|
@ -81,6 +82,7 @@ pub fn uu_app<'a>() -> Command<'a> {
|
|||
.short('C')
|
||||
.long(OPT_LINE_BYTES)
|
||||
.takes_value(true)
|
||||
.value_name("SIZE")
|
||||
.default_value("2")
|
||||
.help("put at most SIZE bytes of lines per output file"),
|
||||
)
|
||||
|
@ -89,6 +91,7 @@ pub fn uu_app<'a>() -> Command<'a> {
|
|||
.short('l')
|
||||
.long(OPT_LINES)
|
||||
.takes_value(true)
|
||||
.value_name("NUMBER")
|
||||
.default_value("1000")
|
||||
.help("put NUMBER lines/records per output file"),
|
||||
)
|
||||
|
@ -97,6 +100,7 @@ pub fn uu_app<'a>() -> Command<'a> {
|
|||
.short('n')
|
||||
.long(OPT_NUMBER)
|
||||
.takes_value(true)
|
||||
.value_name("CHUNKS")
|
||||
.help("generate CHUNKS output files; see explanation below"),
|
||||
)
|
||||
// rest of the arguments
|
||||
|
@ -104,8 +108,9 @@ pub fn uu_app<'a>() -> Command<'a> {
|
|||
Arg::new(OPT_ADDITIONAL_SUFFIX)
|
||||
.long(OPT_ADDITIONAL_SUFFIX)
|
||||
.takes_value(true)
|
||||
.value_name("SUFFIX")
|
||||
.default_value("")
|
||||
.help("additional suffix to append to output file names"),
|
||||
.help("additional SUFFIX to append to output file names"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(OPT_FILTER)
|
||||
|
@ -137,6 +142,7 @@ pub fn uu_app<'a>() -> Command<'a> {
|
|||
.short('a')
|
||||
.long(OPT_SUFFIX_LENGTH)
|
||||
.takes_value(true)
|
||||
.value_name("N")
|
||||
.default_value(OPT_DEFAULT_SUFFIX_LENGTH)
|
||||
.help("use suffixes of length N (default 2)"),
|
||||
)
|
||||
|
|
|
@ -360,6 +360,47 @@ fn test_total() {
|
|||
assert_eq!(computed_total_avail, reported_total_avail);
|
||||
}
|
||||
|
||||
/// Test that the "total" label appears in the correct column.
|
||||
///
|
||||
/// The "total" label should appear in the "source" column, or in the
|
||||
/// "target" column if "source" is not visible.
|
||||
#[test]
|
||||
fn test_total_label_in_correct_column() {
|
||||
let output = new_ucmd!()
|
||||
.args(&["--output=source", "--total", "."])
|
||||
.succeeds()
|
||||
.stdout_move_str();
|
||||
let last_line = output.lines().last().unwrap();
|
||||
assert_eq!(last_line.trim(), "total");
|
||||
|
||||
let output = new_ucmd!()
|
||||
.args(&["--output=target", "--total", "."])
|
||||
.succeeds()
|
||||
.stdout_move_str();
|
||||
let last_line = output.lines().last().unwrap();
|
||||
assert_eq!(last_line.trim(), "total");
|
||||
|
||||
let output = new_ucmd!()
|
||||
.args(&["--output=source,target", "--total", "."])
|
||||
.succeeds()
|
||||
.stdout_move_str();
|
||||
let last_line = output.lines().last().unwrap();
|
||||
assert_eq!(
|
||||
last_line.split_whitespace().collect::<Vec<&str>>(),
|
||||
vec!["total", "-"]
|
||||
);
|
||||
|
||||
let output = new_ucmd!()
|
||||
.args(&["--output=target,source", "--total", "."])
|
||||
.succeeds()
|
||||
.stdout_move_str();
|
||||
let last_line = output.lines().last().unwrap();
|
||||
assert_eq!(
|
||||
last_line.split_whitespace().collect::<Vec<&str>>(),
|
||||
vec!["-", "total"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_use_percentage() {
|
||||
let output = new_ucmd!()
|
||||
|
@ -421,7 +462,7 @@ fn test_default_block_size() {
|
|||
.arg("--output=size")
|
||||
.succeeds()
|
||||
.stdout_move_str();
|
||||
let header = output.lines().next().unwrap().to_string();
|
||||
let header = output.lines().next().unwrap().trim().to_string();
|
||||
|
||||
assert_eq!(header, "1K-blocks");
|
||||
|
||||
|
@ -430,7 +471,7 @@ fn test_default_block_size() {
|
|||
.env("POSIXLY_CORRECT", "1")
|
||||
.succeeds()
|
||||
.stdout_move_str();
|
||||
let header = output.lines().next().unwrap().to_string();
|
||||
let header = output.lines().next().unwrap().trim().to_string();
|
||||
|
||||
assert_eq!(header, "512B-blocks");
|
||||
}
|
||||
|
@ -445,6 +486,7 @@ fn test_default_block_size_in_posix_portability_mode() {
|
|||
.split_whitespace()
|
||||
.nth(1)
|
||||
.unwrap()
|
||||
.trim()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
|
@ -466,7 +508,7 @@ fn test_block_size_1024() {
|
|||
.args(&["-B", &format!("{}", block_size), "--output=size"])
|
||||
.succeeds()
|
||||
.stdout_move_str();
|
||||
output.lines().next().unwrap().to_string()
|
||||
output.lines().next().unwrap().trim().to_string()
|
||||
}
|
||||
|
||||
assert_eq!(get_header(1024), "1K-blocks");
|
||||
|
@ -490,7 +532,7 @@ fn test_block_size_with_suffix() {
|
|||
.args(&["-B", block_size, "--output=size"])
|
||||
.succeeds()
|
||||
.stdout_move_str();
|
||||
output.lines().next().unwrap().to_string()
|
||||
output.lines().next().unwrap().trim().to_string()
|
||||
}
|
||||
|
||||
assert_eq!(get_header("K"), "1K-blocks");
|
||||
|
@ -522,6 +564,7 @@ fn test_block_size_in_posix_portability_mode() {
|
|||
.split_whitespace()
|
||||
.nth(1)
|
||||
.unwrap()
|
||||
.trim()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
|
@ -540,7 +583,7 @@ fn test_block_size_from_env() {
|
|||
.env(env_var, env_value)
|
||||
.succeeds()
|
||||
.stdout_move_str();
|
||||
output.lines().next().unwrap().to_string()
|
||||
output.lines().next().unwrap().trim().to_string()
|
||||
}
|
||||
|
||||
assert_eq!(get_header("DF_BLOCK_SIZE", "111"), "111B-blocks");
|
||||
|
@ -559,7 +602,7 @@ fn test_block_size_from_env_precedences() {
|
|||
.env(k2, v2)
|
||||
.succeeds()
|
||||
.stdout_move_str();
|
||||
output.lines().next().unwrap().to_string()
|
||||
output.lines().next().unwrap().trim().to_string()
|
||||
}
|
||||
|
||||
let df_block_size = ("DF_BLOCK_SIZE", "111");
|
||||
|
@ -578,7 +621,7 @@ fn test_precedence_of_block_size_arg_over_env() {
|
|||
.env("DF_BLOCK_SIZE", "111")
|
||||
.succeeds()
|
||||
.stdout_move_str();
|
||||
let header = output.lines().next().unwrap().to_string();
|
||||
let header = output.lines().next().unwrap().trim().to_string();
|
||||
|
||||
assert_eq!(header, "999B-blocks");
|
||||
}
|
||||
|
@ -592,7 +635,7 @@ fn test_invalid_block_size_from_env() {
|
|||
.env("DF_BLOCK_SIZE", "invalid")
|
||||
.succeeds()
|
||||
.stdout_move_str();
|
||||
let header = output.lines().next().unwrap().to_string();
|
||||
let header = output.lines().next().unwrap().trim().to_string();
|
||||
|
||||
assert_eq!(header, default_block_size_header);
|
||||
|
||||
|
@ -602,7 +645,7 @@ fn test_invalid_block_size_from_env() {
|
|||
.env("BLOCK_SIZE", "222")
|
||||
.succeeds()
|
||||
.stdout_move_str();
|
||||
let header = output.lines().next().unwrap().to_string();
|
||||
let header = output.lines().next().unwrap().trim().to_string();
|
||||
|
||||
assert_eq!(header, default_block_size_header);
|
||||
}
|
||||
|
@ -626,6 +669,7 @@ fn test_ignore_block_size_from_env_in_posix_portability_mode() {
|
|||
.split_whitespace()
|
||||
.nth(1)
|
||||
.unwrap()
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
assert_eq!(header, default_block_size_header);
|
||||
|
|
|
@ -62,6 +62,14 @@ fn test_internal_db() {
|
|||
.stdout_is_fixture("internal.expected");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ls_colors() {
|
||||
new_ucmd!()
|
||||
.arg("--print-ls-colors")
|
||||
.run()
|
||||
.stdout_is_fixture("ls_colors.expected");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bash_default() {
|
||||
new_ucmd!()
|
||||
|
@ -109,6 +117,18 @@ fn test_exclusive_option() {
|
|||
.arg("-cp")
|
||||
.fails()
|
||||
.stderr_contains("mutually exclusive");
|
||||
new_ucmd!()
|
||||
.args(&["-b", "--print-ls-colors"])
|
||||
.fails()
|
||||
.stderr_contains("mutually exclusive");
|
||||
new_ucmd!()
|
||||
.args(&["-c", "--print-ls-colors"])
|
||||
.fails()
|
||||
.stderr_contains("mutually exclusive");
|
||||
new_ucmd!()
|
||||
.args(&["-p", "--print-ls-colors"])
|
||||
.fails()
|
||||
.stderr_contains("mutually exclusive");
|
||||
}
|
||||
|
||||
fn test_helper(file_name: &str, term: &str) {
|
||||
|
|
|
@ -73,6 +73,15 @@ fn test_tabs_mixed_style_list() {
|
|||
.stdout_is("a b c d e");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_tabs_args() {
|
||||
new_ucmd!()
|
||||
.args(&["--tabs=3", "--tabs=6", "--tabs=9"])
|
||||
.pipe_in("a\tb\tc\td\te")
|
||||
.succeeds()
|
||||
.stdout_is("a b c d e");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tabs_empty_string() {
|
||||
new_ucmd!()
|
||||
|
|
|
@ -650,3 +650,55 @@ fn test_backup_force() {
|
|||
// we should have the same content as b as we had time to do a backup
|
||||
assert_eq!(at.read("b~"), "b2\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hard_logical() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let file_a = "file1";
|
||||
let link = "symlink1";
|
||||
let target = "hard-to-a";
|
||||
let target2 = "hard-to-a2";
|
||||
at.touch(file_a);
|
||||
at.symlink_file(file_a, link);
|
||||
|
||||
ucmd.args(&["-P", "-L", link, target]);
|
||||
assert!(!at.is_symlink(target));
|
||||
|
||||
ucmd.args(&["-P", "-L", "-s", link, target2]);
|
||||
assert!(!at.is_symlink(target2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hard_logical_non_exit_fail() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
|
||||
let file_a = "/no-such-dir";
|
||||
let link = "hard-to-dangle";
|
||||
|
||||
scene.ucmd().args(&["-s", file_a]);
|
||||
assert!(!at.is_symlink("no-such-dir"));
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.args(&["-L", "no-such-dir", link])
|
||||
.fails()
|
||||
.stderr_contains("failed to link 'no-such-dir'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hard_logical_dir_fail() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
let dir = "d";
|
||||
at.mkdir(dir);
|
||||
let target = "link-to-dir";
|
||||
|
||||
scene.ucmd().args(&["-s", dir, target]);
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.args(&["-L", target, "hard-to-dir-link"])
|
||||
.fails()
|
||||
.stderr_contains("failed to link 'link-to-dir'");
|
||||
}
|
||||
|
|
|
@ -519,6 +519,7 @@ fn test_directory_permissions() {
|
|||
/// Test that a template with a path separator is invalid.
|
||||
#[test]
|
||||
fn test_template_path_separator() {
|
||||
#[cfg(not(windows))]
|
||||
new_ucmd!()
|
||||
.args(&["-t", "a/bXXX"])
|
||||
.fails()
|
||||
|
@ -526,6 +527,14 @@ fn test_template_path_separator() {
|
|||
"mktemp: invalid template, {}, contains directory separator\n",
|
||||
"a/bXXX".quote()
|
||||
));
|
||||
#[cfg(windows)]
|
||||
new_ucmd!()
|
||||
.args(&["-t", r"a\bXXX"])
|
||||
.fails()
|
||||
.stderr_only(format!(
|
||||
"mktemp: invalid template, {}, contains directory separator\n",
|
||||
r"a\bXXX".quote()
|
||||
));
|
||||
}
|
||||
|
||||
/// Test that a suffix with a path separator is invalid.
|
||||
|
@ -558,3 +567,8 @@ fn test_too_few_xs_suffix_directory() {
|
|||
.fails()
|
||||
.stderr_only("mktemp: too few X's in template 'aXXX'\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_too_many_arguments() {
|
||||
new_ucmd!().args(&["-q", "a", "b"]).fails().code_is(1);
|
||||
}
|
||||
|
|
148
tests/fixtures/dircolors/ls_colors.expected
vendored
Normal file
148
tests/fixtures/dircolors/ls_colors.expected
vendored
Normal file
|
@ -0,0 +1,148 @@
|
|||
[0mrs 0[0m
|
||||
[01;34mdi 01;34[0m
|
||||
[01;36mln 01;36[0m
|
||||
[00mmh 00[0m
|
||||
[40;33mpi 40;33[0m
|
||||
[01;35mso 01;35[0m
|
||||
[01;35mdo 01;35[0m
|
||||
[40;33;01mbd 40;33;01[0m
|
||||
[40;33;01mcd 40;33;01[0m
|
||||
[40;31;01mor 40;31;01[0m
|
||||
[00mmi 00[0m
|
||||
[37;41msu 37;41[0m
|
||||
[30;43msg 30;43[0m
|
||||
[00mca 00[0m
|
||||
[30;42mtw 30;42[0m
|
||||
[34;42mow 34;42[0m
|
||||
[37;44mst 37;44[0m
|
||||
[01;32mex 01;32[0m
|
||||
[01;31m*.tar 01;31[0m
|
||||
[01;31m*.tgz 01;31[0m
|
||||
[01;31m*.arc 01;31[0m
|
||||
[01;31m*.arj 01;31[0m
|
||||
[01;31m*.taz 01;31[0m
|
||||
[01;31m*.lha 01;31[0m
|
||||
[01;31m*.lz4 01;31[0m
|
||||
[01;31m*.lzh 01;31[0m
|
||||
[01;31m*.lzma 01;31[0m
|
||||
[01;31m*.tlz 01;31[0m
|
||||
[01;31m*.txz 01;31[0m
|
||||
[01;31m*.tzo 01;31[0m
|
||||
[01;31m*.t7z 01;31[0m
|
||||
[01;31m*.zip 01;31[0m
|
||||
[01;31m*.z 01;31[0m
|
||||
[01;31m*.dz 01;31[0m
|
||||
[01;31m*.gz 01;31[0m
|
||||
[01;31m*.lrz 01;31[0m
|
||||
[01;31m*.lz 01;31[0m
|
||||
[01;31m*.lzo 01;31[0m
|
||||
[01;31m*.xz 01;31[0m
|
||||
[01;31m*.zst 01;31[0m
|
||||
[01;31m*.tzst 01;31[0m
|
||||
[01;31m*.bz2 01;31[0m
|
||||
[01;31m*.bz 01;31[0m
|
||||
[01;31m*.tbz 01;31[0m
|
||||
[01;31m*.tbz2 01;31[0m
|
||||
[01;31m*.tz 01;31[0m
|
||||
[01;31m*.deb 01;31[0m
|
||||
[01;31m*.rpm 01;31[0m
|
||||
[01;31m*.jar 01;31[0m
|
||||
[01;31m*.war 01;31[0m
|
||||
[01;31m*.ear 01;31[0m
|
||||
[01;31m*.sar 01;31[0m
|
||||
[01;31m*.rar 01;31[0m
|
||||
[01;31m*.alz 01;31[0m
|
||||
[01;31m*.ace 01;31[0m
|
||||
[01;31m*.zoo 01;31[0m
|
||||
[01;31m*.cpio 01;31[0m
|
||||
[01;31m*.7z 01;31[0m
|
||||
[01;31m*.rz 01;31[0m
|
||||
[01;31m*.cab 01;31[0m
|
||||
[01;31m*.wim 01;31[0m
|
||||
[01;31m*.swm 01;31[0m
|
||||
[01;31m*.dwm 01;31[0m
|
||||
[01;31m*.esd 01;31[0m
|
||||
[01;35m*.avif 01;35[0m
|
||||
[01;35m*.jpg 01;35[0m
|
||||
[01;35m*.jpeg 01;35[0m
|
||||
[01;35m*.mjpg 01;35[0m
|
||||
[01;35m*.mjpeg 01;35[0m
|
||||
[01;35m*.gif 01;35[0m
|
||||
[01;35m*.bmp 01;35[0m
|
||||
[01;35m*.pbm 01;35[0m
|
||||
[01;35m*.pgm 01;35[0m
|
||||
[01;35m*.ppm 01;35[0m
|
||||
[01;35m*.tga 01;35[0m
|
||||
[01;35m*.xbm 01;35[0m
|
||||
[01;35m*.xpm 01;35[0m
|
||||
[01;35m*.tif 01;35[0m
|
||||
[01;35m*.tiff 01;35[0m
|
||||
[01;35m*.png 01;35[0m
|
||||
[01;35m*.svg 01;35[0m
|
||||
[01;35m*.svgz 01;35[0m
|
||||
[01;35m*.mng 01;35[0m
|
||||
[01;35m*.pcx 01;35[0m
|
||||
[01;35m*.mov 01;35[0m
|
||||
[01;35m*.mpg 01;35[0m
|
||||
[01;35m*.mpeg 01;35[0m
|
||||
[01;35m*.m2v 01;35[0m
|
||||
[01;35m*.mkv 01;35[0m
|
||||
[01;35m*.webm 01;35[0m
|
||||
[01;35m*.webp 01;35[0m
|
||||
[01;35m*.ogm 01;35[0m
|
||||
[01;35m*.mp4 01;35[0m
|
||||
[01;35m*.m4v 01;35[0m
|
||||
[01;35m*.mp4v 01;35[0m
|
||||
[01;35m*.vob 01;35[0m
|
||||
[01;35m*.qt 01;35[0m
|
||||
[01;35m*.nuv 01;35[0m
|
||||
[01;35m*.wmv 01;35[0m
|
||||
[01;35m*.asf 01;35[0m
|
||||
[01;35m*.rm 01;35[0m
|
||||
[01;35m*.rmvb 01;35[0m
|
||||
[01;35m*.flc 01;35[0m
|
||||
[01;35m*.avi 01;35[0m
|
||||
[01;35m*.fli 01;35[0m
|
||||
[01;35m*.flv 01;35[0m
|
||||
[01;35m*.gl 01;35[0m
|
||||
[01;35m*.dl 01;35[0m
|
||||
[01;35m*.xcf 01;35[0m
|
||||
[01;35m*.xwd 01;35[0m
|
||||
[01;35m*.yuv 01;35[0m
|
||||
[01;35m*.cgm 01;35[0m
|
||||
[01;35m*.emf 01;35[0m
|
||||
[01;35m*.ogv 01;35[0m
|
||||
[01;35m*.ogx 01;35[0m
|
||||
[00;36m*.aac 00;36[0m
|
||||
[00;36m*.au 00;36[0m
|
||||
[00;36m*.flac 00;36[0m
|
||||
[00;36m*.m4a 00;36[0m
|
||||
[00;36m*.mid 00;36[0m
|
||||
[00;36m*.midi 00;36[0m
|
||||
[00;36m*.mka 00;36[0m
|
||||
[00;36m*.mp3 00;36[0m
|
||||
[00;36m*.mpc 00;36[0m
|
||||
[00;36m*.ogg 00;36[0m
|
||||
[00;36m*.ra 00;36[0m
|
||||
[00;36m*.wav 00;36[0m
|
||||
[00;36m*.oga 00;36[0m
|
||||
[00;36m*.opus 00;36[0m
|
||||
[00;36m*.spx 00;36[0m
|
||||
[00;36m*.xspf 00;36[0m
|
||||
[00;90m*~ 00;90[0m
|
||||
[00;90m*# 00;90[0m
|
||||
[00;90m*.bak 00;90[0m
|
||||
[00;90m*.old 00;90[0m
|
||||
[00;90m*.orig 00;90[0m
|
||||
[00;90m*.part 00;90[0m
|
||||
[00;90m*.rej 00;90[0m
|
||||
[00;90m*.swp 00;90[0m
|
||||
[00;90m*.tmp 00;90[0m
|
||||
[00;90m*.dpkg-dist 00;90[0m
|
||||
[00;90m*.dpkg-old 00;90[0m
|
||||
[00;90m*.ucf-dist 00;90[0m
|
||||
[00;90m*.ucf-new 00;90[0m
|
||||
[00;90m*.ucf-old 00;90[0m
|
||||
[00;90m*.rpmnew 00;90[0m
|
||||
[00;90m*.rpmorig 00;90[0m
|
||||
[00;90m*.rpmsave 00;90[0m
|
|
@ -199,3 +199,4 @@ sed -i -e "s/provoked error./provoked error\ncat pat |sort -u > pat/" tests/misc
|
|||
|
||||
# Update the GNU error message to match ours
|
||||
sed -i -e "s/ln: 'f' and 'f' are the same file/ln: failed to link 'f' to 'f': Same file/g" tests/ln/hard-backup.sh
|
||||
sed -i -e "s/failed to access 'no-such-dir'\":/failed to link 'no-such-dir'\"/" -e "s/link-to-dir: hard link not allowed for directory/failed to link 'link-to-dir' to/" -e "s|link-to-dir/: hard link not allowed for directory|failed to link 'link-to-dir/' to|" tests/ln/hard-to-sym.sh
|
Loading…
Reference in a new issue