mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
Merge branch 'master' of https://github.com/uutils/coreutils
This commit is contained in:
commit
4c395146dd
116 changed files with 3865 additions and 1614 deletions
3
.github/workflows/GNU.yml
vendored
3
.github/workflows/GNU.yml
vendored
|
@ -80,6 +80,9 @@ jobs:
|
||||||
-e '/tests\/misc\/help-version-getopt.sh/ D' \
|
-e '/tests\/misc\/help-version-getopt.sh/ D' \
|
||||||
Makefile
|
Makefile
|
||||||
|
|
||||||
|
# printf doesn't limit the values used in its arg, so this produced ~2GB of output
|
||||||
|
sed -i '/INT_OFLOW/ D' tests/misc/printf.sh
|
||||||
|
|
||||||
# Use the system coreutils where the test fails due to error in a util that is not the one being tested
|
# Use the system coreutils where the test fails due to error in a util that is not the one being tested
|
||||||
sed -i 's|stat|/usr/bin/stat|' tests/chgrp/basic.sh tests/cp/existing-perm-dir.sh tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh
|
sed -i 's|stat|/usr/bin/stat|' tests/chgrp/basic.sh tests/cp/existing-perm-dir.sh tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh
|
||||||
sed -i 's|ls -|/usr/bin/ls -|' tests/chgrp/posix-H.sh tests/chown/deref.sh tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh tests/du/8gb.sh
|
sed -i 's|ls -|/usr/bin/ls -|' tests/chgrp/posix-H.sh tests/chown/deref.sh tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh tests/du/8gb.sh
|
||||||
|
|
94
Cargo.lock
generated
94
Cargo.lock
generated
|
@ -28,6 +28,15 @@ dependencies = [
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ansi_term"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
||||||
|
dependencies = [
|
||||||
|
"winapi 0.3.9",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayvec"
|
name = "arrayvec"
|
||||||
version = "0.4.12"
|
version = "0.4.12"
|
||||||
|
@ -127,9 +136,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cast"
|
name = "cast"
|
||||||
version = "0.2.3"
|
version = "0.2.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0"
|
checksum = "cc38c385bfd7e444464011bb24820f40dd1c76bcdfa1b78611cb7c2e5cafab75"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
]
|
]
|
||||||
|
@ -169,7 +178,7 @@ version = "2.33.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
|
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term",
|
"ansi_term 0.11.0",
|
||||||
"atty",
|
"atty",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"strsim",
|
"strsim",
|
||||||
|
@ -212,6 +221,7 @@ dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
"nix 0.20.0",
|
"nix 0.20.0",
|
||||||
|
"pretty_assertions",
|
||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
"regex",
|
"regex",
|
||||||
"sha1",
|
"sha1",
|
||||||
|
@ -452,9 +462,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-channel"
|
name = "crossbeam-channel"
|
||||||
version = "0.5.0"
|
version = "0.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775"
|
checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
|
@ -517,6 +527,16 @@ dependencies = [
|
||||||
"memchr 2.3.4",
|
"memchr 2.3.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ctor"
|
||||||
|
version = "0.1.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d"
|
||||||
|
dependencies = [
|
||||||
|
"quote 1.0.9",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "custom_derive"
|
name = "custom_derive"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
|
@ -529,6 +549,12 @@ version = "2.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f4f47ca1860a761136924ddd2422ba77b2ea54fe8cc75b9040804a0d9d32ad97"
|
checksum = "f4f47ca1860a761136924ddd2422ba77b2ea54fe8cc75b9040804a0d9d32ad97"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "diff"
|
||||||
|
version = "0.1.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
|
@ -580,7 +606,7 @@ checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"libc",
|
"libc",
|
||||||
"redox_syscall 0.2.5",
|
"redox_syscall 0.2.6",
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -783,6 +809,15 @@ dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lscolors"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d24b894c45c9da468621cdd615a5a79ee5e5523dd4f75c76ebc03d458940c16e"
|
||||||
|
dependencies = [
|
||||||
|
"ansi_term 0.12.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "match_cfg"
|
name = "match_cfg"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -897,6 +932,12 @@ version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
|
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "onig"
|
name = "onig"
|
||||||
version = "4.3.3"
|
version = "4.3.3"
|
||||||
|
@ -925,6 +966,15 @@ version = "11.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
|
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "output_vt100"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
|
||||||
|
dependencies = [
|
||||||
|
"winapi 0.3.9",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "paste"
|
name = "paste"
|
||||||
version = "0.1.18"
|
version = "0.1.18"
|
||||||
|
@ -994,6 +1044,18 @@ version = "0.2.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
|
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pretty_assertions"
|
||||||
|
version = "0.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1cab0e7c02cf376875e9335e0ba1da535775beb5450d21e1dffca068818ed98b"
|
||||||
|
dependencies = [
|
||||||
|
"ansi_term 0.12.1",
|
||||||
|
"ctor",
|
||||||
|
"diff",
|
||||||
|
"output_vt100",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-hack"
|
name = "proc-macro-hack"
|
||||||
version = "0.5.19"
|
version = "0.5.19"
|
||||||
|
@ -1176,9 +1238,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.2.5"
|
version = "0.2.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9"
|
checksum = "8270314b5ccceb518e7e578952f0b72b88222d02e8f77f5ecf7abbb673539041"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
]
|
]
|
||||||
|
@ -1189,14 +1251,14 @@ version = "0.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f"
|
checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"redox_syscall 0.2.5",
|
"redox_syscall 0.2.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.4.5"
|
version = "1.4.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19"
|
checksum = "2a26af418b574bd56588335b3a3659a65725d4e636eb1016c2f9e3b38c7cc759"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr 2.3.4",
|
"memchr 2.3.4",
|
||||||
|
@ -1400,9 +1462,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.69"
|
version = "1.0.70"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb"
|
checksum = "b9505f307c872bab8eb46f77ae357c8eba1fdacead58ee5a850116b1d7f82883"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.9",
|
"quote 1.0.9",
|
||||||
|
@ -1460,7 +1522,7 @@ checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"numtoa",
|
"numtoa",
|
||||||
"redox_syscall 0.2.5",
|
"redox_syscall 0.2.6",
|
||||||
"redox_termios",
|
"redox_termios",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1996,11 +2058,12 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"globset",
|
"globset",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
"lscolors",
|
||||||
"number_prefix",
|
"number_prefix",
|
||||||
|
"once_cell",
|
||||||
"term_grid",
|
"term_grid",
|
||||||
"termsize",
|
"termsize",
|
||||||
"time",
|
"time",
|
||||||
"unicode-width",
|
|
||||||
"uucore",
|
"uucore",
|
||||||
"uucore_procs",
|
"uucore_procs",
|
||||||
]
|
]
|
||||||
|
@ -2306,6 +2369,7 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"smallvec 1.6.1",
|
"smallvec 1.6.1",
|
||||||
"tempdir",
|
"tempdir",
|
||||||
|
"unicode-width",
|
||||||
"uucore",
|
"uucore",
|
||||||
"uucore_procs",
|
"uucore_procs",
|
||||||
]
|
]
|
||||||
|
|
|
@ -335,6 +335,7 @@ filetime = "0.2"
|
||||||
glob = "0.3.0"
|
glob = "0.3.0"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
nix = "0.20.0"
|
nix = "0.20.0"
|
||||||
|
pretty_assertions = "0.7.2"
|
||||||
rand = "0.7"
|
rand = "0.7"
|
||||||
regex = "1.0"
|
regex = "1.0"
|
||||||
sha1 = { version="0.6", features=["std"] }
|
sha1 = { version="0.6", features=["std"] }
|
||||||
|
|
|
@ -22,6 +22,12 @@ use std::io::{self, Read, Write};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use uucore::fs::is_stdin_interactive;
|
use uucore::fs::is_stdin_interactive;
|
||||||
|
|
||||||
|
/// Linux splice support
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
|
mod splice;
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
|
||||||
/// Unix domain socket support
|
/// Unix domain socket support
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::net::Shutdown;
|
use std::net::Shutdown;
|
||||||
|
@ -30,14 +36,6 @@ use std::os::unix::fs::FileTypeExt;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use unix_socket::UnixStream;
|
use unix_socket::UnixStream;
|
||||||
|
|
||||||
/// Linux splice support
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
||||||
use nix::fcntl::{splice, SpliceFFlags};
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
||||||
use nix::unistd::pipe;
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
|
||||||
|
|
||||||
static NAME: &str = "cat";
|
static NAME: &str = "cat";
|
||||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
static SYNTAX: &str = "[OPTION]... [FILE]...";
|
static SYNTAX: &str = "[OPTION]... [FILE]...";
|
||||||
|
@ -395,7 +393,7 @@ fn write_fast<R: Read>(handle: &mut InputHandle<R>) -> CatResult<()> {
|
||||||
{
|
{
|
||||||
// If we're on Linux or Android, try to use the splice() system call
|
// If we're on Linux or Android, try to use the splice() system call
|
||||||
// for faster writing. If it works, we're done.
|
// for faster writing. If it works, we're done.
|
||||||
if !write_fast_using_splice(handle, stdout_lock.as_raw_fd())? {
|
if !splice::write_fast_using_splice(handle, stdout_lock.as_raw_fd())? {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -411,75 +409,6 @@ fn write_fast<R: Read>(handle: &mut InputHandle<R>) -> CatResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This function is called from `write_fast()` on Linux and Android. The
|
|
||||||
/// function `splice()` is used to move data between two file descriptors
|
|
||||||
/// without copying between kernel- and userspace. This results in a large
|
|
||||||
/// speedup.
|
|
||||||
///
|
|
||||||
/// The `bool` in the result value indicates if we need to fall back to normal
|
|
||||||
/// copying or not. False means we don't have to.
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
||||||
#[inline]
|
|
||||||
fn write_fast_using_splice<R: Read>(handle: &mut InputHandle<R>, writer: RawFd) -> CatResult<bool> {
|
|
||||||
const BUF_SIZE: usize = 1024 * 16;
|
|
||||||
|
|
||||||
let (pipe_rd, pipe_wr) = pipe()?;
|
|
||||||
|
|
||||||
// We only fall back if splice fails on the first call.
|
|
||||||
match splice(
|
|
||||||
handle.file_descriptor,
|
|
||||||
None,
|
|
||||||
pipe_wr,
|
|
||||||
None,
|
|
||||||
BUF_SIZE,
|
|
||||||
SpliceFFlags::empty(),
|
|
||||||
) {
|
|
||||||
Ok(n) => {
|
|
||||||
if n == 0 {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
splice_exact(pipe_rd, writer, n)?;
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let n = splice(
|
|
||||||
handle.file_descriptor,
|
|
||||||
None,
|
|
||||||
pipe_wr,
|
|
||||||
None,
|
|
||||||
BUF_SIZE,
|
|
||||||
SpliceFFlags::empty(),
|
|
||||||
)?;
|
|
||||||
if n == 0 {
|
|
||||||
// We read 0 bytes from the input,
|
|
||||||
// which means we're done copying.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
splice_exact(pipe_rd, writer, n)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Splice wrapper which handles short writes
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
||||||
#[inline]
|
|
||||||
fn splice_exact(read_fd: RawFd, write_fd: RawFd, num_bytes: usize) -> nix::Result<()> {
|
|
||||||
let mut left = num_bytes;
|
|
||||||
loop {
|
|
||||||
let written = splice(read_fd, None, write_fd, None, left, SpliceFFlags::empty())?;
|
|
||||||
left -= written;
|
|
||||||
if left == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Outputs file contents to stdout in a line-by-line fashion,
|
/// Outputs file contents to stdout in a line-by-line fashion,
|
||||||
/// propagating any errors that might occur.
|
/// propagating any errors that might occur.
|
||||||
fn write_lines<R: Read>(
|
fn write_lines<R: Read>(
|
||||||
|
|
91
src/uu/cat/src/splice.rs
Normal file
91
src/uu/cat/src/splice.rs
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
use super::{CatResult, InputHandle};
|
||||||
|
|
||||||
|
use nix::fcntl::{splice, SpliceFFlags};
|
||||||
|
use nix::unistd::{self, pipe};
|
||||||
|
use std::io::Read;
|
||||||
|
use std::os::unix::io::RawFd;
|
||||||
|
|
||||||
|
const BUF_SIZE: usize = 1024 * 16;
|
||||||
|
|
||||||
|
/// This function is called from `write_fast()` on Linux and Android. The
|
||||||
|
/// function `splice()` is used to move data between two file descriptors
|
||||||
|
/// without copying between kernel- and userspace. This results in a large
|
||||||
|
/// speedup.
|
||||||
|
///
|
||||||
|
/// The `bool` in the result value indicates if we need to fall back to normal
|
||||||
|
/// copying or not. False means we don't have to.
|
||||||
|
#[inline]
|
||||||
|
pub(super) fn write_fast_using_splice<R: Read>(
|
||||||
|
handle: &mut InputHandle<R>,
|
||||||
|
write_fd: RawFd,
|
||||||
|
) -> CatResult<bool> {
|
||||||
|
let (pipe_rd, pipe_wr) = match pipe() {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(_) => {
|
||||||
|
// It is very rare that creating a pipe fails, but it can happen.
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match splice(
|
||||||
|
handle.file_descriptor,
|
||||||
|
None,
|
||||||
|
pipe_wr,
|
||||||
|
None,
|
||||||
|
BUF_SIZE,
|
||||||
|
SpliceFFlags::empty(),
|
||||||
|
) {
|
||||||
|
Ok(n) => {
|
||||||
|
if n == 0 {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
if splice_exact(pipe_rd, write_fd, n).is_err() {
|
||||||
|
// If the first splice manages to copy to the intermediate
|
||||||
|
// pipe, but the second splice to stdout fails for some reason
|
||||||
|
// we can recover by copying the data that we have from the
|
||||||
|
// intermediate pipe to stdout using normal read/write. Then
|
||||||
|
// we tell the caller to fall back.
|
||||||
|
copy_exact(pipe_rd, write_fd, n)?;
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Splice wrapper which handles short writes.
|
||||||
|
#[inline]
|
||||||
|
fn splice_exact(read_fd: RawFd, write_fd: RawFd, num_bytes: usize) -> nix::Result<()> {
|
||||||
|
let mut left = num_bytes;
|
||||||
|
loop {
|
||||||
|
let written = splice(read_fd, None, write_fd, None, left, SpliceFFlags::empty())?;
|
||||||
|
left -= written;
|
||||||
|
if left == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Caller must ensure that `num_bytes <= BUF_SIZE`, otherwise this function
|
||||||
|
/// will panic. The way we use this function in `write_fast_using_splice`
|
||||||
|
/// above is safe because `splice` is set to write at most `BUF_SIZE` to the
|
||||||
|
/// pipe.
|
||||||
|
#[inline]
|
||||||
|
fn copy_exact(read_fd: RawFd, write_fd: RawFd, num_bytes: usize) -> nix::Result<()> {
|
||||||
|
let mut left = num_bytes;
|
||||||
|
let mut buf = [0; BUF_SIZE];
|
||||||
|
loop {
|
||||||
|
let read = unistd::read(read_fd, &mut buf[..left])?;
|
||||||
|
let written = unistd::write(write_fd, &mut buf[..read])?;
|
||||||
|
left -= written;
|
||||||
|
if left == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -155,7 +155,8 @@ pub enum OverwriteMode {
|
||||||
NoClobber,
|
NoClobber,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Eq, PartialEq)]
|
/// Possible arguments for `--reflink`.
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||||
pub enum ReflinkMode {
|
pub enum ReflinkMode {
|
||||||
Always,
|
Always,
|
||||||
Auto,
|
Auto,
|
||||||
|
@ -210,7 +211,6 @@ pub struct Options {
|
||||||
overwrite: OverwriteMode,
|
overwrite: OverwriteMode,
|
||||||
parents: bool,
|
parents: bool,
|
||||||
strip_trailing_slashes: bool,
|
strip_trailing_slashes: bool,
|
||||||
reflink: bool,
|
|
||||||
reflink_mode: ReflinkMode,
|
reflink_mode: ReflinkMode,
|
||||||
preserve_attributes: Vec<Attribute>,
|
preserve_attributes: Vec<Attribute>,
|
||||||
recursive: bool,
|
recursive: bool,
|
||||||
|
@ -633,12 +633,12 @@ impl Options {
|
||||||
update: matches.is_present(OPT_UPDATE),
|
update: matches.is_present(OPT_UPDATE),
|
||||||
verbose: matches.is_present(OPT_VERBOSE),
|
verbose: matches.is_present(OPT_VERBOSE),
|
||||||
strip_trailing_slashes: matches.is_present(OPT_STRIP_TRAILING_SLASHES),
|
strip_trailing_slashes: matches.is_present(OPT_STRIP_TRAILING_SLASHES),
|
||||||
reflink: matches.is_present(OPT_REFLINK),
|
|
||||||
reflink_mode: {
|
reflink_mode: {
|
||||||
if let Some(reflink) = matches.value_of(OPT_REFLINK) {
|
if let Some(reflink) = matches.value_of(OPT_REFLINK) {
|
||||||
match reflink {
|
match reflink {
|
||||||
"always" => ReflinkMode::Always,
|
"always" => ReflinkMode::Always,
|
||||||
"auto" => ReflinkMode::Auto,
|
"auto" => ReflinkMode::Auto,
|
||||||
|
"never" => ReflinkMode::Never,
|
||||||
value => {
|
value => {
|
||||||
return Err(Error::InvalidArgument(format!(
|
return Err(Error::InvalidArgument(format!(
|
||||||
"invalid argument '{}' for \'reflink\'",
|
"invalid argument '{}' for \'reflink\'",
|
||||||
|
@ -1193,47 +1193,20 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
///Copy the file from `source` to `dest` either using the normal `fs::copy` or the
|
/// Copy the file from `source` to `dest` either using the normal `fs::copy` or a
|
||||||
///`FICLONE` ioctl if --reflink is specified and the filesystem supports it.
|
/// copy-on-write scheme if --reflink is specified and the filesystem supports it.
|
||||||
fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> {
|
fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> {
|
||||||
if options.reflink {
|
if options.reflink_mode != ReflinkMode::Never {
|
||||||
#[cfg(not(target_os = "linux"))]
|
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
||||||
return Err("--reflink is only supported on linux".to_string().into());
|
return Err("--reflink is only supported on linux and macOS"
|
||||||
|
.to_string()
|
||||||
|
.into());
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
copy_on_write_macos(source, dest, options.reflink_mode)?;
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
copy_on_write_linux(source, dest, options.reflink_mode)?;
|
||||||
let src_file = File::open(source).unwrap().into_raw_fd();
|
|
||||||
let dst_file = OpenOptions::new()
|
|
||||||
.write(true)
|
|
||||||
.truncate(false)
|
|
||||||
.create(true)
|
|
||||||
.open(dest)
|
|
||||||
.unwrap()
|
|
||||||
.into_raw_fd();
|
|
||||||
match options.reflink_mode {
|
|
||||||
ReflinkMode::Always => unsafe {
|
|
||||||
let result = ficlone(dst_file, src_file as *const i32);
|
|
||||||
if result != 0 {
|
|
||||||
return Err(format!(
|
|
||||||
"failed to clone {:?} from {:?}: {}",
|
|
||||||
source,
|
|
||||||
dest,
|
|
||||||
std::io::Error::last_os_error()
|
|
||||||
)
|
|
||||||
.into());
|
|
||||||
} else {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ReflinkMode::Auto => unsafe {
|
|
||||||
let result = ficlone(dst_file, src_file as *const i32);
|
|
||||||
if result != 0 {
|
|
||||||
fs::copy(source, dest).context(&*context_for(source, dest))?;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ReflinkMode::Never => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if options.no_dereference && fs::symlink_metadata(&source)?.file_type().is_symlink() {
|
} else if options.no_dereference && fs::symlink_metadata(&source)?.file_type().is_symlink() {
|
||||||
// Here, we will copy the symlink itself (actually, just recreate it)
|
// Here, we will copy the symlink itself (actually, just recreate it)
|
||||||
let link = fs::read_link(&source)?;
|
let link = fs::read_link(&source)?;
|
||||||
|
@ -1266,6 +1239,101 @@ fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()>
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Copies `source` to `dest` using copy-on-write if possible.
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn copy_on_write_linux(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyResult<()> {
|
||||||
|
debug_assert!(mode != ReflinkMode::Never);
|
||||||
|
|
||||||
|
let src_file = File::open(source).unwrap().into_raw_fd();
|
||||||
|
let dst_file = OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.truncate(false)
|
||||||
|
.create(true)
|
||||||
|
.open(dest)
|
||||||
|
.unwrap()
|
||||||
|
.into_raw_fd();
|
||||||
|
match mode {
|
||||||
|
ReflinkMode::Always => unsafe {
|
||||||
|
let result = ficlone(dst_file, src_file as *const i32);
|
||||||
|
if result != 0 {
|
||||||
|
return Err(format!(
|
||||||
|
"failed to clone {:?} from {:?}: {}",
|
||||||
|
source,
|
||||||
|
dest,
|
||||||
|
std::io::Error::last_os_error()
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
} else {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ReflinkMode::Auto => unsafe {
|
||||||
|
let result = ficlone(dst_file, src_file as *const i32);
|
||||||
|
if result != 0 {
|
||||||
|
fs::copy(source, dest).context(&*context_for(source, dest))?;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ReflinkMode::Never => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copies `source` to `dest` using copy-on-write if possible.
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn copy_on_write_macos(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyResult<()> {
|
||||||
|
debug_assert!(mode != ReflinkMode::Never);
|
||||||
|
|
||||||
|
// Extract paths in a form suitable to be passed to a syscall.
|
||||||
|
// The unwrap() is safe because they come from the command-line and so contain non nul
|
||||||
|
// character.
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
let src = CString::new(source.as_os_str().as_bytes()).unwrap();
|
||||||
|
let dst = CString::new(dest.as_os_str().as_bytes()).unwrap();
|
||||||
|
|
||||||
|
// clonefile(2) was introduced in macOS 10.12 so we cannot statically link against it
|
||||||
|
// for backward compatibility.
|
||||||
|
let clonefile = CString::new("clonefile").unwrap();
|
||||||
|
let raw_pfn = unsafe { libc::dlsym(libc::RTLD_NEXT, clonefile.as_ptr()) };
|
||||||
|
|
||||||
|
let mut error = 0;
|
||||||
|
if !raw_pfn.is_null() {
|
||||||
|
// Call clonefile(2).
|
||||||
|
// Safety: Casting a C function pointer to a rust function value is one of the few
|
||||||
|
// blessed uses of `transmute()`.
|
||||||
|
unsafe {
|
||||||
|
let pfn: extern "C" fn(
|
||||||
|
src: *const libc::c_char,
|
||||||
|
dst: *const libc::c_char,
|
||||||
|
flags: u32,
|
||||||
|
) -> libc::c_int = std::mem::transmute(raw_pfn);
|
||||||
|
error = pfn(src.as_ptr(), dst.as_ptr(), 0);
|
||||||
|
if std::io::Error::last_os_error().kind() == std::io::ErrorKind::AlreadyExists {
|
||||||
|
// clonefile(2) fails if the destination exists. Remove it and try again. Do not
|
||||||
|
// bother to check if removal worked because we're going to try to clone again.
|
||||||
|
let _ = fs::remove_file(dest);
|
||||||
|
error = pfn(src.as_ptr(), dst.as_ptr(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if raw_pfn.is_null() || error != 0 {
|
||||||
|
// clonefile(2) is not supported or it error'ed out (possibly because the FS does not
|
||||||
|
// support COW).
|
||||||
|
match mode {
|
||||||
|
ReflinkMode::Always => {
|
||||||
|
return Err(
|
||||||
|
format!("failed to clone {:?} from {:?}: {}", source, dest, error).into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ReflinkMode::Auto => fs::copy(source, dest).context(&*context_for(source, dest))?,
|
||||||
|
ReflinkMode::Never => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Generate an error message if `target` is not the correct `target_type`
|
/// Generate an error message if `target` is not the correct `target_type`
|
||||||
pub fn verify_target_type(target: &Path, target_type: &TargetType) -> CopyResult<()> {
|
pub fn verify_target_type(target: &Path, target_type: &TargetType) -> CopyResult<()> {
|
||||||
match (target_type, target.is_dir()) {
|
match (target_type, target.is_dir()) {
|
||||||
|
|
|
@ -500,7 +500,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
};
|
};
|
||||||
|
|
||||||
let strs = if matches.free.is_empty() {
|
let strs = if matches.free.is_empty() {
|
||||||
vec!["./".to_owned()]
|
vec!["./".to_owned()] // TODO: gnu `du` doesn't use trailing "/" here
|
||||||
} else {
|
} else {
|
||||||
matches.free.clone()
|
matches.free.clone()
|
||||||
};
|
};
|
||||||
|
|
|
@ -41,6 +41,7 @@ pub struct Behavior {
|
||||||
compare: bool,
|
compare: bool,
|
||||||
strip: bool,
|
strip: bool,
|
||||||
strip_program: String,
|
strip_program: String,
|
||||||
|
create_leading: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Eq, PartialEq)]
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
|
@ -70,7 +71,7 @@ static OPT_BACKUP: &str = "backup";
|
||||||
static OPT_BACKUP_2: &str = "backup2";
|
static OPT_BACKUP_2: &str = "backup2";
|
||||||
static OPT_DIRECTORY: &str = "directory";
|
static OPT_DIRECTORY: &str = "directory";
|
||||||
static OPT_IGNORED: &str = "ignored";
|
static OPT_IGNORED: &str = "ignored";
|
||||||
static OPT_CREATED: &str = "created";
|
static OPT_CREATE_LEADING: &str = "create-leading";
|
||||||
static OPT_GROUP: &str = "group";
|
static OPT_GROUP: &str = "group";
|
||||||
static OPT_MODE: &str = "mode";
|
static OPT_MODE: &str = "mode";
|
||||||
static OPT_OWNER: &str = "owner";
|
static OPT_OWNER: &str = "owner";
|
||||||
|
@ -133,9 +134,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
|
|
||||||
.arg(
|
.arg(
|
||||||
// TODO implement flag
|
// TODO implement flag
|
||||||
Arg::with_name(OPT_CREATED)
|
Arg::with_name(OPT_CREATE_LEADING)
|
||||||
.short("D")
|
.short("D")
|
||||||
.help("(unimplemented) create all leading components of DEST except the last, then copy SOURCE to DEST")
|
.help("create all leading components of DEST except the last, then copy SOURCE to DEST")
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_GROUP)
|
Arg::with_name(OPT_GROUP)
|
||||||
|
@ -266,8 +267,6 @@ fn check_unimplemented<'a>(matches: &ArgMatches) -> Result<(), &'a str> {
|
||||||
Err("--backup")
|
Err("--backup")
|
||||||
} else if matches.is_present(OPT_BACKUP_2) {
|
} else if matches.is_present(OPT_BACKUP_2) {
|
||||||
Err("-b")
|
Err("-b")
|
||||||
} else if matches.is_present(OPT_CREATED) {
|
|
||||||
Err("-D")
|
|
||||||
} else if matches.is_present(OPT_SUFFIX) {
|
} else if matches.is_present(OPT_SUFFIX) {
|
||||||
Err("--suffix, -S")
|
Err("--suffix, -S")
|
||||||
} else if matches.is_present(OPT_TARGET_DIRECTORY) {
|
} else if matches.is_present(OPT_TARGET_DIRECTORY) {
|
||||||
|
@ -343,6 +342,7 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
|
||||||
.value_of(OPT_STRIP_PROGRAM)
|
.value_of(OPT_STRIP_PROGRAM)
|
||||||
.unwrap_or(DEFAULT_STRIP_PROGRAM),
|
.unwrap_or(DEFAULT_STRIP_PROGRAM),
|
||||||
),
|
),
|
||||||
|
create_leading: matches.is_present(OPT_CREATE_LEADING),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -410,12 +410,35 @@ fn standard(paths: Vec<String>, b: Behavior) -> i32 {
|
||||||
.iter()
|
.iter()
|
||||||
.map(PathBuf::from)
|
.map(PathBuf::from)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let target = Path::new(paths.last().unwrap());
|
let target = Path::new(paths.last().unwrap());
|
||||||
|
|
||||||
if (target.is_file() || is_new_file_path(target)) && sources.len() == 1 {
|
if sources.len() > 1 || (target.exists() && target.is_dir()) {
|
||||||
|
copy_files_into_dir(sources, &target.to_path_buf(), &b)
|
||||||
|
} else {
|
||||||
|
if let Some(parent) = target.parent() {
|
||||||
|
if !parent.exists() && b.create_leading {
|
||||||
|
if let Err(e) = fs::create_dir_all(parent) {
|
||||||
|
show_error!("failed to create {}: {}", parent.display(), e);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if mode::chmod(&parent, b.mode()).is_err() {
|
||||||
|
show_error!("failed to chmod {}", parent.display());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if target.is_file() || is_new_file_path(target) {
|
||||||
copy_file_to_file(&sources[0], &target.to_path_buf(), &b)
|
copy_file_to_file(&sources[0], &target.to_path_buf(), &b)
|
||||||
} else {
|
} else {
|
||||||
copy_files_into_dir(sources, &target.to_path_buf(), &b)
|
show_error!(
|
||||||
|
"invalid target {}: No such file or directory",
|
||||||
|
target.display()
|
||||||
|
);
|
||||||
|
1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
34
src/uu/ls/BENCHMARKING.md
Normal file
34
src/uu/ls/BENCHMARKING.md
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
# Benchmarking ls
|
||||||
|
|
||||||
|
ls majorly involves fetching a lot of details (depending upon what details are requested, eg. time/date, inode details, etc) for each path using system calls. Ideally, any system call should be done only once for each of the paths - not adhering to this principle leads to a lot of system call overhead multiplying and bubbling up, especially for recursive ls, therefore it is important to always benchmark multiple scenarios.
|
||||||
|
This is an overwiew over what was benchmarked, and if you make changes to `ls`, you are encouraged to check
|
||||||
|
how performance was affected for the workloads listed below. Feel free to add other workloads to the
|
||||||
|
list that we should improve / make sure not to regress.
|
||||||
|
|
||||||
|
Run `cargo build --release` before benchmarking after you make a change!
|
||||||
|
|
||||||
|
## Simple recursive ls
|
||||||
|
|
||||||
|
- Get a large tree, for example linux kernel source tree.
|
||||||
|
- Benchmark simple recursive ls with hyperfine: `hyperfine --warmup 2 "target/release/coreutils ls -R tree > /dev/null"`.
|
||||||
|
|
||||||
|
## Recursive ls with all and long options
|
||||||
|
|
||||||
|
- Same tree as above
|
||||||
|
- Benchmark recursive ls with -al -R options with hyperfine: `hyperfine --warmup 2 "target/release/coreutils ls -al -R tree > /dev/null"`.
|
||||||
|
|
||||||
|
## Comparing with GNU ls
|
||||||
|
|
||||||
|
Hyperfine accepts multiple commands to run and will compare them. To compare performance with GNU ls
|
||||||
|
duplicate the string you passed to hyperfine but remove the `target/release/coreutils` bit from it.
|
||||||
|
|
||||||
|
Example: `hyperfine --warmup 2 "target/release/coreutils ls -al -R tree > /dev/null"` becomes
|
||||||
|
`hyperfine --warmup 2 "target/release/coreutils ls -al -R tree > /dev/null" "ls -al -R tree > /dev/null"`
|
||||||
|
(This assumes GNU ls is installed as `ls`)
|
||||||
|
|
||||||
|
This can also be used to compare with version of ls built before your changes to ensure your change does not regress this
|
||||||
|
|
||||||
|
## Checking system call count
|
||||||
|
|
||||||
|
- Another thing to look at would be system calls count using strace (on linux) or equivalent on other operating systems.
|
||||||
|
- Example: `strace -c target/release/coreutils ls -al -R tree`
|
|
@ -16,18 +16,19 @@ path = "src/ls.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = "2.33"
|
clap = "2.33"
|
||||||
lazy_static = "1.0.1"
|
|
||||||
number_prefix = "0.4"
|
number_prefix = "0.4"
|
||||||
term_grid = "0.1.5"
|
term_grid = "0.1.5"
|
||||||
termsize = "0.1.6"
|
termsize = "0.1.6"
|
||||||
time = "0.1.40"
|
time = "0.1.40"
|
||||||
unicode-width = "0.1.5"
|
|
||||||
globset = "0.4.6"
|
globset = "0.4.6"
|
||||||
|
lscolors = { version="0.7.1", features=["ansi_term"] }
|
||||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs"] }
|
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs"] }
|
||||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||||
|
once_cell = "1.7.2"
|
||||||
|
atty = "0.2"
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
atty = "0.2"
|
lazy_static = "1.4.0"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "ls"
|
name = "ls"
|
||||||
|
|
|
@ -7,41 +7,41 @@
|
||||||
|
|
||||||
// spell-checker:ignore (ToDO) cpio svgz webm somegroup nlink rmvb xspf
|
// spell-checker:ignore (ToDO) cpio svgz webm somegroup nlink rmvb xspf
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate uucore;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
#[macro_use]
|
|
||||||
extern crate uucore;
|
|
||||||
|
|
||||||
mod quoting_style;
|
mod quoting_style;
|
||||||
mod version_cmp;
|
mod version_cmp;
|
||||||
|
|
||||||
use clap::{App, Arg};
|
use clap::{App, Arg};
|
||||||
use globset::{self, Glob, GlobSet, GlobSetBuilder};
|
use globset::{self, Glob, GlobSet, GlobSetBuilder};
|
||||||
|
use lscolors::LsColors;
|
||||||
use number_prefix::NumberPrefix;
|
use number_prefix::NumberPrefix;
|
||||||
|
use once_cell::unsync::OnceCell;
|
||||||
use quoting_style::{escape_name, QuotingStyle};
|
use quoting_style::{escape_name, QuotingStyle};
|
||||||
#[cfg(unix)]
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fs;
|
|
||||||
use std::fs::{DirEntry, FileType, Metadata};
|
|
||||||
#[cfg(unix)]
|
|
||||||
use std::os::unix::fs::FileTypeExt;
|
|
||||||
#[cfg(any(unix, target_os = "redox"))]
|
|
||||||
use std::os::unix::fs::MetadataExt;
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use std::os::windows::fs::MetadataExt;
|
use std::os::windows::fs::MetadataExt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::{
|
||||||
|
cmp::Reverse,
|
||||||
|
fs::{self, DirEntry, FileType, Metadata},
|
||||||
|
io::{stdout, BufWriter, Stdout, Write},
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
process::exit,
|
||||||
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
|
};
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::time::Duration;
|
use std::{
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
collections::HashMap,
|
||||||
use std::{cmp::Reverse, process::exit};
|
os::unix::fs::{FileTypeExt, MetadataExt},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
|
use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
|
||||||
use time::{strftime, Timespec};
|
use time::{strftime, Timespec};
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use unicode_width::UnicodeWidthStr;
|
use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR};
|
||||||
#[cfg(unix)]
|
|
||||||
use uucore::libc::{mode_t, S_ISGID, S_ISUID, S_ISVTX, S_IWOTH, S_IXGRP, S_IXOTH, S_IXUSR};
|
|
||||||
|
|
||||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
static ABOUT: &str = "
|
static ABOUT: &str = "
|
||||||
|
@ -54,30 +54,6 @@ fn get_usage() -> String {
|
||||||
format!("{0} [OPTION]... [FILE]...", executable!())
|
format!("{0} [OPTION]... [FILE]...", executable!())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
static DEFAULT_COLORS: &str = "rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:";
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
lazy_static! {
|
|
||||||
static ref LS_COLORS: String =
|
|
||||||
std::env::var("LS_COLORS").unwrap_or_else(|_| DEFAULT_COLORS.to_string());
|
|
||||||
static ref COLOR_MAP: HashMap<&'static str, &'static str> = {
|
|
||||||
let codes = LS_COLORS.split(':');
|
|
||||||
let mut map = HashMap::new();
|
|
||||||
for c in codes {
|
|
||||||
let p: Vec<_> = c.splitn(2, '=').collect();
|
|
||||||
if p.len() == 2 {
|
|
||||||
map.insert(p[0], p[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
map
|
|
||||||
};
|
|
||||||
static ref RESET_CODE: &'static str = COLOR_MAP.get("rs").unwrap_or(&"0");
|
|
||||||
static ref LEFT_CODE: &'static str = COLOR_MAP.get("lc").unwrap_or(&"\x1b[");
|
|
||||||
static ref RIGHT_CODE: &'static str = COLOR_MAP.get("rc").unwrap_or(&"m");
|
|
||||||
static ref END_CODE: &'static str = COLOR_MAP.get("ec").unwrap_or(&"");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod options {
|
pub mod options {
|
||||||
pub mod format {
|
pub mod format {
|
||||||
pub static ONELINE: &str = "1";
|
pub static ONELINE: &str = "1";
|
||||||
|
@ -113,10 +89,8 @@ pub mod options {
|
||||||
pub static C: &str = "quote-name";
|
pub static C: &str = "quote-name";
|
||||||
}
|
}
|
||||||
pub static QUOTING_STYLE: &str = "quoting-style";
|
pub static QUOTING_STYLE: &str = "quoting-style";
|
||||||
|
|
||||||
pub mod indicator_style {
|
pub mod indicator_style {
|
||||||
pub static NONE: &str = "none";
|
pub static SLASH: &str = "p";
|
||||||
pub static SLASH: &str = "slash";
|
|
||||||
pub static FILE_TYPE: &str = "file-type";
|
pub static FILE_TYPE: &str = "file-type";
|
||||||
pub static CLASSIFY: &str = "classify";
|
pub static CLASSIFY: &str = "classify";
|
||||||
}
|
}
|
||||||
|
@ -135,9 +109,6 @@ pub mod options {
|
||||||
pub static TIME: &str = "time";
|
pub static TIME: &str = "time";
|
||||||
pub static IGNORE_BACKUPS: &str = "ignore-backups";
|
pub static IGNORE_BACKUPS: &str = "ignore-backups";
|
||||||
pub static DIRECTORY: &str = "directory";
|
pub static DIRECTORY: &str = "directory";
|
||||||
pub static CLASSIFY: &str = "classify";
|
|
||||||
pub static FILE_TYPE: &str = "file-type";
|
|
||||||
pub static SLASH: &str = "p";
|
|
||||||
pub static INODE: &str = "inode";
|
pub static INODE: &str = "inode";
|
||||||
pub static REVERSE: &str = "reverse";
|
pub static REVERSE: &str = "reverse";
|
||||||
pub static RECURSIVE: &str = "recursive";
|
pub static RECURSIVE: &str = "recursive";
|
||||||
|
@ -212,8 +183,7 @@ struct Config {
|
||||||
time: Time,
|
time: Time,
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
inode: bool,
|
inode: bool,
|
||||||
#[cfg(unix)]
|
color: Option<LsColors>,
|
||||||
color: bool,
|
|
||||||
long: LongFormat,
|
long: LongFormat,
|
||||||
width: Option<u16>,
|
width: Option<u16>,
|
||||||
quoting_style: QuotingStyle,
|
quoting_style: QuotingStyle,
|
||||||
|
@ -337,8 +307,7 @@ impl Config {
|
||||||
Time::Modification
|
Time::Modification
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(unix)]
|
let needs_color = match options.value_of(options::COLOR) {
|
||||||
let color = match options.value_of(options::COLOR) {
|
|
||||||
None => options.is_present(options::COLOR),
|
None => options.is_present(options::COLOR),
|
||||||
Some(val) => match val {
|
Some(val) => match val {
|
||||||
"" | "always" | "yes" | "force" => true,
|
"" | "always" | "yes" | "force" => true,
|
||||||
|
@ -347,6 +316,12 @@ impl Config {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let color = if needs_color {
|
||||||
|
Some(LsColors::from_env().unwrap_or_default())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let size_format = if options.is_present(options::size::HUMAN_READABLE) {
|
let size_format = if options.is_present(options::size::HUMAN_READABLE) {
|
||||||
SizeFormat::Binary
|
SizeFormat::Binary
|
||||||
} else if options.is_present(options::size::SI) {
|
} else if options.is_present(options::size::SI) {
|
||||||
|
@ -448,19 +423,11 @@ impl Config {
|
||||||
"slash" => IndicatorStyle::Slash,
|
"slash" => IndicatorStyle::Slash,
|
||||||
&_ => IndicatorStyle::None,
|
&_ => IndicatorStyle::None,
|
||||||
}
|
}
|
||||||
} else if options.is_present(options::indicator_style::NONE) {
|
} else if options.is_present(options::indicator_style::CLASSIFY) {
|
||||||
IndicatorStyle::None
|
|
||||||
} else if options.is_present(options::indicator_style::CLASSIFY)
|
|
||||||
|| options.is_present(options::CLASSIFY)
|
|
||||||
{
|
|
||||||
IndicatorStyle::Classify
|
IndicatorStyle::Classify
|
||||||
} else if options.is_present(options::indicator_style::SLASH)
|
} else if options.is_present(options::indicator_style::SLASH) {
|
||||||
|| options.is_present(options::SLASH)
|
|
||||||
{
|
|
||||||
IndicatorStyle::Slash
|
IndicatorStyle::Slash
|
||||||
} else if options.is_present(options::indicator_style::FILE_TYPE)
|
} else if options.is_present(options::indicator_style::FILE_TYPE) {
|
||||||
|| options.is_present(options::FILE_TYPE)
|
|
||||||
{
|
|
||||||
IndicatorStyle::FileType
|
IndicatorStyle::FileType
|
||||||
} else {
|
} else {
|
||||||
IndicatorStyle::None
|
IndicatorStyle::None
|
||||||
|
@ -492,6 +459,10 @@ impl Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if files == Files::Normal {
|
||||||
|
ignore_patterns.add(Glob::new(".*").unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
let ignore_patterns = ignore_patterns.build().unwrap();
|
let ignore_patterns = ignore_patterns.build().unwrap();
|
||||||
|
|
||||||
let dereference = if options.is_present(options::dereference::ALL) {
|
let dereference = if options.is_present(options::dereference::ALL) {
|
||||||
|
@ -520,7 +491,6 @@ impl Config {
|
||||||
size_format,
|
size_format,
|
||||||
directory: options.is_present(options::DIRECTORY),
|
directory: options.is_present(options::DIRECTORY),
|
||||||
time,
|
time,
|
||||||
#[cfg(unix)]
|
|
||||||
color,
|
color,
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
inode: options.is_present(options::INODE),
|
inode: options.is_present(options::INODE),
|
||||||
|
@ -983,45 +953,45 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.possible_values(&["none", "slash", "file-type", "classify"])
|
.possible_values(&["none", "slash", "file-type", "classify"])
|
||||||
.overrides_with_all(&[
|
.overrides_with_all(&[
|
||||||
options::FILE_TYPE,
|
options::indicator_style::FILE_TYPE,
|
||||||
options::SLASH,
|
options::indicator_style::SLASH,
|
||||||
options::CLASSIFY,
|
options::indicator_style::CLASSIFY,
|
||||||
options::INDICATOR_STYLE,
|
options::INDICATOR_STYLE,
|
||||||
]))
|
]))
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(options::CLASSIFY)
|
Arg::with_name(options::indicator_style::CLASSIFY)
|
||||||
.short("F")
|
.short("F")
|
||||||
.long(options::CLASSIFY)
|
.long(options::indicator_style::CLASSIFY)
|
||||||
.help("Append a character to each file name indicating the file type. Also, for \
|
.help("Append a character to each file name indicating the file type. Also, for \
|
||||||
regular files that are executable, append '*'. The file type indicators are \
|
regular files that are executable, append '*'. The file type indicators are \
|
||||||
'/' for directories, '@' for symbolic links, '|' for FIFOs, '=' for sockets, \
|
'/' for directories, '@' for symbolic links, '|' for FIFOs, '=' for sockets, \
|
||||||
'>' for doors, and nothing for regular files.")
|
'>' for doors, and nothing for regular files.")
|
||||||
.overrides_with_all(&[
|
.overrides_with_all(&[
|
||||||
options::FILE_TYPE,
|
options::indicator_style::FILE_TYPE,
|
||||||
options::SLASH,
|
options::indicator_style::SLASH,
|
||||||
options::CLASSIFY,
|
options::indicator_style::CLASSIFY,
|
||||||
options::INDICATOR_STYLE,
|
options::INDICATOR_STYLE,
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(options::FILE_TYPE)
|
Arg::with_name(options::indicator_style::FILE_TYPE)
|
||||||
.long(options::FILE_TYPE)
|
.long(options::indicator_style::FILE_TYPE)
|
||||||
.help("Same as --classify, but do not append '*'")
|
.help("Same as --classify, but do not append '*'")
|
||||||
.overrides_with_all(&[
|
.overrides_with_all(&[
|
||||||
options::FILE_TYPE,
|
options::indicator_style::FILE_TYPE,
|
||||||
options::SLASH,
|
options::indicator_style::SLASH,
|
||||||
options::CLASSIFY,
|
options::indicator_style::CLASSIFY,
|
||||||
options::INDICATOR_STYLE,
|
options::INDICATOR_STYLE,
|
||||||
]))
|
]))
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(options::SLASH)
|
Arg::with_name(options::indicator_style::SLASH)
|
||||||
.short(options::SLASH)
|
.short(options::indicator_style::SLASH)
|
||||||
.help("Append / indicator to directories."
|
.help("Append / indicator to directories."
|
||||||
)
|
)
|
||||||
.overrides_with_all(&[
|
.overrides_with_all(&[
|
||||||
options::FILE_TYPE,
|
options::indicator_style::FILE_TYPE,
|
||||||
options::SLASH,
|
options::indicator_style::SLASH,
|
||||||
options::CLASSIFY,
|
options::indicator_style::CLASSIFY,
|
||||||
options::INDICATOR_STYLE,
|
options::INDICATOR_STYLE,
|
||||||
]))
|
]))
|
||||||
|
|
||||||
|
@ -1038,12 +1008,82 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
list(locs, Config::from(matches))
|
list(locs, Config::from(matches))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents a Path along with it's associated data
|
||||||
|
/// Any data that will be reused several times makes sense to be added to this structure
|
||||||
|
/// Caching data here helps eliminate redundant syscalls to fetch same information
|
||||||
|
struct PathData {
|
||||||
|
// Result<MetaData> got from symlink_metadata() or metadata() based on config
|
||||||
|
md: OnceCell<Option<Metadata>>,
|
||||||
|
ft: OnceCell<Option<FileType>>,
|
||||||
|
// Name of the file - will be empty for . or ..
|
||||||
|
file_name: String,
|
||||||
|
// PathBuf that all above data corresponds to
|
||||||
|
p_buf: PathBuf,
|
||||||
|
must_dereference: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PathData {
|
||||||
|
fn new(
|
||||||
|
p_buf: PathBuf,
|
||||||
|
file_type: Option<std::io::Result<FileType>>,
|
||||||
|
config: &Config,
|
||||||
|
command_line: bool,
|
||||||
|
) -> Self {
|
||||||
|
let name = p_buf
|
||||||
|
.file_name()
|
||||||
|
.map_or(String::new(), |s| s.to_string_lossy().into_owned());
|
||||||
|
let must_dereference = match &config.dereference {
|
||||||
|
Dereference::All => true,
|
||||||
|
Dereference::Args => command_line,
|
||||||
|
Dereference::DirArgs => {
|
||||||
|
if command_line {
|
||||||
|
if let Ok(md) = p_buf.metadata() {
|
||||||
|
md.is_dir()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Dereference::None => false,
|
||||||
|
};
|
||||||
|
let ft = match file_type {
|
||||||
|
Some(ft) => OnceCell::from(ft.ok()),
|
||||||
|
None => OnceCell::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
md: OnceCell::new(),
|
||||||
|
ft,
|
||||||
|
file_name: name,
|
||||||
|
p_buf,
|
||||||
|
must_dereference,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn md(&self) -> Option<&Metadata> {
|
||||||
|
self.md
|
||||||
|
.get_or_init(|| get_metadata(&self.p_buf, self.must_dereference).ok())
|
||||||
|
.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn file_type(&self) -> Option<&FileType> {
|
||||||
|
self.ft
|
||||||
|
.get_or_init(|| self.md().map(|md| md.file_type()))
|
||||||
|
.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn list(locs: Vec<String>, config: Config) -> i32 {
|
fn list(locs: Vec<String>, config: Config) -> i32 {
|
||||||
let number_of_locs = locs.len();
|
let number_of_locs = locs.len();
|
||||||
|
|
||||||
let mut files = Vec::<PathBuf>::new();
|
let mut files = Vec::<PathData>::new();
|
||||||
let mut dirs = Vec::<PathBuf>::new();
|
let mut dirs = Vec::<PathData>::new();
|
||||||
let mut has_failed = false;
|
let mut has_failed = false;
|
||||||
|
|
||||||
|
let mut out = BufWriter::new(stdout());
|
||||||
|
|
||||||
for loc in locs {
|
for loc in locs {
|
||||||
let p = PathBuf::from(&loc);
|
let p = PathBuf::from(&loc);
|
||||||
if !p.exists() {
|
if !p.exists() {
|
||||||
|
@ -1054,38 +1094,30 @@ fn list(locs: Vec<String>, config: Config) -> i32 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let show_dir_contents = if !config.directory {
|
let path_data = PathData::new(p, None, &config, true);
|
||||||
match config.dereference {
|
|
||||||
Dereference::None => {
|
let show_dir_contents = if let Some(ft) = path_data.file_type() {
|
||||||
if let Ok(md) = p.symlink_metadata() {
|
!config.directory && ft.is_dir()
|
||||||
md.is_dir()
|
|
||||||
} else {
|
} else {
|
||||||
show_error!("'{}': {}", &loc, "No such file or directory");
|
|
||||||
has_failed = true;
|
has_failed = true;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => p.is_dir(),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
if show_dir_contents {
|
if show_dir_contents {
|
||||||
dirs.push(p);
|
dirs.push(path_data);
|
||||||
} else {
|
} else {
|
||||||
files.push(p);
|
files.push(path_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sort_entries(&mut files, &config);
|
sort_entries(&mut files, &config);
|
||||||
display_items(&files, None, &config, true);
|
display_items(&files, None, &config, &mut out);
|
||||||
|
|
||||||
sort_entries(&mut dirs, &config);
|
sort_entries(&mut dirs, &config);
|
||||||
for dir in dirs {
|
for dir in dirs {
|
||||||
if number_of_locs > 1 {
|
if number_of_locs > 1 {
|
||||||
println!("\n{}:", dir.to_string_lossy());
|
let _ = writeln!(out, "\n{}:", dir.p_buf.display());
|
||||||
}
|
}
|
||||||
enter_directory(&dir, &config);
|
enter_directory(&dir, &config, &mut out);
|
||||||
}
|
}
|
||||||
if has_failed {
|
if has_failed {
|
||||||
1
|
1
|
||||||
|
@ -1094,22 +1126,21 @@ fn list(locs: Vec<String>, config: Config) -> i32 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sort_entries(entries: &mut Vec<PathBuf>, config: &Config) {
|
fn sort_entries(entries: &mut Vec<PathData>, config: &Config) {
|
||||||
match config.sort {
|
match config.sort {
|
||||||
Sort::Time => entries.sort_by_key(|k| {
|
Sort::Time => entries.sort_by_key(|k| {
|
||||||
Reverse(
|
Reverse(
|
||||||
get_metadata(k, false)
|
k.md()
|
||||||
.ok()
|
|
||||||
.and_then(|md| get_system_time(&md, config))
|
.and_then(|md| get_system_time(&md, config))
|
||||||
.unwrap_or(UNIX_EPOCH),
|
.unwrap_or(UNIX_EPOCH),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
Sort::Size => {
|
Sort::Size => {
|
||||||
entries.sort_by_key(|k| Reverse(get_metadata(k, false).map(|md| md.len()).unwrap_or(0)))
|
entries.sort_by_key(|k| Reverse(k.md().as_ref().map(|md| md.len()).unwrap_or(0)))
|
||||||
}
|
}
|
||||||
// The default sort in GNU ls is case insensitive
|
// The default sort in GNU ls is case insensitive
|
||||||
Sort::Name => entries.sort_by_key(|k| k.to_string_lossy().to_lowercase()),
|
Sort::Name => entries.sort_by_cached_key(|k| k.file_name.to_lowercase()),
|
||||||
Sort::Version => entries.sort_by(|a, b| version_cmp::version_cmp(a, b)),
|
Sort::Version => entries.sort_by(|k, j| version_cmp::version_cmp(&k.p_buf, &j.p_buf)),
|
||||||
Sort::None => {}
|
Sort::None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1122,48 +1153,53 @@ fn sort_entries(entries: &mut Vec<PathBuf>, config: &Config) {
|
||||||
fn is_hidden(file_path: &DirEntry) -> bool {
|
fn is_hidden(file_path: &DirEntry) -> bool {
|
||||||
let metadata = fs::metadata(file_path.path()).unwrap();
|
let metadata = fs::metadata(file_path.path()).unwrap();
|
||||||
let attr = metadata.file_attributes();
|
let attr = metadata.file_attributes();
|
||||||
((attr & 0x2) > 0) || file_path.file_name().to_string_lossy().starts_with('.')
|
(attr & 0x2) > 0
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
fn is_hidden(file_path: &DirEntry) -> bool {
|
|
||||||
file_path.file_name().to_string_lossy().starts_with('.')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_display(entry: &DirEntry, config: &Config) -> bool {
|
fn should_display(entry: &DirEntry, config: &Config) -> bool {
|
||||||
let ffi_name = entry.file_name();
|
let ffi_name = entry.file_name();
|
||||||
|
|
||||||
|
// For unix, the hidden files are already included in the ignore pattern
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
if config.files == Files::Normal && is_hidden(entry) {
|
if config.files == Files::Normal && is_hidden(entry) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.ignore_patterns.is_match(&ffi_name) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
true
|
|
||||||
|
!config.ignore_patterns.is_match(&ffi_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enter_directory(dir: &Path, config: &Config) {
|
fn enter_directory(dir: &PathData, config: &Config, out: &mut BufWriter<Stdout>) {
|
||||||
let mut entries: Vec<_> = safe_unwrap!(fs::read_dir(dir).and_then(Iterator::collect));
|
let mut entries: Vec<_> = if config.files == Files::All {
|
||||||
|
vec![
|
||||||
entries.retain(|e| should_display(e, config));
|
PathData::new(dir.p_buf.join("."), None, config, false),
|
||||||
|
PathData::new(dir.p_buf.join(".."), None, config, false),
|
||||||
let mut entries: Vec<_> = entries.iter().map(DirEntry::path).collect();
|
]
|
||||||
sort_entries(&mut entries, config);
|
|
||||||
|
|
||||||
if config.files == Files::All {
|
|
||||||
let mut display_entries = entries.clone();
|
|
||||||
display_entries.insert(0, dir.join(".."));
|
|
||||||
display_entries.insert(0, dir.join("."));
|
|
||||||
display_items(&display_entries, Some(dir), config, false);
|
|
||||||
} else {
|
} else {
|
||||||
display_items(&entries, Some(dir), config, false);
|
vec![]
|
||||||
}
|
};
|
||||||
|
|
||||||
|
let mut temp: Vec<_> = safe_unwrap!(fs::read_dir(&dir.p_buf))
|
||||||
|
.map(|res| safe_unwrap!(res))
|
||||||
|
.filter(|e| should_display(e, config))
|
||||||
|
.map(|e| PathData::new(DirEntry::path(&e), Some(e.file_type()), config, false))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
sort_entries(&mut temp, config);
|
||||||
|
|
||||||
|
entries.append(&mut temp);
|
||||||
|
|
||||||
|
display_items(&entries, Some(&dir.p_buf), config, out);
|
||||||
|
|
||||||
if config.recursive {
|
if config.recursive {
|
||||||
for e in entries.iter().filter(|p| p.is_dir()) {
|
for e in entries
|
||||||
println!("\n{}:", e.to_string_lossy());
|
.iter()
|
||||||
enter_directory(&e, config);
|
.skip(if config.files == Files::All { 2 } else { 0 })
|
||||||
|
.filter(|p| p.file_type().map(|ft| ft.is_dir()).unwrap_or(false))
|
||||||
|
{
|
||||||
|
let _ = writeln!(out, "\n{}:", e.p_buf.display());
|
||||||
|
enter_directory(&e, config, out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1176,8 +1212,8 @@ fn get_metadata(entry: &Path, dereference: bool) -> std::io::Result<Metadata> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_dir_entry_size(entry: &Path, config: &Config) -> (usize, usize) {
|
fn display_dir_entry_size(entry: &PathData, config: &Config) -> (usize, usize) {
|
||||||
if let Ok(md) = get_metadata(entry, false) {
|
if let Some(md) = entry.md() {
|
||||||
(
|
(
|
||||||
display_symlink_count(&md).len(),
|
display_symlink_count(&md).len(),
|
||||||
display_file_size(&md, config).len(),
|
display_file_size(&md, config).len(),
|
||||||
|
@ -1191,7 +1227,12 @@ fn pad_left(string: String, count: usize) -> String {
|
||||||
format!("{:>width$}", string, width = count)
|
format!("{:>width$}", string, width = count)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_items(items: &[PathBuf], strip: Option<&Path>, config: &Config, command_line: bool) {
|
fn display_items(
|
||||||
|
items: &[PathData],
|
||||||
|
strip: Option<&Path>,
|
||||||
|
config: &Config,
|
||||||
|
out: &mut BufWriter<Stdout>,
|
||||||
|
) {
|
||||||
if config.format == Format::Long {
|
if config.format == Format::Long {
|
||||||
let (mut max_links, mut max_size) = (1, 1);
|
let (mut max_links, mut max_size) = (1, 1);
|
||||||
for item in items {
|
for item in items {
|
||||||
|
@ -1200,58 +1241,59 @@ fn display_items(items: &[PathBuf], strip: Option<&Path>, config: &Config, comma
|
||||||
max_size = size.max(max_size);
|
max_size = size.max(max_size);
|
||||||
}
|
}
|
||||||
for item in items {
|
for item in items {
|
||||||
display_item_long(item, strip, max_links, max_size, config, command_line);
|
display_item_long(item, strip, max_links, max_size, config, out);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let names = items.iter().filter_map(|i| {
|
let names = items
|
||||||
let md = get_metadata(i, false);
|
.iter()
|
||||||
match md {
|
.filter_map(|i| display_file_name(&i, strip, config));
|
||||||
Err(e) => {
|
|
||||||
let filename = get_file_name(i, strip);
|
|
||||||
show_error!("'{}': {}", filename, e);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
Ok(md) => Some(display_file_name(&i, strip, &md, config)),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
match (&config.format, config.width) {
|
match (&config.format, config.width) {
|
||||||
(Format::Columns, Some(width)) => display_grid(names, width, Direction::TopToBottom),
|
(Format::Columns, Some(width)) => {
|
||||||
(Format::Across, Some(width)) => display_grid(names, width, Direction::LeftToRight),
|
display_grid(names, width, Direction::TopToBottom, out)
|
||||||
|
}
|
||||||
|
(Format::Across, Some(width)) => {
|
||||||
|
display_grid(names, width, Direction::LeftToRight, out)
|
||||||
|
}
|
||||||
(Format::Commas, width_opt) => {
|
(Format::Commas, width_opt) => {
|
||||||
let term_width = width_opt.unwrap_or(1);
|
let term_width = width_opt.unwrap_or(1);
|
||||||
let mut current_col = 0;
|
let mut current_col = 0;
|
||||||
let mut names = names;
|
let mut names = names;
|
||||||
if let Some(name) = names.next() {
|
if let Some(name) = names.next() {
|
||||||
print!("{}", name.contents);
|
let _ = write!(out, "{}", name.contents);
|
||||||
current_col = name.width as u16 + 2;
|
current_col = name.width as u16 + 2;
|
||||||
}
|
}
|
||||||
for name in names {
|
for name in names {
|
||||||
let name_width = name.width as u16;
|
let name_width = name.width as u16;
|
||||||
if current_col + name_width + 1 > term_width {
|
if current_col + name_width + 1 > term_width {
|
||||||
current_col = name_width + 2;
|
current_col = name_width + 2;
|
||||||
print!(",\n{}", name.contents);
|
let _ = write!(out, ",\n{}", name.contents);
|
||||||
} else {
|
} else {
|
||||||
current_col += name_width + 2;
|
current_col += name_width + 2;
|
||||||
print!(", {}", name.contents);
|
let _ = write!(out, ", {}", name.contents);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Current col is never zero again if names have been printed.
|
// Current col is never zero again if names have been printed.
|
||||||
// So we print a newline.
|
// So we print a newline.
|
||||||
if current_col > 0 {
|
if current_col > 0 {
|
||||||
println!();
|
let _ = writeln!(out,);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
for name in names {
|
for name in names {
|
||||||
println!("{}", name.contents);
|
let _ = writeln!(out, "{}", name.contents);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_grid(names: impl Iterator<Item = Cell>, width: u16, direction: Direction) {
|
fn display_grid(
|
||||||
|
names: impl Iterator<Item = Cell>,
|
||||||
|
width: u16,
|
||||||
|
direction: Direction,
|
||||||
|
out: &mut BufWriter<Stdout>,
|
||||||
|
) {
|
||||||
let mut grid = Grid::new(GridOptions {
|
let mut grid = Grid::new(GridOptions {
|
||||||
filling: Filling::Spaces(2),
|
filling: Filling::Spaces(2),
|
||||||
direction,
|
direction,
|
||||||
|
@ -1262,56 +1304,44 @@ fn display_grid(names: impl Iterator<Item = Cell>, width: u16, direction: Direct
|
||||||
}
|
}
|
||||||
|
|
||||||
match grid.fit_into_width(width as usize) {
|
match grid.fit_into_width(width as usize) {
|
||||||
Some(output) => print!("{}", output),
|
Some(output) => {
|
||||||
|
let _ = write!(out, "{}", output);
|
||||||
|
}
|
||||||
// Width is too small for the grid, so we fit it in one column
|
// Width is too small for the grid, so we fit it in one column
|
||||||
None => print!("{}", grid.fit_into_columns(1)),
|
None => {
|
||||||
|
let _ = write!(out, "{}", grid.fit_into_columns(1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use uucore::fs::display_permissions;
|
use uucore::fs::display_permissions;
|
||||||
|
|
||||||
fn display_item_long(
|
fn display_item_long(
|
||||||
item: &Path,
|
item: &PathData,
|
||||||
strip: Option<&Path>,
|
strip: Option<&Path>,
|
||||||
max_links: usize,
|
max_links: usize,
|
||||||
max_size: usize,
|
max_size: usize,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
command_line: bool,
|
out: &mut BufWriter<Stdout>,
|
||||||
) {
|
) {
|
||||||
let dereference = match &config.dereference {
|
let md = match item.md() {
|
||||||
Dereference::All => true,
|
None => {
|
||||||
Dereference::Args => command_line,
|
let filename = get_file_name(&item.p_buf, strip);
|
||||||
Dereference::DirArgs => {
|
show_error!("could not show file: {}", filename);
|
||||||
if command_line {
|
|
||||||
if let Ok(md) = item.metadata() {
|
|
||||||
md.is_dir()
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Dereference::None => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let md = match get_metadata(item, dereference) {
|
|
||||||
Err(e) => {
|
|
||||||
let filename = get_file_name(&item, strip);
|
|
||||||
show_error!("{}: {}", filename, e);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Ok(md) => md,
|
Some(md) => md,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
if config.inode {
|
if config.inode {
|
||||||
print!("{} ", get_inode(&md));
|
let _ = write!(out, "{} ", get_inode(&md));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
print!(
|
let _ = write!(
|
||||||
|
out,
|
||||||
"{}{} {}",
|
"{}{} {}",
|
||||||
display_file_type(md.file_type()),
|
display_file_type(md.file_type()),
|
||||||
display_permissions(&md),
|
display_permissions(&md),
|
||||||
|
@ -1319,24 +1349,28 @@ fn display_item_long(
|
||||||
);
|
);
|
||||||
|
|
||||||
if config.long.owner {
|
if config.long.owner {
|
||||||
print!(" {}", display_uname(&md, config));
|
let _ = write!(out, " {}", display_uname(&md, config));
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.long.group {
|
if config.long.group {
|
||||||
print!(" {}", display_group(&md, config));
|
let _ = write!(out, " {}", display_group(&md, config));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Author is only different from owner on GNU/Hurd, so we reuse
|
// Author is only different from owner on GNU/Hurd, so we reuse
|
||||||
// the owner, since GNU/Hurd is not currently supported by Rust.
|
// the owner, since GNU/Hurd is not currently supported by Rust.
|
||||||
if config.long.author {
|
if config.long.author {
|
||||||
print!(" {}", display_uname(&md, config));
|
let _ = write!(out, " {}", display_uname(&md, config));
|
||||||
}
|
}
|
||||||
|
|
||||||
println!(
|
let _ = writeln!(
|
||||||
|
out,
|
||||||
" {} {} {}",
|
" {} {} {}",
|
||||||
pad_left(display_file_size(&md, config), max_size),
|
pad_left(display_file_size(&md, config), max_size),
|
||||||
display_date(&md, config),
|
display_date(&md, config),
|
||||||
display_file_name(&item, strip, &md, config).contents,
|
// unwrap is fine because it fails when metadata is not available
|
||||||
|
// but we already know that it is because it's checked at the
|
||||||
|
// start of the function.
|
||||||
|
display_file_name(&item, strip, config).unwrap().contents,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1348,23 +1382,51 @@ fn get_inode(metadata: &Metadata) -> String {
|
||||||
// Currently getpwuid is `linux` target only. If it's broken out into
|
// Currently getpwuid is `linux` target only. If it's broken out into
|
||||||
// a posix-compliant attribute this can be updated...
|
// a posix-compliant attribute this can be updated...
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
use std::sync::Mutex;
|
||||||
|
#[cfg(unix)]
|
||||||
use uucore::entries;
|
use uucore::entries;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn cached_uid2usr(uid: u32) -> String {
|
||||||
|
lazy_static! {
|
||||||
|
static ref UID_CACHE: Mutex<HashMap<u32, String>> = Mutex::new(HashMap::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut uid_cache = UID_CACHE.lock().unwrap();
|
||||||
|
uid_cache
|
||||||
|
.entry(uid)
|
||||||
|
.or_insert_with(|| entries::uid2usr(uid).unwrap_or_else(|_| uid.to_string()))
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
fn display_uname(metadata: &Metadata, config: &Config) -> String {
|
fn display_uname(metadata: &Metadata, config: &Config) -> String {
|
||||||
if config.long.numeric_uid_gid {
|
if config.long.numeric_uid_gid {
|
||||||
metadata.uid().to_string()
|
metadata.uid().to_string()
|
||||||
} else {
|
} else {
|
||||||
entries::uid2usr(metadata.uid()).unwrap_or_else(|_| metadata.uid().to_string())
|
cached_uid2usr(metadata.uid())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn cached_gid2grp(gid: u32) -> String {
|
||||||
|
lazy_static! {
|
||||||
|
static ref GID_CACHE: Mutex<HashMap<u32, String>> = Mutex::new(HashMap::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut gid_cache = GID_CACHE.lock().unwrap();
|
||||||
|
gid_cache
|
||||||
|
.entry(gid)
|
||||||
|
.or_insert_with(|| entries::gid2grp(gid).unwrap_or_else(|_| gid.to_string()))
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
fn display_group(metadata: &Metadata, config: &Config) -> String {
|
fn display_group(metadata: &Metadata, config: &Config) -> String {
|
||||||
if config.long.numeric_uid_gid {
|
if config.long.numeric_uid_gid {
|
||||||
metadata.gid().to_string()
|
metadata.gid().to_string()
|
||||||
} else {
|
} else {
|
||||||
entries::gid2grp(metadata.gid()).unwrap_or_else(|_| metadata.gid().to_string())
|
cached_gid2grp(metadata.gid())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1374,7 +1436,6 @@ fn display_uname(_metadata: &Metadata, _config: &Config) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
#[cfg(not(unix))]
|
||||||
#[allow(unused_variables)]
|
|
||||||
fn display_group(_metadata: &Metadata, _config: &Config) -> String {
|
fn display_group(_metadata: &Metadata, _config: &Config) -> String {
|
||||||
"somegroup".to_string()
|
"somegroup".to_string()
|
||||||
}
|
}
|
||||||
|
@ -1449,13 +1510,13 @@ fn display_file_size(metadata: &Metadata, config: &Config) -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_file_type(file_type: FileType) -> String {
|
fn display_file_type(file_type: FileType) -> char {
|
||||||
if file_type.is_dir() {
|
if file_type.is_dir() {
|
||||||
"d".to_string()
|
'd'
|
||||||
} else if file_type.is_symlink() {
|
} else if file_type.is_symlink() {
|
||||||
"l".to_string()
|
'l'
|
||||||
} else {
|
} else {
|
||||||
"-".to_string()
|
'-'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1470,140 +1531,58 @@ fn get_file_name(name: &Path, strip: Option<&Path>) -> String {
|
||||||
name.to_string_lossy().into_owned()
|
name.to_string_lossy().into_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
#[cfg(unix)]
|
||||||
fn display_file_name(
|
fn file_is_executable(md: &Metadata) -> bool {
|
||||||
path: &Path,
|
// Mode always returns u32, but the flags might not be, based on the platform
|
||||||
strip: Option<&Path>,
|
// e.g. linux has u32, mac has u16.
|
||||||
metadata: &Metadata,
|
// S_IXUSR -> user has execute permission
|
||||||
config: &Config,
|
// S_IXGRP -> group has execute persmission
|
||||||
) -> Cell {
|
// S_IXOTH -> other users have execute permission
|
||||||
let mut name = escape_name(get_file_name(path, strip), &config.quoting_style);
|
md.mode() & ((S_IXUSR | S_IXGRP | S_IXOTH) as u32) != 0
|
||||||
let file_type = metadata.file_type();
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::clippy::collapsible_else_if)]
|
||||||
|
fn classify_file(path: &PathData) -> Option<char> {
|
||||||
|
let file_type = path.file_type()?;
|
||||||
|
|
||||||
match config.indicator_style {
|
|
||||||
IndicatorStyle::Classify | IndicatorStyle::FileType => {
|
|
||||||
if file_type.is_dir() {
|
if file_type.is_dir() {
|
||||||
name.push('/');
|
Some('/')
|
||||||
}
|
|
||||||
if file_type.is_symlink() {
|
|
||||||
name.push('@');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
IndicatorStyle::Slash => {
|
|
||||||
if file_type.is_dir() {
|
|
||||||
name.push('/');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
};
|
|
||||||
|
|
||||||
if config.format == Format::Long && metadata.file_type().is_symlink() {
|
|
||||||
if let Ok(target) = path.read_link() {
|
|
||||||
// We don't bother updating width here because it's not used for long listings
|
|
||||||
let target_name = target.to_string_lossy().to_string();
|
|
||||||
name.push_str(" -> ");
|
|
||||||
name.push_str(&target_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
name.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
fn color_name(name: String, typ: &str) -> String {
|
|
||||||
let mut typ = typ;
|
|
||||||
if !COLOR_MAP.contains_key(typ) {
|
|
||||||
if typ == "or" {
|
|
||||||
typ = "ln";
|
|
||||||
} else if typ == "mi" {
|
|
||||||
typ = "fi";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if let Some(code) = COLOR_MAP.get(typ) {
|
|
||||||
format!(
|
|
||||||
"{}{}{}{}{}{}{}{}",
|
|
||||||
*LEFT_CODE, code, *RIGHT_CODE, name, *END_CODE, *LEFT_CODE, *RESET_CODE, *RIGHT_CODE,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
macro_rules! has {
|
|
||||||
($mode:expr, $perm:expr) => {
|
|
||||||
$mode & ($perm as mode_t) != 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
#[allow(clippy::cognitive_complexity)]
|
|
||||||
fn display_file_name(
|
|
||||||
path: &Path,
|
|
||||||
strip: Option<&Path>,
|
|
||||||
metadata: &Metadata,
|
|
||||||
config: &Config,
|
|
||||||
) -> Cell {
|
|
||||||
let mut name = escape_name(get_file_name(path, strip), &config.quoting_style);
|
|
||||||
if config.format != Format::Long && config.inode {
|
|
||||||
name = get_inode(metadata) + " " + &name;
|
|
||||||
}
|
|
||||||
let mut width = UnicodeWidthStr::width(&*name);
|
|
||||||
|
|
||||||
let ext;
|
|
||||||
if config.color || config.indicator_style != IndicatorStyle::None {
|
|
||||||
let file_type = metadata.file_type();
|
|
||||||
|
|
||||||
let (code, sym) = if file_type.is_dir() {
|
|
||||||
("di", Some('/'))
|
|
||||||
} else if file_type.is_symlink() {
|
} else if file_type.is_symlink() {
|
||||||
if path.exists() {
|
Some('@')
|
||||||
("ln", Some('@'))
|
|
||||||
} else {
|
} else {
|
||||||
("or", Some('@'))
|
#[cfg(unix)]
|
||||||
}
|
{
|
||||||
} else if file_type.is_socket() {
|
if file_type.is_socket() {
|
||||||
("so", Some('='))
|
Some('=')
|
||||||
} else if file_type.is_fifo() {
|
} else if file_type.is_fifo() {
|
||||||
("pi", Some('|'))
|
Some('|')
|
||||||
} else if file_type.is_block_device() {
|
} else if file_type.is_file() && file_is_executable(path.md()?) {
|
||||||
("bd", None)
|
|
||||||
} else if file_type.is_char_device() {
|
|
||||||
("cd", None)
|
|
||||||
} else if file_type.is_file() {
|
|
||||||
let mode = metadata.mode() as mode_t;
|
|
||||||
let sym = if has!(mode, S_IXUSR | S_IXGRP | S_IXOTH) {
|
|
||||||
Some('*')
|
Some('*')
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
|
||||||
if has!(mode, S_ISUID) {
|
|
||||||
("su", sym)
|
|
||||||
} else if has!(mode, S_ISGID) {
|
|
||||||
("sg", sym)
|
|
||||||
} else if has!(mode, S_ISVTX) && has!(mode, S_IWOTH) {
|
|
||||||
("tw", sym)
|
|
||||||
} else if has!(mode, S_ISVTX) {
|
|
||||||
("st", sym)
|
|
||||||
} else if has!(mode, S_IWOTH) {
|
|
||||||
("ow", sym)
|
|
||||||
} else if has!(mode, S_IXUSR | S_IXGRP | S_IXOTH) {
|
|
||||||
("ex", sym)
|
|
||||||
} else if metadata.nlink() > 1 {
|
|
||||||
("mh", sym)
|
|
||||||
} else if let Some(e) = path.extension() {
|
|
||||||
ext = format!("*.{}", e.to_string_lossy());
|
|
||||||
(ext.as_str(), None)
|
|
||||||
} else {
|
|
||||||
("fi", None)
|
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
("", None)
|
#[cfg(not(unix))]
|
||||||
};
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if config.color {
|
fn display_file_name(path: &PathData, strip: Option<&Path>, config: &Config) -> Option<Cell> {
|
||||||
name = color_name(name, code);
|
let mut name = escape_name(get_file_name(&path.p_buf, strip), &config.quoting_style);
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
if config.format != Format::Long && config.inode {
|
||||||
|
name = get_inode(path.md()?) + " " + &name;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ls_colors) = &config.color {
|
||||||
|
name = color_name(&ls_colors, &path.p_buf, name, path.md()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.indicator_style != IndicatorStyle::None {
|
||||||
|
let sym = classify_file(path);
|
||||||
|
|
||||||
let char_opt = match config.indicator_style {
|
let char_opt = match config.indicator_style {
|
||||||
IndicatorStyle::Classify => sym,
|
IndicatorStyle::Classify => sym,
|
||||||
|
@ -1626,23 +1605,23 @@ fn display_file_name(
|
||||||
|
|
||||||
if let Some(c) = char_opt {
|
if let Some(c) = char_opt {
|
||||||
name.push(c);
|
name.push(c);
|
||||||
width += 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.format == Format::Long && metadata.file_type().is_symlink() {
|
if config.format == Format::Long && path.file_type()?.is_symlink() {
|
||||||
if let Ok(target) = path.read_link() {
|
if let Ok(target) = path.p_buf.read_link() {
|
||||||
// We don't bother updating width here because it's not used for long listings
|
|
||||||
let code = if target.exists() { "fi" } else { "mi" };
|
|
||||||
let target_name = color_name(target.to_string_lossy().to_string(), code);
|
|
||||||
name.push_str(" -> ");
|
name.push_str(" -> ");
|
||||||
name.push_str(&target_name);
|
name.push_str(&target.to_string_lossy());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Cell {
|
Some(name.into())
|
||||||
contents: name,
|
}
|
||||||
width,
|
|
||||||
|
fn color_name(ls_colors: &LsColors, path: &Path, name: String, md: &Metadata) -> String {
|
||||||
|
match ls_colors.style_for_path_with_metadata(path, Some(&md)) {
|
||||||
|
Some(style) => style.to_ansi_term_style().paint(name).to_string(),
|
||||||
|
None => name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::char::from_digit;
|
use std::char::from_digit;
|
||||||
|
|
||||||
const SPECIAL_SHELL_CHARS: &str = "~`#$&*()\\|[]{};'\"<>?! ";
|
const SPECIAL_SHELL_CHARS: &str = "~`#$&*()|[]{};\\'\"<>?! ";
|
||||||
|
|
||||||
pub(super) enum QuotingStyle {
|
pub(super) enum QuotingStyle {
|
||||||
Shell {
|
Shell {
|
||||||
|
@ -27,12 +27,10 @@ pub(super) enum Quotes {
|
||||||
// This implementation is heavily inspired by the std::char::EscapeDefault implementation
|
// This implementation is heavily inspired by the std::char::EscapeDefault implementation
|
||||||
// in the Rust standard library. This custom implementation is needed because the
|
// in the Rust standard library. This custom implementation is needed because the
|
||||||
// characters \a, \b, \e, \f & \v are not recognized by Rust.
|
// characters \a, \b, \e, \f & \v are not recognized by Rust.
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
struct EscapedChar {
|
struct EscapedChar {
|
||||||
state: EscapeState,
|
state: EscapeState,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
enum EscapeState {
|
enum EscapeState {
|
||||||
Done,
|
Done,
|
||||||
Char(char),
|
Char(char),
|
||||||
|
@ -41,14 +39,12 @@ enum EscapeState {
|
||||||
Octal(EscapeOctal),
|
Octal(EscapeOctal),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
struct EscapeOctal {
|
struct EscapeOctal {
|
||||||
c: char,
|
c: char,
|
||||||
state: EscapeOctalState,
|
state: EscapeOctalState,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
enum EscapeOctalState {
|
enum EscapeOctalState {
|
||||||
Done,
|
Done,
|
||||||
Backslash,
|
Backslash,
|
||||||
|
@ -135,7 +131,6 @@ impl EscapedChar {
|
||||||
'\x0B' => Backslash('v'),
|
'\x0B' => Backslash('v'),
|
||||||
'\x0C' => Backslash('f'),
|
'\x0C' => Backslash('f'),
|
||||||
'\r' => Backslash('r'),
|
'\r' => Backslash('r'),
|
||||||
'\\' => Backslash('\\'),
|
|
||||||
'\x00'..='\x1F' | '\x7F' => Octal(EscapeOctal::from(c)),
|
'\x00'..='\x1F' | '\x7F' => Octal(EscapeOctal::from(c)),
|
||||||
'\'' => match quotes {
|
'\'' => match quotes {
|
||||||
Quotes::Single => Backslash('\''),
|
Quotes::Single => Backslash('\''),
|
||||||
|
@ -511,6 +506,23 @@ mod tests {
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// A control character followed by a special shell character
|
||||||
|
check_names(
|
||||||
|
"one\n&two",
|
||||||
|
vec![
|
||||||
|
("one?&two", "literal"),
|
||||||
|
("one\n&two", "literal-show"),
|
||||||
|
("one\\n&two", "escape"),
|
||||||
|
("\"one\\n&two\"", "c"),
|
||||||
|
("'one?&two'", "shell"),
|
||||||
|
("'one\n&two'", "shell-show"),
|
||||||
|
("'one?&two'", "shell-always"),
|
||||||
|
("'one\n&two'", "shell-always-show"),
|
||||||
|
("'one'$'\\n''&two'", "shell-escape"),
|
||||||
|
("'one'$'\\n''&two'", "shell-escape-always"),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
// The first 16 control characters. NUL is also included, even though it is of
|
// The first 16 control characters. NUL is also included, even though it is of
|
||||||
// no importance for file names.
|
// no importance for file names.
|
||||||
check_names(
|
check_names(
|
||||||
|
@ -627,4 +639,22 @@ mod tests {
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_backslash() {
|
||||||
|
// Escaped in C-style, but not in Shell-style escaping
|
||||||
|
check_names(
|
||||||
|
"one\\two",
|
||||||
|
vec![
|
||||||
|
("one\\two", "literal"),
|
||||||
|
("one\\two", "literal-show"),
|
||||||
|
("one\\\\two", "escape"),
|
||||||
|
("\"one\\\\two\"", "c"),
|
||||||
|
("'one\\two'", "shell"),
|
||||||
|
("\'one\\two\'", "shell-always"),
|
||||||
|
("'one\\two'", "shell-escape"),
|
||||||
|
("'one\\two'", "shell-escape-always"),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,6 @@
|
||||||
|
|
||||||
// spell-checker:ignore (ToDO) parsemode makedev sysmacros makenod newmode perror IFBLK IFCHR IFIFO
|
// spell-checker:ignore (ToDO) parsemode makedev sysmacros makenod newmode perror IFBLK IFCHR IFIFO
|
||||||
|
|
||||||
mod parsemode;
|
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate uucore;
|
extern crate uucore;
|
||||||
|
|
||||||
|
@ -98,7 +96,7 @@ for details about the options it supports.",
|
||||||
let mut last_umask: mode_t = 0;
|
let mut last_umask: mode_t = 0;
|
||||||
let mut newmode: mode_t = MODE_RW_UGO;
|
let mut newmode: mode_t = MODE_RW_UGO;
|
||||||
if matches.opt_present("mode") {
|
if matches.opt_present("mode") {
|
||||||
match parsemode::parse_mode(matches.opt_str("mode")) {
|
match uucore::mode::parse_mode(matches.opt_str("mode")) {
|
||||||
Ok(parsed) => {
|
Ok(parsed) => {
|
||||||
if parsed > 0o777 {
|
if parsed > 0o777 {
|
||||||
show_info!("mode must specify only file permission bits");
|
show_info!("mode must specify only file permission bits");
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
// spell-checker:ignore (ToDO) fperm
|
|
||||||
|
|
||||||
use libc::{mode_t, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR};
|
|
||||||
|
|
||||||
use uucore::mode;
|
|
||||||
|
|
||||||
pub fn parse_mode(mode: Option<String>) -> Result<mode_t, String> {
|
|
||||||
let fperm = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
|
|
||||||
if let Some(mode) = mode {
|
|
||||||
let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
|
||||||
let result = if mode.contains(arr) {
|
|
||||||
mode::parse_numeric(fperm as u32, mode.as_str())
|
|
||||||
} else {
|
|
||||||
mode::parse_symbolic(fperm as u32, mode.as_str(), true)
|
|
||||||
};
|
|
||||||
result.map(|mode| mode as mode_t)
|
|
||||||
} else {
|
|
||||||
Ok(fperm)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
/// Test if the program is running under WSL
|
|
||||||
// ref: <https://github.com/microsoft/WSL/issues/4555> @@ <https://archive.is/dP0bz>
|
|
||||||
// ToDO: test on WSL2 which likely doesn't need special handling; plan change to `is_wsl_1()` if WSL2 is less needy
|
|
||||||
pub fn is_wsl() -> bool {
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
{
|
|
||||||
if let Ok(b) = std::fs::read("/proc/sys/kernel/osrelease") {
|
|
||||||
if let Ok(s) = std::str::from_utf8(&b) {
|
|
||||||
let a = s.to_ascii_lowercase();
|
|
||||||
return a.contains("microsoft") || a.contains("wsl");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn symbolic_modes() {
|
|
||||||
assert_eq!(super::parse_mode(Some("u+x".to_owned())).unwrap(), 0o766);
|
|
||||||
assert_eq!(
|
|
||||||
super::parse_mode(Some("+x".to_owned())).unwrap(),
|
|
||||||
if !is_wsl() { 0o777 } else { 0o776 }
|
|
||||||
);
|
|
||||||
assert_eq!(super::parse_mode(Some("a-w".to_owned())).unwrap(), 0o444);
|
|
||||||
assert_eq!(super::parse_mode(Some("g-r".to_owned())).unwrap(), 0o626);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn numeric_modes() {
|
|
||||||
assert_eq!(super::parse_mode(Some("644".to_owned())).unwrap(), 0o644);
|
|
||||||
assert_eq!(super::parse_mode(Some("+100".to_owned())).unwrap(), 0o766);
|
|
||||||
assert_eq!(super::parse_mode(Some("-4".to_owned())).unwrap(), 0o662);
|
|
||||||
assert_eq!(super::parse_mode(None).unwrap(), 0o666);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -18,18 +18,15 @@ pub fn err_msg(msg: &str) {
|
||||||
|
|
||||||
// by default stdout only flushes
|
// by default stdout only flushes
|
||||||
// to console when a newline is passed.
|
// to console when a newline is passed.
|
||||||
#[allow(unused_must_use)]
|
|
||||||
pub fn flush_char(c: char) {
|
pub fn flush_char(c: char) {
|
||||||
print!("{}", c);
|
print!("{}", c);
|
||||||
stdout().flush();
|
let _ = stdout().flush();
|
||||||
}
|
}
|
||||||
#[allow(unused_must_use)]
|
|
||||||
pub fn flush_str(s: &str) {
|
pub fn flush_str(s: &str) {
|
||||||
print!("{}", s);
|
print!("{}", s);
|
||||||
stdout().flush();
|
let _ = stdout().flush();
|
||||||
}
|
}
|
||||||
#[allow(unused_must_use)]
|
|
||||||
pub fn flush_bytes(bslice: &[u8]) {
|
pub fn flush_bytes(bslice: &[u8]) {
|
||||||
stdout().write(bslice);
|
let _ = stdout().write(bslice);
|
||||||
stdout().flush();
|
let _ = stdout().flush();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
// spell-checker:ignore (change!) each's
|
// spell-checker:ignore (change!) each's
|
||||||
// spell-checker:ignore (ToDO) LONGHELP FORMATSTRING templating parameterizing formatstr
|
// spell-checker:ignore (ToDO) LONGHELP FORMATSTRING templating parameterizing formatstr
|
||||||
|
|
||||||
|
@ -9,7 +8,6 @@ mod tokenize;
|
||||||
|
|
||||||
static NAME: &str = "printf";
|
static NAME: &str = "printf";
|
||||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
static SHORT_USAGE: &str = "printf: usage: printf [-v var] format [arguments]";
|
|
||||||
static LONGHELP_LEAD: &str = "printf
|
static LONGHELP_LEAD: &str = "printf
|
||||||
|
|
||||||
USAGE: printf FORMATSTRING [ARGUMENT]...
|
USAGE: printf FORMATSTRING [ARGUMENT]...
|
||||||
|
|
|
@ -28,8 +28,7 @@ pub fn arrnum_int_mult(arr_num: &[u8], basenum: u8, base_ten_int_fact: u8) -> Ve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[allow(clippy::map_clone)]
|
let ret: Vec<u8> = ret_rev.into_iter().rev().collect();
|
||||||
let ret: Vec<u8> = ret_rev.iter().rev().map(|x| *x).collect();
|
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,70 +101,6 @@ pub fn arrnum_int_div_step(
|
||||||
remainder: rem_out,
|
remainder: rem_out,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// pub struct ArrFloat {
|
|
||||||
// pub leading_zeros: u8,
|
|
||||||
// pub values: Vec<u8>,
|
|
||||||
// pub basenum: u8
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// pub struct ArrFloatDivOut {
|
|
||||||
// pub quotient: u8,
|
|
||||||
// pub remainder: ArrFloat
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// pub fn arrfloat_int_div(
|
|
||||||
// arrfloat_in : &ArrFloat,
|
|
||||||
// base_ten_int_divisor : u8,
|
|
||||||
// precision : u16
|
|
||||||
// ) -> DivOut {
|
|
||||||
//
|
|
||||||
// let mut remainder = ArrFloat {
|
|
||||||
// basenum: arrfloat_in.basenum,
|
|
||||||
// leading_zeros: arrfloat_in.leading_zeroes,
|
|
||||||
// values: Vec<u8>::new()
|
|
||||||
// }
|
|
||||||
// let mut quotient = 0;
|
|
||||||
//
|
|
||||||
// let mut bufferval : u16 = 0;
|
|
||||||
// let base : u16 = arrfloat_in.basenum as u16;
|
|
||||||
// let divisor : u16 = base_ten_int_divisor as u16;
|
|
||||||
//
|
|
||||||
// let mut it_f = arrfloat_in.values.iter();
|
|
||||||
// let mut position = 0 + arrfloat_in.leading_zeroes as u16;
|
|
||||||
// let mut at_end = false;
|
|
||||||
// while position< precision {
|
|
||||||
// let next_digit = match it_f.next() {
|
|
||||||
// Some(c) => {}
|
|
||||||
// None => { 0 }
|
|
||||||
// }
|
|
||||||
// match u_cur {
|
|
||||||
// Some(u) => {
|
|
||||||
// bufferval += u.clone() as u16;
|
|
||||||
// if bufferval > divisor {
|
|
||||||
// while bufferval >= divisor {
|
|
||||||
// quotient+=1;
|
|
||||||
// bufferval -= divisor;
|
|
||||||
// }
|
|
||||||
// if bufferval == 0 {
|
|
||||||
// rem_out.position +=1;
|
|
||||||
// } else {
|
|
||||||
// rem_out.replace = Some(bufferval as u8);
|
|
||||||
// }
|
|
||||||
// break;
|
|
||||||
// } else {
|
|
||||||
// bufferval *= base;
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// None => {
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// u_cur = it_f.next().clone();
|
|
||||||
// rem_out.position+=1;
|
|
||||||
// }
|
|
||||||
// ArrFloatDivOut { quotient: quotient, remainder: remainder }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
pub fn arrnum_int_add(arrnum: &[u8], basenum: u8, base_ten_int_term: u8) -> Vec<u8> {
|
pub fn arrnum_int_add(arrnum: &[u8], basenum: u8, base_ten_int_term: u8) -> Vec<u8> {
|
||||||
let mut carry: u16 = u16::from(base_ten_int_term);
|
let mut carry: u16 = u16::from(base_ten_int_term);
|
||||||
let mut rem: u16;
|
let mut rem: u16;
|
||||||
|
@ -193,8 +128,7 @@ pub fn arrnum_int_add(arrnum: &[u8], basenum: u8, base_ten_int_term: u8) -> Vec<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[allow(clippy::map_clone)]
|
let ret: Vec<u8> = ret_rev.into_iter().rev().collect();
|
||||||
let ret: Vec<u8> = ret_rev.iter().rev().map(|x| *x).collect();
|
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,8 +153,7 @@ pub fn unsigned_to_arrnum(src: u16) -> Vec<u8> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// temporary needs-improvement-function
|
// temporary needs-improvement-function
|
||||||
#[allow(unused_variables)]
|
pub fn base_conv_float(src: &[u8], radix_src: u8, _radix_dest: u8) -> f64 {
|
||||||
pub fn base_conv_float(src: &[u8], radix_src: u8, radix_dest: u8) -> f64 {
|
|
||||||
// it would require a lot of addl code
|
// it would require a lot of addl code
|
||||||
// to implement this for arbitrary string input.
|
// to implement this for arbitrary string input.
|
||||||
// until then, the below operates as an outline
|
// until then, the below operates as an outline
|
||||||
|
@ -267,7 +200,6 @@ pub fn arrnum_to_str(src: &[u8], radix_def_dest: &dyn RadixDef) -> String {
|
||||||
str_out
|
str_out
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
pub fn base_conv_str(
|
pub fn base_conv_str(
|
||||||
src: &str,
|
src: &str,
|
||||||
radix_def_src: &dyn RadixDef,
|
radix_def_src: &dyn RadixDef,
|
||||||
|
|
|
@ -43,45 +43,15 @@ impl Formatter for CninetyNineHexFloatf {
|
||||||
// c99 hex has unique requirements of all floating point subs in pretty much every part of building a primitive, from prefix and suffix to need for base conversion (in all other cases if you don't have decimal you must have decimal, here it's the other way around)
|
// c99 hex has unique requirements of all floating point subs in pretty much every part of building a primitive, from prefix and suffix to need for base conversion (in all other cases if you don't have decimal you must have decimal, here it's the other way around)
|
||||||
|
|
||||||
// on the todo list is to have a trait for get_primitive that is implemented by each float formatter and can override a default. when that happens we can take the parts of get_primitive_dec specific to dec and spin them out to their own functions that can be overridden.
|
// on the todo list is to have a trait for get_primitive that is implemented by each float formatter and can override a default. when that happens we can take the parts of get_primitive_dec specific to dec and spin them out to their own functions that can be overridden.
|
||||||
#[allow(unused_variables)]
|
|
||||||
#[allow(unused_assignments)]
|
|
||||||
fn get_primitive_hex(
|
fn get_primitive_hex(
|
||||||
inprefix: &InPrefix,
|
inprefix: &InPrefix,
|
||||||
str_in: &str,
|
_str_in: &str,
|
||||||
analysis: &FloatAnalysis,
|
_analysis: &FloatAnalysis,
|
||||||
last_dec_place: usize,
|
_last_dec_place: usize,
|
||||||
capitalized: bool,
|
capitalized: bool,
|
||||||
) -> FormatPrimitive {
|
) -> FormatPrimitive {
|
||||||
let prefix = Some(String::from(if inprefix.sign == -1 { "-0x" } else { "0x" }));
|
let prefix = Some(String::from(if inprefix.sign == -1 { "-0x" } else { "0x" }));
|
||||||
|
|
||||||
// assign the digits before and after the decimal points
|
|
||||||
// to separate slices. If no digits after decimal point,
|
|
||||||
// assign 0
|
|
||||||
let (mut first_segment_raw, second_segment_raw) = match analysis.decimal_pos {
|
|
||||||
Some(pos) => (&str_in[..pos], &str_in[pos + 1..]),
|
|
||||||
None => (str_in, "0"),
|
|
||||||
};
|
|
||||||
if first_segment_raw.is_empty() {
|
|
||||||
first_segment_raw = "0";
|
|
||||||
}
|
|
||||||
// convert to string, hexifying if input is in dec.
|
|
||||||
// let (first_segment, second_segment) =
|
|
||||||
// match inprefix.radix_in {
|
|
||||||
// Base::Ten => {
|
|
||||||
// (to_hex(first_segment_raw, true),
|
|
||||||
// to_hex(second_segment_raw, false))
|
|
||||||
// }
|
|
||||||
// _ => {
|
|
||||||
// (String::from(first_segment_raw),
|
|
||||||
// String::from(second_segment_raw))
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// f.pre_decimal = Some(first_segment);
|
|
||||||
// f.post_decimal = Some(second_segment);
|
|
||||||
//
|
|
||||||
|
|
||||||
// TODO actual conversion, make sure to get back mantissa.
|
// TODO actual conversion, make sure to get back mantissa.
|
||||||
// for hex to hex, it's really just a matter of moving the
|
// for hex to hex, it's really just a matter of moving the
|
||||||
// decimal point and calculating the mantissa by its initial
|
// decimal point and calculating the mantissa by its initial
|
||||||
|
|
|
@ -22,12 +22,11 @@ fn get_len_fprim(fprim: &FormatPrimitive) -> usize {
|
||||||
len
|
len
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Decf {
|
pub struct Decf;
|
||||||
as_num: f64,
|
|
||||||
}
|
|
||||||
impl Decf {
|
impl Decf {
|
||||||
pub fn new() -> Decf {
|
pub fn new() -> Decf {
|
||||||
Decf { as_num: 0.0 }
|
Decf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Formatter for Decf {
|
impl Formatter for Decf {
|
||||||
|
|
|
@ -5,12 +5,10 @@ use super::super::format_field::FormatField;
|
||||||
use super::super::formatter::{FormatPrimitive, Formatter, InPrefix};
|
use super::super::formatter::{FormatPrimitive, Formatter, InPrefix};
|
||||||
use super::float_common::{get_primitive_dec, primitive_to_str_common, FloatAnalysis};
|
use super::float_common::{get_primitive_dec, primitive_to_str_common, FloatAnalysis};
|
||||||
|
|
||||||
pub struct Floatf {
|
pub struct Floatf;
|
||||||
as_num: f64,
|
|
||||||
}
|
|
||||||
impl Floatf {
|
impl Floatf {
|
||||||
pub fn new() -> Floatf {
|
pub fn new() -> Floatf {
|
||||||
Floatf { as_num: 0.0 }
|
Floatf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Formatter for Floatf {
|
impl Formatter for Floatf {
|
||||||
|
|
|
@ -11,7 +11,7 @@ use std::i64;
|
||||||
use std::u64;
|
use std::u64;
|
||||||
|
|
||||||
pub struct Intf {
|
pub struct Intf {
|
||||||
a: u32,
|
_a: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
// see the Intf::analyze() function below
|
// see the Intf::analyze() function below
|
||||||
|
@ -24,7 +24,7 @@ struct IntAnalysis {
|
||||||
|
|
||||||
impl Intf {
|
impl Intf {
|
||||||
pub fn new() -> Intf {
|
pub fn new() -> Intf {
|
||||||
Intf { a: 0 }
|
Intf { _a: 0 }
|
||||||
}
|
}
|
||||||
// take a ref to argument string, and basic information
|
// take a ref to argument string, and basic information
|
||||||
// about prefix (offset, radix, sign), and analyze string
|
// about prefix (offset, radix, sign), and analyze string
|
||||||
|
|
|
@ -5,12 +5,11 @@ use super::super::format_field::FormatField;
|
||||||
use super::super::formatter::{FormatPrimitive, Formatter, InPrefix};
|
use super::super::formatter::{FormatPrimitive, Formatter, InPrefix};
|
||||||
use super::float_common::{get_primitive_dec, primitive_to_str_common, FloatAnalysis};
|
use super::float_common::{get_primitive_dec, primitive_to_str_common, FloatAnalysis};
|
||||||
|
|
||||||
pub struct Scif {
|
pub struct Scif;
|
||||||
as_num: f64,
|
|
||||||
}
|
|
||||||
impl Scif {
|
impl Scif {
|
||||||
pub fn new() -> Scif {
|
pub fn new() -> Scif {
|
||||||
Scif { as_num: 0.0 }
|
Scif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Formatter for Scif {
|
impl Formatter for Scif {
|
||||||
|
|
|
@ -242,18 +242,16 @@ impl UnescapedText {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[allow(unused_variables)]
|
|
||||||
impl token::Tokenizer for UnescapedText {
|
impl token::Tokenizer for UnescapedText {
|
||||||
fn from_it(
|
fn from_it(
|
||||||
it: &mut PutBackN<Chars>,
|
it: &mut PutBackN<Chars>,
|
||||||
args: &mut Peekable<Iter<String>>,
|
_: &mut Peekable<Iter<String>>,
|
||||||
) -> Option<Box<dyn token::Token>> {
|
) -> Option<Box<dyn token::Token>> {
|
||||||
UnescapedText::from_it_core(it, false)
|
UnescapedText::from_it_core(it, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[allow(unused_variables)]
|
|
||||||
impl token::Token for UnescapedText {
|
impl token::Token for UnescapedText {
|
||||||
fn print(&self, pf_args_it: &mut Peekable<Iter<String>>) {
|
fn print(&self, _: &mut Peekable<Iter<String>>) {
|
||||||
cli::flush_bytes(&self.0[..]);
|
cli::flush_bytes(&self.0[..]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,8 @@ clap = "2.33"
|
||||||
fnv = "1.0.7"
|
fnv = "1.0.7"
|
||||||
itertools = "0.10.0"
|
itertools = "0.10.0"
|
||||||
semver = "0.9.0"
|
semver = "0.9.0"
|
||||||
smallvec = { version = "1.6.1", features = ["serde"] }
|
smallvec = "1.6.1"
|
||||||
|
unicode-width = "0.1.8"
|
||||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] }
|
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] }
|
||||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||||
tempdir = "0.3.7"
|
tempdir = "0.3.7"
|
||||||
|
|
|
@ -138,7 +138,15 @@ impl NumInfo {
|
||||||
sign: if had_digit { sign } else { Sign::Positive },
|
sign: if had_digit { sign } else { Sign::Positive },
|
||||||
exponent: 0,
|
exponent: 0,
|
||||||
},
|
},
|
||||||
0..0,
|
if had_digit {
|
||||||
|
// In this case there were only zeroes.
|
||||||
|
// For debug output to work properly, we have to claim to match the end of the number.
|
||||||
|
num.len()..num.len()
|
||||||
|
} else {
|
||||||
|
// This was no number at all.
|
||||||
|
// For debug output to work properly, we have to claim to match the start of the number.
|
||||||
|
0..0
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,6 @@ use rayon::prelude::*;
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
use serde::{Deserializer, Deserialize, Serialize};
|
use serde::{Deserializer, Deserialize, Serialize};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::collections::BinaryHeap;
|
use std::collections::BinaryHeap;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
@ -38,9 +37,10 @@ use std::fs::File;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Lines, Read, Write};
|
use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Lines, Read, Write};
|
||||||
use std::mem::replace;
|
use std::mem::replace;
|
||||||
use std::ops::{Range, RangeInclusive};
|
use std::ops::Range;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::Path;
|
||||||
use uucore::fs::is_stdin_interactive; // for Iterator::dedup();
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
use uucore::fs::is_stdin_interactive; // for Iterator::dedup()
|
||||||
|
|
||||||
static NAME: &str = "sort";
|
static NAME: &str = "sort";
|
||||||
static ABOUT: &str = "Display sorted concatenation of all FILE(s).";
|
static ABOUT: &str = "Display sorted concatenation of all FILE(s).";
|
||||||
|
@ -66,6 +66,7 @@ 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";
|
||||||
static OPT_CHECK_SILENT: &str = "check-silent";
|
static OPT_CHECK_SILENT: &str = "check-silent";
|
||||||
|
static OPT_DEBUG: &str = "debug";
|
||||||
static OPT_IGNORE_CASE: &str = "ignore-case";
|
static OPT_IGNORE_CASE: &str = "ignore-case";
|
||||||
static OPT_IGNORE_BLANKS: &str = "ignore-blanks";
|
static OPT_IGNORE_BLANKS: &str = "ignore-blanks";
|
||||||
static OPT_IGNORE_NONPRINTING: &str = "ignore-nonprinting";
|
static OPT_IGNORE_NONPRINTING: &str = "ignore-nonprinting";
|
||||||
|
@ -94,7 +95,7 @@ static DEFAULT_TMPDIR: &str = r"/tmp";
|
||||||
// 16GB buffer for Vec<Line> before we dump to disk, never used
|
// 16GB buffer for Vec<Line> before we dump to disk, never used
|
||||||
static DEFAULT_BUF_SIZE: usize = 16000000000;
|
static DEFAULT_BUF_SIZE: usize = 16000000000;
|
||||||
|
|
||||||
#[derive(Eq, Ord, PartialEq, PartialOrd, Clone)]
|
#[derive(Eq, Ord, PartialEq, PartialOrd, Clone, Copy)]
|
||||||
enum SortMode {
|
enum SortMode {
|
||||||
Numeric,
|
Numeric,
|
||||||
HumanNumeric,
|
HumanNumeric,
|
||||||
|
@ -106,6 +107,7 @@ enum SortMode {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct GlobalSettings {
|
struct GlobalSettings {
|
||||||
mode: SortMode,
|
mode: SortMode,
|
||||||
|
debug: bool,
|
||||||
ignore_blanks: bool,
|
ignore_blanks: bool,
|
||||||
ignore_case: bool,
|
ignore_case: bool,
|
||||||
dictionary_order: bool,
|
dictionary_order: bool,
|
||||||
|
@ -154,6 +156,7 @@ impl Default for GlobalSettings {
|
||||||
fn default() -> GlobalSettings {
|
fn default() -> GlobalSettings {
|
||||||
GlobalSettings {
|
GlobalSettings {
|
||||||
mode: SortMode::Default,
|
mode: SortMode::Default,
|
||||||
|
debug: false,
|
||||||
ignore_blanks: false,
|
ignore_blanks: false,
|
||||||
ignore_case: false,
|
ignore_case: false,
|
||||||
dictionary_order: false,
|
dictionary_order: false,
|
||||||
|
@ -190,7 +193,7 @@ struct KeySettings {
|
||||||
impl From<&GlobalSettings> for KeySettings {
|
impl From<&GlobalSettings> for KeySettings {
|
||||||
fn from(settings: &GlobalSettings) -> Self {
|
fn from(settings: &GlobalSettings) -> Self {
|
||||||
Self {
|
Self {
|
||||||
mode: settings.mode.clone(),
|
mode: settings.mode,
|
||||||
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,
|
||||||
|
@ -236,7 +239,7 @@ impl SelectionRange {
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
enum NumCache {
|
enum NumCache {
|
||||||
#[serde(deserialize_with="bailout_parse_f64")]
|
#[serde(deserialize_with="bailout_parse_f64")]
|
||||||
AsF64(f64),
|
AsF64(GeneralF64ParseResult),
|
||||||
WithInfo(NumInfo),
|
WithInfo(NumInfo),
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
@ -250,7 +253,7 @@ fn bailout_parse_f64<'de, D>(d: D) -> Result<f64, D::Error> where D: Deserialize
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NumCache {
|
impl NumCache {
|
||||||
fn as_f64(&self) -> f64 {
|
fn as_f64(&self) -> GeneralF64ParseResult {
|
||||||
match self {
|
match self {
|
||||||
NumCache::AsF64(n) => *n,
|
NumCache::AsF64(n) => *n,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
@ -309,18 +312,13 @@ impl Line {
|
||||||
.selectors
|
.selectors
|
||||||
.iter()
|
.iter()
|
||||||
.map(|selector| {
|
.map(|selector| {
|
||||||
let mut range =
|
let range = selector.get_selection(&line, fields.as_deref());
|
||||||
if let Some(range) = selector.get_field_selection(&line, fields.as_deref()) {
|
let mut range = if let Some(transformed) =
|
||||||
if let Some(transformed) =
|
|
||||||
transform(&line[range.to_owned()], &selector.settings)
|
transform(&line[range.to_owned()], &selector.settings)
|
||||||
{
|
{
|
||||||
SelectionRange::String(transformed)
|
SelectionRange::String(transformed)
|
||||||
} else {
|
} else {
|
||||||
SelectionRange::ByIndex(range.start().to_owned()..range.end() + 1)
|
SelectionRange::ByIndex(range)
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If there is no match, match the empty string.
|
|
||||||
SelectionRange::ByIndex(0..0)
|
|
||||||
};
|
};
|
||||||
let num_cache = if selector.settings.mode == SortMode::Numeric
|
let num_cache = if selector.settings.mode == SortMode::Numeric
|
||||||
|| selector.settings.mode == SortMode::HumanNumeric
|
|| selector.settings.mode == SortMode::HumanNumeric
|
||||||
|
@ -336,7 +334,8 @@ impl Line {
|
||||||
range.shorten(num_range);
|
range.shorten(num_range);
|
||||||
NumCache::WithInfo(info)
|
NumCache::WithInfo(info)
|
||||||
} else if selector.settings.mode == SortMode::GeneralNumeric && settings.buffer_size == DEFAULT_BUF_SIZE {
|
} else if selector.settings.mode == SortMode::GeneralNumeric && settings.buffer_size == DEFAULT_BUF_SIZE {
|
||||||
NumCache::AsF64(permissive_f64_parse(get_leading_gen(range.get_str(&line))))
|
let str = range.get_str(&line);
|
||||||
|
NumCache::AsF64(general_f64_parse(&str[get_leading_gen(str)]))
|
||||||
} else {
|
} else {
|
||||||
NumCache::None
|
NumCache::None
|
||||||
};
|
};
|
||||||
|
@ -345,6 +344,129 @@ impl Line {
|
||||||
.collect();
|
.collect();
|
||||||
Self { line, selections }
|
Self { line, selections }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Writes indicators for the selections this line matched. The original line content is NOT expected
|
||||||
|
/// to be already printed.
|
||||||
|
fn print_debug(
|
||||||
|
&self,
|
||||||
|
settings: &GlobalSettings,
|
||||||
|
writer: &mut dyn Write,
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
// We do not consider this function performance critical, as debug output is only useful for small files,
|
||||||
|
// which are not a performance problem in any case. Therefore there aren't any special performance
|
||||||
|
// optimizations here.
|
||||||
|
|
||||||
|
let line = self.line.replace('\t', ">");
|
||||||
|
writeln!(writer, "{}", line)?;
|
||||||
|
|
||||||
|
let fields = tokenize(&self.line, settings.separator);
|
||||||
|
for selector in settings.selectors.iter() {
|
||||||
|
let mut selection = selector.get_selection(&self.line, Some(&fields));
|
||||||
|
match selector.settings.mode {
|
||||||
|
SortMode::Numeric | SortMode::HumanNumeric => {
|
||||||
|
// find out which range is used for numeric comparisons
|
||||||
|
let (_, num_range) = NumInfo::parse(
|
||||||
|
&self.line[selection.clone()],
|
||||||
|
NumInfoParseSettings {
|
||||||
|
accept_si_units: selector.settings.mode == SortMode::HumanNumeric,
|
||||||
|
thousands_separator: Some(THOUSANDS_SEP),
|
||||||
|
decimal_pt: Some(DECIMAL_PT),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let initial_selection = selection.clone();
|
||||||
|
|
||||||
|
// Shorten selection to num_range.
|
||||||
|
selection.start += num_range.start;
|
||||||
|
selection.end = selection.start + num_range.len();
|
||||||
|
|
||||||
|
// include a trailing si unit
|
||||||
|
if selector.settings.mode == SortMode::HumanNumeric
|
||||||
|
&& self.line[selection.end..initial_selection.end]
|
||||||
|
.starts_with(&['k', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'][..])
|
||||||
|
{
|
||||||
|
selection.end += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// include leading zeroes, a leading minus or a leading decimal point
|
||||||
|
while self.line[initial_selection.start..selection.start]
|
||||||
|
.ends_with(&['-', '0', '.'][..])
|
||||||
|
{
|
||||||
|
selection.start -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SortMode::GeneralNumeric => {
|
||||||
|
let initial_selection = &self.line[selection.clone()];
|
||||||
|
|
||||||
|
let leading = get_leading_gen(initial_selection);
|
||||||
|
|
||||||
|
// Shorten selection to leading.
|
||||||
|
selection.start += leading.start;
|
||||||
|
selection.end = selection.start + leading.len();
|
||||||
|
}
|
||||||
|
SortMode::Month => {
|
||||||
|
let initial_selection = &self.line[selection.clone()];
|
||||||
|
|
||||||
|
let month = if month_parse(initial_selection) == Month::Unknown {
|
||||||
|
// We failed to parse a month, which is equivalent to matching nothing.
|
||||||
|
0..0
|
||||||
|
} else {
|
||||||
|
// We parsed a month. Match the three first non-whitespace characters, which must be the month we parsed.
|
||||||
|
let mut chars = initial_selection
|
||||||
|
.char_indices()
|
||||||
|
.skip_while(|(_, c)| c.is_whitespace());
|
||||||
|
chars.next().unwrap().0
|
||||||
|
..chars.nth(2).map_or(initial_selection.len(), |(idx, _)| idx)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Shorten selection to month.
|
||||||
|
selection.start += month.start;
|
||||||
|
selection.end = selection.start + month.len();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(
|
||||||
|
writer,
|
||||||
|
"{}",
|
||||||
|
" ".repeat(UnicodeWidthStr::width(&line[..selection.start]))
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// TODO: Once our minimum supported rust version is at least 1.47, use selection.is_empty() instead.
|
||||||
|
#[allow(clippy::len_zero)]
|
||||||
|
{
|
||||||
|
if selection.len() == 0 {
|
||||||
|
writeln!(writer, "^ no match for key")?;
|
||||||
|
} else {
|
||||||
|
writeln!(
|
||||||
|
writer,
|
||||||
|
"{}",
|
||||||
|
"_".repeat(UnicodeWidthStr::width(&line[selection]))
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !(settings.random
|
||||||
|
|| settings.stable
|
||||||
|
|| settings.unique
|
||||||
|
|| !(settings.dictionary_order
|
||||||
|
|| settings.ignore_blanks
|
||||||
|
|| settings.ignore_case
|
||||||
|
|| settings.ignore_non_printing
|
||||||
|
|| settings.mode != SortMode::Default))
|
||||||
|
{
|
||||||
|
// A last resort comparator is in use, underline the whole line.
|
||||||
|
if self.line.is_empty() {
|
||||||
|
writeln!(writer, "^ no match for key")?;
|
||||||
|
} else {
|
||||||
|
writeln!(
|
||||||
|
writer,
|
||||||
|
"{}",
|
||||||
|
"_".repeat(UnicodeWidthStr::width(line.as_str()))
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transform this line. Returns None if there's no need to transform.
|
/// Transform this line. Returns None if there's no need to transform.
|
||||||
|
@ -407,20 +529,18 @@ fn tokenize_default(line: &str) -> Vec<Field> {
|
||||||
|
|
||||||
/// Split between separators. These separators are not included in fields.
|
/// Split between separators. These separators are not included in fields.
|
||||||
fn tokenize_with_separator(line: &str, separator: char) -> Vec<Field> {
|
fn tokenize_with_separator(line: &str, separator: char) -> Vec<Field> {
|
||||||
let mut tokens = vec![0..0];
|
let mut tokens = vec![];
|
||||||
let mut previous_was_separator = false;
|
let separator_indices =
|
||||||
for (idx, char) in line.char_indices() {
|
line.char_indices()
|
||||||
if previous_was_separator {
|
.filter_map(|(i, c)| if c == separator { Some(i) } else { None });
|
||||||
tokens.push(idx..0);
|
let mut start = 0;
|
||||||
|
for sep_idx in separator_indices {
|
||||||
|
tokens.push(start..sep_idx);
|
||||||
|
start = sep_idx + 1;
|
||||||
}
|
}
|
||||||
if char == separator {
|
if start < line.len() {
|
||||||
tokens.last_mut().unwrap().end = idx;
|
tokens.push(start..line.len());
|
||||||
previous_was_separator = true;
|
|
||||||
} else {
|
|
||||||
previous_was_separator = false;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
tokens.last_mut().unwrap().end = line.len();
|
|
||||||
tokens
|
tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -464,6 +584,28 @@ impl KeyPosition {
|
||||||
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.
|
||||||
|
// Instad of reporting an error, let them overwrite each other.
|
||||||
|
|
||||||
|
// FIXME: This should only override if the overridden flag is a global flag.
|
||||||
|
// If conflicting flags are attached to the key, GNU sort crashes and we should probably too.
|
||||||
|
match option {
|
||||||
|
'h' | 'n' | 'g' | 'M' => {
|
||||||
|
settings.dictionary_order = false;
|
||||||
|
settings.ignore_non_printing = false;
|
||||||
|
}
|
||||||
|
'd' | 'i' => {
|
||||||
|
settings.mode = match settings.mode {
|
||||||
|
SortMode::Numeric
|
||||||
|
| SortMode::HumanNumeric
|
||||||
|
| SortMode::GeneralNumeric
|
||||||
|
| SortMode::Month => SortMode::Default,
|
||||||
|
// Only SortMode::Default and SortMode::Version work with dictionary_order and ignore_non_printing
|
||||||
|
m @ SortMode::Default | m @ SortMode::Version => m,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Strip away option characters from the original value so we can parse it later
|
// Strip away option characters from the original value so we can parse it later
|
||||||
*value_with_options = &value_with_options[..options_start];
|
*value_with_options = &value_with_options[..options_start];
|
||||||
|
@ -506,13 +648,16 @@ impl FieldSelector {
|
||||||
|
|
||||||
/// Look up the slice that corresponds to this selector for the given line.
|
/// Look up the slice that corresponds to this selector for the given line.
|
||||||
/// If needs_fields returned false, fields may be None.
|
/// If needs_fields returned false, fields may be None.
|
||||||
fn get_field_selection<'a>(
|
fn get_selection<'a>(&self, line: &'a str, tokens: Option<&[Field]>) -> Range<usize> {
|
||||||
&self,
|
enum Resolution {
|
||||||
line: &'a str,
|
// The start index of the resolved character, inclusive
|
||||||
tokens: Option<&[Field]>,
|
StartOfChar(usize),
|
||||||
) -> Option<RangeInclusive<usize>> {
|
// The end index of the resolved character, exclusive.
|
||||||
enum ResolutionErr {
|
// This is only returned if the character index is 0.
|
||||||
|
EndOfChar(usize),
|
||||||
|
// The resolved character would be in front of the first character
|
||||||
TooLow,
|
TooLow,
|
||||||
|
// The resolved character would be after the last character
|
||||||
TooHigh,
|
TooHigh,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -521,15 +666,15 @@ impl FieldSelector {
|
||||||
line: &str,
|
line: &str,
|
||||||
tokens: Option<&[Field]>,
|
tokens: Option<&[Field]>,
|
||||||
position: &KeyPosition,
|
position: &KeyPosition,
|
||||||
) -> Result<usize, ResolutionErr> {
|
) -> Resolution {
|
||||||
if tokens.map_or(false, |fields| fields.len() < position.field) {
|
if tokens.map_or(false, |fields| fields.len() < position.field) {
|
||||||
Err(ResolutionErr::TooHigh)
|
Resolution::TooHigh
|
||||||
} else if position.char == 0 {
|
} else if position.char == 0 {
|
||||||
let end = tokens.unwrap()[position.field - 1].end;
|
let end = tokens.unwrap()[position.field - 1].end;
|
||||||
if end == 0 {
|
if end == 0 {
|
||||||
Err(ResolutionErr::TooLow)
|
Resolution::TooLow
|
||||||
} else {
|
} else {
|
||||||
Ok(end - 1)
|
Resolution::EndOfChar(end)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut idx = if position.field == 1 {
|
let mut idx = if position.field == 1 {
|
||||||
|
@ -538,38 +683,52 @@ impl FieldSelector {
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
tokens.unwrap()[position.field - 1].start
|
tokens.unwrap()[position.field - 1].start
|
||||||
} + position.char
|
};
|
||||||
- 1;
|
idx += line[idx..]
|
||||||
|
.char_indices()
|
||||||
|
.nth(position.char - 1)
|
||||||
|
.map_or(line.len(), |(idx, _)| idx);
|
||||||
if idx >= line.len() {
|
if idx >= line.len() {
|
||||||
Err(ResolutionErr::TooHigh)
|
Resolution::TooHigh
|
||||||
} else {
|
} else {
|
||||||
if position.ignore_blanks {
|
if position.ignore_blanks {
|
||||||
if let Some(not_whitespace) =
|
if let Some((not_whitespace, _)) =
|
||||||
line[idx..].chars().position(|c| !c.is_whitespace())
|
line[idx..].char_indices().find(|(_, c)| !c.is_whitespace())
|
||||||
{
|
{
|
||||||
idx += not_whitespace;
|
idx += not_whitespace;
|
||||||
} else {
|
} else {
|
||||||
return Err(ResolutionErr::TooHigh);
|
return Resolution::TooHigh;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(idx)
|
Resolution::StartOfChar(idx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(from) = resolve_index(line, tokens, &self.from) {
|
match resolve_index(line, tokens, &self.from) {
|
||||||
|
Resolution::StartOfChar(from) => {
|
||||||
let to = self.to.as_ref().map(|to| resolve_index(line, tokens, &to));
|
let to = self.to.as_ref().map(|to| resolve_index(line, tokens, &to));
|
||||||
|
|
||||||
match to {
|
match to {
|
||||||
Some(Ok(to)) => Some(from..=to),
|
Some(Resolution::StartOfChar(mut to)) => {
|
||||||
|
to += line[to..].chars().next().map_or(1, |c| c.len_utf8());
|
||||||
|
from..to
|
||||||
|
}
|
||||||
|
Some(Resolution::EndOfChar(to)) => from..to,
|
||||||
// If `to` was not given or the match would be after the end of the line,
|
// If `to` was not given or the match would be after the end of the line,
|
||||||
// match everything until the end of the line.
|
// match everything until the end of the line.
|
||||||
None | Some(Err(ResolutionErr::TooHigh)) => Some(from..=line.len() - 1),
|
None | Some(Resolution::TooHigh) => from..line.len(),
|
||||||
// If `to` is before the start of the line, report no match.
|
// If `to` is before the start of the line, report no match.
|
||||||
// This can happen if the line starts with a separator.
|
// This can happen if the line starts with a separator.
|
||||||
Some(Err(ResolutionErr::TooLow)) => None,
|
Some(Resolution::TooLow) => 0..0,
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
None
|
Resolution::TooLow | Resolution::EndOfChar(_) => {
|
||||||
|
unreachable!("This should only happen if the field start index is 0, but that should already have caused an error.")
|
||||||
|
}
|
||||||
|
// While for comparisons it's only important that this is an empty slice,
|
||||||
|
// to produce accurate debug output we need to match an empty slice at the end of the line.
|
||||||
|
Resolution::TooHigh => line.len()..line.len(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -597,7 +756,7 @@ impl<'a> PartialOrd for MergeableFile<'a> {
|
||||||
|
|
||||||
impl<'a> PartialEq for MergeableFile<'a> {
|
impl<'a> PartialEq for MergeableFile<'a> {
|
||||||
fn eq(&self, other: &MergeableFile) -> bool {
|
fn eq(&self, other: &MergeableFile) -> bool {
|
||||||
Ordering::Equal == compare_by(&self.current_line, &other.current_line, self.settings)
|
Ordering::Equal == self.cmp(other)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -628,8 +787,8 @@ impl<'a> FileMerger<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Iterator for FileMerger<'a> {
|
impl<'a> Iterator for FileMerger<'a> {
|
||||||
type Item = String;
|
type Item = Line;
|
||||||
fn next(&mut self) -> Option<String> {
|
fn next(&mut self) -> Option<Line> {
|
||||||
match self.heap.pop() {
|
match self.heap.pop() {
|
||||||
Some(mut current) => {
|
Some(mut current) => {
|
||||||
match current.lines.next() {
|
match current.lines.next() {
|
||||||
|
@ -639,12 +798,12 @@ impl<'a> Iterator for FileMerger<'a> {
|
||||||
Line::new(next_line, &self.settings),
|
Line::new(next_line, &self.settings),
|
||||||
);
|
);
|
||||||
self.heap.push(current);
|
self.heap.push(current);
|
||||||
Some(ret.line)
|
Some(ret)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Don't put it back in the heap (it's empty/erroring)
|
// Don't put it back in the heap (it's empty/erroring)
|
||||||
// but its first line is still valid.
|
// but its first line is still valid.
|
||||||
Some(current.current_line.line)
|
Some(current.current_line)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -708,7 +867,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
Arg::with_name(OPT_DICTIONARY_ORDER)
|
Arg::with_name(OPT_DICTIONARY_ORDER)
|
||||||
.short("d")
|
.short("d")
|
||||||
.long(OPT_DICTIONARY_ORDER)
|
.long(OPT_DICTIONARY_ORDER)
|
||||||
.help("consider only blanks and alphanumeric characters"),
|
.help("consider only blanks and alphanumeric characters")
|
||||||
|
.conflicts_with_all(&[OPT_NUMERIC_SORT, OPT_GENERAL_NUMERIC_SORT, OPT_HUMAN_NUMERIC_SORT, OPT_MONTH_SORT]),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_MERGE)
|
Arg::with_name(OPT_MERGE)
|
||||||
|
@ -736,9 +896,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_IGNORE_NONPRINTING)
|
Arg::with_name(OPT_IGNORE_NONPRINTING)
|
||||||
.short("-i")
|
.short("i")
|
||||||
.long(OPT_IGNORE_NONPRINTING)
|
.long(OPT_IGNORE_NONPRINTING)
|
||||||
.help("ignore nonprinting characters"),
|
.help("ignore nonprinting characters")
|
||||||
|
.conflicts_with_all(&[OPT_NUMERIC_SORT, OPT_GENERAL_NUMERIC_SORT, OPT_HUMAN_NUMERIC_SORT, OPT_MONTH_SORT]),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_IGNORE_BLANKS)
|
Arg::with_name(OPT_IGNORE_BLANKS)
|
||||||
|
@ -829,9 +990,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
.value_name("NUL_FILES")
|
.value_name("NUL_FILES")
|
||||||
.multiple(true),
|
.multiple(true),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name(OPT_DEBUG)
|
||||||
|
.long(OPT_DEBUG)
|
||||||
|
.help("underline the parts of the line that are actually used for sorting"),
|
||||||
|
)
|
||||||
.arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true))
|
.arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true))
|
||||||
.get_matches_from(args);
|
.get_matches_from(args);
|
||||||
|
|
||||||
|
settings.debug = matches.is_present(OPT_DEBUG);
|
||||||
|
|
||||||
// check whether user specified a zero terminated list of files for input, otherwise read files from args
|
// check whether user specified a zero terminated list of files for input, otherwise read files from args
|
||||||
let mut files: Vec<String> = if matches.is_present(OPT_FILES0_FROM) {
|
let mut files: Vec<String> = if matches.is_present(OPT_FILES0_FROM) {
|
||||||
let files0_from: Vec<String> = matches
|
let files0_from: Vec<String> = matches
|
||||||
|
@ -964,6 +1132,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
1,
|
1,
|
||||||
&mut key_settings,
|
&mut key_settings,
|
||||||
);
|
);
|
||||||
|
if from.char == 0 {
|
||||||
|
crash!(
|
||||||
|
1,
|
||||||
|
"invalid character index 0 in `{}` for the start position of a field",
|
||||||
|
key
|
||||||
|
)
|
||||||
|
}
|
||||||
let to = from_to
|
let to = from_to
|
||||||
.next()
|
.next()
|
||||||
.map(|to| KeyPosition::parse(to, 0, &mut key_settings));
|
.map(|to| KeyPosition::parse(to, 0, &mut key_settings));
|
||||||
|
@ -1042,7 +1217,10 @@ fn exec(files: Vec<String>, settings: GlobalSettings) -> i32 {
|
||||||
|
|
||||||
if settings.merge {
|
if settings.merge {
|
||||||
if settings.unique {
|
if settings.unique {
|
||||||
print_sorted(file_merger.dedup(), &settings)
|
print_sorted(
|
||||||
|
file_merger.dedup_by(|a, b| compare_by(a, b, settings) == Ordering::Equal),
|
||||||
|
&settings,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
print_sorted(file_merger, &settings)
|
print_sorted(file_merger, &settings)
|
||||||
}
|
}
|
||||||
|
@ -1050,12 +1228,11 @@ fn exec(files: Vec<String>, settings: GlobalSettings) -> i32 {
|
||||||
print_sorted(
|
print_sorted(
|
||||||
lines
|
lines
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.dedup_by(|a, b| compare_by(a, b, &settings) == Ordering::Equal)
|
.dedup_by(|a, b| compare_by(a, b, settings) == Ordering::Equal),
|
||||||
.map(|line| line.line),
|
|
||||||
&settings,
|
&settings,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
print_sorted(lines.into_iter().map(|line| line.line), &settings)
|
print_sorted(lines.into_iter(), &settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
0
|
0
|
||||||
|
@ -1163,107 +1340,80 @@ fn default_compare(a: &str, b: &str) -> Ordering {
|
||||||
a.cmp(b)
|
a.cmp(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This function does the initial detection of numeric lines for FP compares.
|
// This function cleans up the initial comparison done by leading_num_common for a general numeric compare.
|
||||||
// Lines starting with a number or positive or negative sign.
|
|
||||||
// It also strips the string of any thing that could never
|
|
||||||
// be a number for the purposes of any type of numeric comparison.
|
|
||||||
#[inline(always)]
|
|
||||||
fn leading_num_common(a: &str) -> &str {
|
|
||||||
let mut s = "";
|
|
||||||
|
|
||||||
// check whether char is numeric, whitespace or decimal point or thousand separator
|
|
||||||
for (idx, c) in a.char_indices() {
|
|
||||||
if !c.is_numeric()
|
|
||||||
&& !c.is_whitespace()
|
|
||||||
&& !c.eq(&THOUSANDS_SEP)
|
|
||||||
&& !c.eq(&DECIMAL_PT)
|
|
||||||
// check for e notation
|
|
||||||
&& !c.eq(&'e')
|
|
||||||
&& !c.eq(&'E')
|
|
||||||
// check whether first char is + or -
|
|
||||||
&& !a.chars().next().unwrap_or('\0').eq(&POSITIVE)
|
|
||||||
&& !a.chars().next().unwrap_or('\0').eq(&NEGATIVE)
|
|
||||||
{
|
|
||||||
// Strip string of non-numeric trailing chars
|
|
||||||
s = &a[..idx];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// If line is not a number line, return the line as is
|
|
||||||
s = &a;
|
|
||||||
}
|
|
||||||
s
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This function cleans up the initial comparison done by leading_num_common for a general numeric compare.
|
|
||||||
// In contrast to numeric compare, GNU general numeric/FP sort *should* recognize positive signs and
|
// In contrast to numeric compare, GNU general numeric/FP sort *should* recognize positive signs and
|
||||||
// scientific notation, so we strip those lines only after the end of the following numeric string.
|
// scientific notation, so we strip those lines only after the end of the following numeric string.
|
||||||
// For example, 5e10KFD would be 5e10 or 5x10^10 and +10000HFKJFK would become 10000.
|
// For example, 5e10KFD would be 5e10 or 5x10^10 and +10000HFKJFK would become 10000.
|
||||||
fn get_leading_gen(a: &str) -> &str {
|
fn get_leading_gen(input: &str) -> Range<usize> {
|
||||||
|
let trimmed = input.trim_start();
|
||||||
|
let leading_whitespace_len = input.len() - trimmed.len();
|
||||||
|
for allowed_prefix in &["inf", "-inf", "nan"] {
|
||||||
|
if trimmed.is_char_boundary(allowed_prefix.len())
|
||||||
|
&& trimmed[..allowed_prefix.len()].eq_ignore_ascii_case(allowed_prefix)
|
||||||
|
{
|
||||||
|
return leading_whitespace_len..(leading_whitespace_len + allowed_prefix.len());
|
||||||
|
}
|
||||||
|
}
|
||||||
// Make this iter peekable to see if next char is numeric
|
// Make this iter peekable to see if next char is numeric
|
||||||
let raw_leading_num = leading_num_common(a);
|
let mut char_indices = trimmed.char_indices().peekable();
|
||||||
let mut p_iter = raw_leading_num.chars().peekable();
|
|
||||||
let mut result = "";
|
let first = char_indices.peek();
|
||||||
// Cleanup raw stripped strings
|
|
||||||
while let Some(c) = p_iter.next() {
|
if first.map_or(false, |&(_, c)| c == NEGATIVE || c == POSITIVE) {
|
||||||
let next_char_numeric = p_iter.peek().unwrap_or(&'\0').is_numeric();
|
char_indices.next();
|
||||||
// Only general numeric recognizes e notation and, see block below, the '+' sign
|
|
||||||
// Only GNU (non-general) numeric recognize thousands seperators, takes only leading #
|
|
||||||
if (c.eq(&'e') || c.eq(&'E')) && !next_char_numeric || c.eq(&THOUSANDS_SEP) {
|
|
||||||
result = a.split(c).next().unwrap_or("");
|
|
||||||
break;
|
|
||||||
// If positive sign and next char is not numeric, split at postive sign at keep trailing numbers
|
|
||||||
// There is a more elegant way to do this in Rust 1.45, std::str::strip_prefix
|
|
||||||
} else if c.eq(&POSITIVE) && !next_char_numeric {
|
|
||||||
result = a.trim().trim_start_matches('+');
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
// If no further processing needed to be done, return the line as-is to be sorted
|
|
||||||
result = a;
|
let mut had_e_notation = false;
|
||||||
|
let mut had_decimal_pt = false;
|
||||||
|
while let Some((idx, c)) = char_indices.next() {
|
||||||
|
if c.is_ascii_digit() {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
result
|
if c == DECIMAL_PT && !had_decimal_pt {
|
||||||
|
had_decimal_pt = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let next_char_numeric = char_indices
|
||||||
|
.peek()
|
||||||
|
.map_or(false, |(_, c)| c.is_ascii_digit());
|
||||||
|
if (c == 'e' || c == 'E') && !had_e_notation && next_char_numeric {
|
||||||
|
had_e_notation = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return leading_whitespace_len..(leading_whitespace_len + idx);
|
||||||
|
}
|
||||||
|
leading_whitespace_len..input.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[derive(Copy, Clone, PartialEq, PartialOrd)]
|
||||||
fn remove_trailing_dec<'a, S: Into<Cow<'a, str>>>(input: S) -> Cow<'a, str> {
|
enum GeneralF64ParseResult {
|
||||||
let input = input.into();
|
Invalid,
|
||||||
if let Some(s) = input.find(DECIMAL_PT) {
|
NaN,
|
||||||
let (leading, trailing) = input.split_at(s);
|
NegInfinity,
|
||||||
let output = [leading, ".", trailing.replace(DECIMAL_PT, "").as_str()].concat();
|
Number(f64),
|
||||||
Cow::Owned(output)
|
Infinity,
|
||||||
} else {
|
|
||||||
input
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse the beginning string into an f64, returning -inf instead of NaN on errors.
|
/// Parse the beginning string into an f64, returning -inf instead of NaN on errors.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn permissive_f64_parse(a: &str) -> f64 {
|
fn general_f64_parse(a: &str) -> GeneralF64ParseResult {
|
||||||
// GNU sort treats "NaN" as non-number in numeric, so it needs special care.
|
// The actual behavior here relies on Rust's implementation of parsing floating points.
|
||||||
// *Keep this trim before parse* despite what POSIX may say about -b and -n
|
// For example "nan", "inf" (ignoring the case) and "infinity" are only parsed to floats starting from 1.53.
|
||||||
// because GNU and BSD both seem to require it to match their behavior
|
// TODO: Once our minimum supported Rust version is 1.53 or above, we should add tests for those cases.
|
||||||
//
|
match a.parse::<f64>() {
|
||||||
// Remove any trailing decimals, ie 4568..890... becomes 4568.890
|
Ok(a) if a.is_nan() => GeneralF64ParseResult::NaN,
|
||||||
// Then, we trim whitespace and parse
|
Ok(a) if a == std::f64::NEG_INFINITY => GeneralF64ParseResult::NegInfinity,
|
||||||
match remove_trailing_dec(a).trim().parse::<f64>() {
|
Ok(a) if a == std::f64::INFINITY => GeneralF64ParseResult::Infinity,
|
||||||
Ok(val) if val.is_nan() => std::f64::NEG_INFINITY,
|
Ok(a) => GeneralF64ParseResult::Number(a),
|
||||||
Ok(val) => val,
|
Err(_) => GeneralF64ParseResult::Invalid,
|
||||||
Err(_) => std::f64::NEG_INFINITY,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compares two floats, with errors and non-numerics assumed to be -inf.
|
/// Compares two floats, with errors and non-numerics assumed to be -inf.
|
||||||
/// Stops coercing at the first non-numeric char.
|
/// Stops coercing at the first non-numeric char.
|
||||||
/// We explicitly need to convert to f64 in this case.
|
/// We explicitly need to convert to f64 in this case.
|
||||||
fn general_numeric_compare(a: f64, b: f64) -> Ordering {
|
fn general_numeric_compare(a: GeneralF64ParseResult, b: GeneralF64ParseResult) -> Ordering {
|
||||||
#![allow(clippy::comparison_chain)]
|
a.partial_cmp(&b).unwrap()
|
||||||
// f64::cmp isn't implemented (due to NaN issues); implement directly instead
|
|
||||||
if a > b {
|
|
||||||
Ordering::Greater
|
|
||||||
} else if a < b {
|
|
||||||
Ordering::Less
|
|
||||||
} else {
|
|
||||||
Ordering::Equal
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_rand_string() -> String {
|
fn get_rand_string() -> String {
|
||||||
|
@ -1290,7 +1440,7 @@ fn random_shuffle(a: &str, b: &str, x: String) -> Ordering {
|
||||||
da.cmp(&db)
|
da.cmp(&db)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Eq, Ord, PartialEq, PartialOrd)]
|
#[derive(Eq, Ord, PartialEq, PartialOrd, Clone, Copy)]
|
||||||
enum Month {
|
enum Month {
|
||||||
Unknown,
|
Unknown,
|
||||||
January,
|
January,
|
||||||
|
@ -1309,30 +1459,32 @@ enum Month {
|
||||||
|
|
||||||
/// Parse the beginning string into a Month, returning Month::Unknown on errors.
|
/// Parse the beginning string into a Month, returning Month::Unknown on errors.
|
||||||
fn month_parse(line: &str) -> Month {
|
fn month_parse(line: &str) -> Month {
|
||||||
// GNU splits at any 3 letter match "JUNNNN" is JUN
|
let line = line.trim();
|
||||||
let pattern = if line.trim().len().ge(&3) {
|
|
||||||
// Split a 3 and get first element of tuple ".0"
|
|
||||||
line.trim().split_at(3).0
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = match pattern.to_uppercase().as_ref() {
|
const MONTHS: [(&str, Month); 12] = [
|
||||||
"JAN" => Month::January,
|
("JAN", Month::January),
|
||||||
"FEB" => Month::February,
|
("FEB", Month::February),
|
||||||
"MAR" => Month::March,
|
("MAR", Month::March),
|
||||||
"APR" => Month::April,
|
("APR", Month::April),
|
||||||
"MAY" => Month::May,
|
("MAY", Month::May),
|
||||||
"JUN" => Month::June,
|
("JUN", Month::June),
|
||||||
"JUL" => Month::July,
|
("JUL", Month::July),
|
||||||
"AUG" => Month::August,
|
("AUG", Month::August),
|
||||||
"SEP" => Month::September,
|
("SEP", Month::September),
|
||||||
"OCT" => Month::October,
|
("OCT", Month::October),
|
||||||
"NOV" => Month::November,
|
("NOV", Month::November),
|
||||||
"DEC" => Month::December,
|
("DEC", Month::December),
|
||||||
_ => Month::Unknown,
|
];
|
||||||
};
|
|
||||||
result
|
for (month_str, month) in &MONTHS {
|
||||||
|
if line.is_char_boundary(month_str.len())
|
||||||
|
&& line[..month_str.len()].eq_ignore_ascii_case(month_str)
|
||||||
|
{
|
||||||
|
return *month;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Month::Unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
fn month_compare(a: &str, b: &str) -> Ordering {
|
fn month_compare(a: &str, b: &str) -> Ordering {
|
||||||
|
@ -1390,7 +1542,7 @@ fn remove_nonprinting_chars(s: &str) -> String {
|
||||||
.collect::<String>()
|
.collect::<String>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_sorted<T: Iterator<Item = String>>(iter: T, settings: &GlobalSettings) {
|
fn print_sorted<T: Iterator<Item = Line>>(iter: T, settings: &GlobalSettings) {
|
||||||
let mut file: Box<dyn Write> = match settings.outfile {
|
let mut file: Box<dyn Write> = match settings.outfile {
|
||||||
Some(ref filename) => match File::create(Path::new(&filename)) {
|
Some(ref filename) => match File::create(Path::new(&filename)) {
|
||||||
Ok(f) => Box::new(BufWriter::new(f)) as Box<dyn Write>,
|
Ok(f) => Box::new(BufWriter::new(f)) as Box<dyn Write>,
|
||||||
|
@ -1401,15 +1553,19 @@ fn print_sorted<T: Iterator<Item = String>>(iter: T, settings: &GlobalSettings)
|
||||||
},
|
},
|
||||||
None => Box::new(BufWriter::new(stdout())) as Box<dyn Write>,
|
None => Box::new(BufWriter::new(stdout())) as Box<dyn Write>,
|
||||||
};
|
};
|
||||||
if settings.zero_terminated {
|
if settings.zero_terminated && !settings.debug {
|
||||||
for line in iter {
|
for line in iter {
|
||||||
crash_if_err!(1, file.write_all(line.as_bytes()));
|
crash_if_err!(1, file.write_all(line.line.as_bytes()));
|
||||||
crash_if_err!(1, file.write_all("\0".as_bytes()));
|
crash_if_err!(1, file.write_all("\0".as_bytes()));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for line in iter {
|
for line in iter {
|
||||||
crash_if_err!(1, file.write_all(line.as_bytes()));
|
if !settings.debug {
|
||||||
|
crash_if_err!(1, file.write_all(line.line.as_bytes()));
|
||||||
crash_if_err!(1, file.write_all("\n".as_bytes()));
|
crash_if_err!(1, file.write_all("\n".as_bytes()));
|
||||||
|
} else {
|
||||||
|
crash_if_err!(1, line.print_debug(settings, &mut file));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
crash_if_err!(1, file.flush());
|
crash_if_err!(1, file.flush());
|
||||||
|
@ -1504,4 +1660,14 @@ mod tests {
|
||||||
vec![0..0, 1..1, 2..2, 3..9, 10..18,]
|
vec![0..0, 1..1, 2..2, 3..9, 10..18,]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tokenize_fields_trailing_custom_separator() {
|
||||||
|
let line = "a";
|
||||||
|
assert_eq!(tokenize(line, Some('a')), vec![0..0]);
|
||||||
|
let line = "aa";
|
||||||
|
assert_eq!(tokenize(line, Some('a')), vec![0..0, 1..1]);
|
||||||
|
let line = "..a..a";
|
||||||
|
assert_eq!(tokenize(line, Some('a')), vec![0..2, 3..5]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,10 +91,15 @@ fn tac(filenames: Vec<String>, before: bool, _: bool, separator: &str) -> i32 {
|
||||||
} else {
|
} else {
|
||||||
let path = Path::new(filename);
|
let path = Path::new(filename);
|
||||||
if path.is_dir() || path.metadata().is_err() {
|
if path.is_dir() || path.metadata().is_err() {
|
||||||
|
if path.is_dir() {
|
||||||
|
show_error!("dir: read error: Invalid argument");
|
||||||
|
} else {
|
||||||
show_error!(
|
show_error!(
|
||||||
"failed to open '{}' for reading: No such file or directory",
|
"failed to open '{}' for reading: No such file or directory",
|
||||||
filename
|
filename
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
exit_code = 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
match File::open(path) {
|
match File::open(path) {
|
||||||
|
|
|
@ -117,6 +117,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(options::SLEEP_INT)
|
Arg::with_name(options::SLEEP_INT)
|
||||||
.short("s")
|
.short("s")
|
||||||
|
.takes_value(true)
|
||||||
.long(options::SLEEP_INT)
|
.long(options::SLEEP_INT)
|
||||||
.help("Number or seconds to sleep between polling the file when running with -f"),
|
.help("Number or seconds to sleep between polling the file when running with -f"),
|
||||||
)
|
)
|
||||||
|
|
|
@ -192,7 +192,8 @@ fn truncate(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_size(size: &str) -> (u64, TruncateMode) {
|
fn parse_size(size: &str) -> (u64, TruncateMode) {
|
||||||
let mode = match size.chars().next().unwrap() {
|
let clean_size = size.replace(" ", "");
|
||||||
|
let mode = match clean_size.chars().next().unwrap() {
|
||||||
'+' => TruncateMode::Extend,
|
'+' => TruncateMode::Extend,
|
||||||
'-' => TruncateMode::Reduce,
|
'-' => TruncateMode::Reduce,
|
||||||
'<' => TruncateMode::AtMost,
|
'<' => TruncateMode::AtMost,
|
||||||
|
@ -203,9 +204,9 @@ fn parse_size(size: &str) -> (u64, TruncateMode) {
|
||||||
};
|
};
|
||||||
let bytes = {
|
let bytes = {
|
||||||
let mut slice = if mode == TruncateMode::Reference {
|
let mut slice = if mode == TruncateMode::Reference {
|
||||||
size
|
&clean_size
|
||||||
} else {
|
} else {
|
||||||
&size[1..]
|
&clean_size[1..]
|
||||||
};
|
};
|
||||||
if slice.chars().last().unwrap().is_alphabetic() {
|
if slice.chars().last().unwrap().is_alphabetic() {
|
||||||
slice = &slice[..slice.len() - 1];
|
slice = &slice[..slice.len() - 1];
|
||||||
|
@ -220,11 +221,11 @@ fn parse_size(size: &str) -> (u64, TruncateMode) {
|
||||||
Ok(num) => num,
|
Ok(num) => num,
|
||||||
Err(e) => crash!(1, "'{}' is not a valid number: {}", size, e),
|
Err(e) => crash!(1, "'{}' is not a valid number: {}", size, e),
|
||||||
};
|
};
|
||||||
if size.chars().last().unwrap().is_alphabetic() {
|
if clean_size.chars().last().unwrap().is_alphabetic() {
|
||||||
number *= match size.chars().last().unwrap().to_ascii_uppercase() {
|
number *= match clean_size.chars().last().unwrap().to_ascii_uppercase() {
|
||||||
'B' => match size
|
'B' => match clean_size
|
||||||
.chars()
|
.chars()
|
||||||
.nth(size.len() - 2)
|
.nth(clean_size.len() - 2)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_ascii_uppercase()
|
.to_ascii_uppercase()
|
||||||
{
|
{
|
||||||
|
|
|
@ -61,34 +61,43 @@ impl Uniq {
|
||||||
reader: &mut BufReader<R>,
|
reader: &mut BufReader<R>,
|
||||||
writer: &mut BufWriter<W>,
|
writer: &mut BufWriter<W>,
|
||||||
) {
|
) {
|
||||||
let mut lines: Vec<String> = vec![];
|
|
||||||
let mut first_line_printed = false;
|
let mut first_line_printed = false;
|
||||||
let delimiters = self.delimiters;
|
let mut group_count = 1;
|
||||||
let line_terminator = self.get_line_terminator();
|
let line_terminator = self.get_line_terminator();
|
||||||
// Don't print any delimiting lines before, after or between groups if delimiting method is 'none'
|
let mut lines = reader.split(line_terminator).map(get_line_string);
|
||||||
let no_delimiters = delimiters == Delimiters::None;
|
let mut line = match lines.next() {
|
||||||
// The 'prepend' and 'both' delimit methods will cause output to start with delimiter line
|
Some(l) => l,
|
||||||
let prepend_delimiter = delimiters == Delimiters::Prepend || delimiters == Delimiters::Both;
|
None => return,
|
||||||
// The 'append' and 'both' delimit methods will cause output to end with delimiter line
|
};
|
||||||
let append_delimiter = delimiters == Delimiters::Append || delimiters == Delimiters::Both;
|
|
||||||
|
|
||||||
for line in reader.split(line_terminator).map(get_line_string) {
|
// compare current `line` with consecutive lines (`next_line`) of the input
|
||||||
if !lines.is_empty() && self.cmp_keys(&lines[0], &line) {
|
// and if needed, print `line` based on the command line options provided
|
||||||
// Print delimiter if delimit method is not 'none' and any line has been output
|
for next_line in lines {
|
||||||
// before or if we need to start output with delimiter
|
if self.cmp_keys(&line, &next_line) {
|
||||||
let print_delimiter = !no_delimiters && (prepend_delimiter || first_line_printed);
|
if (group_count == 1 && !self.repeats_only)
|
||||||
first_line_printed |= self.print_lines(writer, &lines, print_delimiter);
|
|| (group_count > 1 && !self.uniques_only)
|
||||||
lines.truncate(0);
|
{
|
||||||
|
self.print_line(writer, &line, group_count, first_line_printed);
|
||||||
|
first_line_printed = true;
|
||||||
}
|
}
|
||||||
lines.push(line);
|
line = next_line;
|
||||||
|
group_count = 1;
|
||||||
|
} else {
|
||||||
|
if self.all_repeated {
|
||||||
|
self.print_line(writer, &line, group_count, first_line_printed);
|
||||||
|
first_line_printed = true;
|
||||||
|
line = next_line;
|
||||||
}
|
}
|
||||||
if !lines.is_empty() {
|
group_count += 1;
|
||||||
// Print delimiter if delimit method is not 'none' and any line has been output
|
|
||||||
// before or if we need to start output with delimiter
|
|
||||||
let print_delimiter = !no_delimiters && (prepend_delimiter || first_line_printed);
|
|
||||||
first_line_printed |= self.print_lines(writer, &lines, print_delimiter);
|
|
||||||
}
|
}
|
||||||
if append_delimiter && first_line_printed {
|
}
|
||||||
|
if (group_count == 1 && !self.repeats_only) || (group_count > 1 && !self.uniques_only) {
|
||||||
|
self.print_line(writer, &line, group_count, first_line_printed);
|
||||||
|
first_line_printed = true;
|
||||||
|
}
|
||||||
|
if (self.delimiters == Delimiters::Append || self.delimiters == Delimiters::Both)
|
||||||
|
&& first_line_printed
|
||||||
|
{
|
||||||
crash_if_err!(1, writer.write_all(&[line_terminator]));
|
crash_if_err!(1, writer.write_all(&[line_terminator]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -163,27 +172,17 @@ impl Uniq {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_lines<W: Write>(
|
fn should_print_delimiter(&self, group_count: usize, first_line_printed: bool) -> bool {
|
||||||
&self,
|
// if no delimiter option is selected then no other checks needed
|
||||||
writer: &mut BufWriter<W>,
|
self.delimiters != Delimiters::None
|
||||||
lines: &[String],
|
// print delimiter only before the first line of a group, not between lines of a group
|
||||||
print_delimiter: bool,
|
&& group_count == 1
|
||||||
) -> bool {
|
// if at least one line has been output before current group then print delimiter
|
||||||
let mut first_line_printed = false;
|
&& (first_line_printed
|
||||||
let mut count = if self.all_repeated { 1 } else { lines.len() };
|
// or if we need to prepend delimiter then print it even at the start of the output
|
||||||
if lines.len() == 1 && !self.repeats_only || lines.len() > 1 && !self.uniques_only {
|
|| self.delimiters == Delimiters::Prepend
|
||||||
self.print_line(writer, &lines[0], count, print_delimiter);
|
// the 'both' delimit mode should prepend and append delimiters
|
||||||
first_line_printed = true;
|
|| self.delimiters == Delimiters::Both)
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
if self.all_repeated {
|
|
||||||
for line in lines[1..].iter() {
|
|
||||||
self.print_line(writer, line, count, print_delimiter && !first_line_printed);
|
|
||||||
first_line_printed = true;
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
first_line_printed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_line<W: Write>(
|
fn print_line<W: Write>(
|
||||||
|
@ -191,11 +190,11 @@ impl Uniq {
|
||||||
writer: &mut BufWriter<W>,
|
writer: &mut BufWriter<W>,
|
||||||
line: &str,
|
line: &str,
|
||||||
count: usize,
|
count: usize,
|
||||||
print_delimiter: bool,
|
first_line_printed: bool,
|
||||||
) {
|
) {
|
||||||
let line_terminator = self.get_line_terminator();
|
let line_terminator = self.get_line_terminator();
|
||||||
|
|
||||||
if print_delimiter {
|
if self.should_print_delimiter(count, first_line_printed) {
|
||||||
crash_if_err!(1, writer.write_all(&[line_terminator]));
|
crash_if_err!(1, writer.write_all(&[line_terminator]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
|
|
||||||
// spell-checker:ignore (vars) fperm srwx
|
// spell-checker:ignore (vars) fperm srwx
|
||||||
|
|
||||||
|
use libc::{mode_t, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR};
|
||||||
|
|
||||||
pub fn parse_numeric(fperm: u32, mut mode: &str) -> Result<u32, String> {
|
pub fn parse_numeric(fperm: u32, mut mode: &str) -> Result<u32, String> {
|
||||||
let (op, pos) = parse_op(mode, Some('='))?;
|
let (op, pos) = parse_op(mode, Some('='))?;
|
||||||
mode = mode[pos..].trim().trim_start_matches('0');
|
mode = mode[pos..].trim().trim_start_matches('0');
|
||||||
|
@ -129,3 +131,41 @@ fn parse_change(mode: &str, fperm: u32, considering_dir: bool) -> (u32, usize) {
|
||||||
}
|
}
|
||||||
(srwx, pos)
|
(srwx, pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_mode(mode: Option<String>) -> Result<mode_t, String> {
|
||||||
|
let fperm = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
|
||||||
|
if let Some(mode) = mode {
|
||||||
|
let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
||||||
|
let result = if mode.contains(arr) {
|
||||||
|
parse_numeric(fperm as u32, mode.as_str())
|
||||||
|
} else {
|
||||||
|
parse_symbolic(fperm as u32, mode.as_str(), true)
|
||||||
|
};
|
||||||
|
result.map(|mode| mode as mode_t)
|
||||||
|
} else {
|
||||||
|
Ok(fperm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn symbolic_modes() {
|
||||||
|
assert_eq!(super::parse_mode(Some("u+x".to_owned())).unwrap(), 0o766);
|
||||||
|
assert_eq!(
|
||||||
|
super::parse_mode(Some("+x".to_owned())).unwrap(),
|
||||||
|
if !crate::os::is_wsl_1() { 0o777 } else { 0o776 }
|
||||||
|
);
|
||||||
|
assert_eq!(super::parse_mode(Some("a-w".to_owned())).unwrap(), 0o444);
|
||||||
|
assert_eq!(super::parse_mode(Some("g-r".to_owned())).unwrap(), 0o626);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn numeric_modes() {
|
||||||
|
assert_eq!(super::parse_mode(Some("644".to_owned())).unwrap(), 0o644);
|
||||||
|
assert_eq!(super::parse_mode(Some("+100".to_owned())).unwrap(), 0o766);
|
||||||
|
assert_eq!(super::parse_mode(Some("-4".to_owned())).unwrap(), 0o662);
|
||||||
|
assert_eq!(super::parse_mode(None).unwrap(), 0o666);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ mod mods; // core cross-platform modules
|
||||||
|
|
||||||
// * cross-platform modules
|
// * cross-platform modules
|
||||||
pub use crate::mods::coreopts;
|
pub use crate::mods::coreopts;
|
||||||
|
pub use crate::mods::os;
|
||||||
pub use crate::mods::panic;
|
pub use crate::mods::panic;
|
||||||
pub use crate::mods::ranges;
|
pub use crate::mods::ranges;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// mods ~ cross-platforms modules (core/bundler file)
|
// mods ~ cross-platforms modules (core/bundler file)
|
||||||
|
|
||||||
pub mod coreopts;
|
pub mod coreopts;
|
||||||
|
pub mod os;
|
||||||
pub mod panic;
|
pub mod panic;
|
||||||
pub mod ranges;
|
pub mod ranges;
|
||||||
|
|
30
src/uucore/src/lib/mods/os.rs
Normal file
30
src/uucore/src/lib/mods/os.rs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
/// Test if the program is running under WSL
|
||||||
|
// ref: <https://github.com/microsoft/WSL/issues/4555> @@ <https://archive.is/dP0bz>
|
||||||
|
pub fn is_wsl_1() -> bool {
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
if is_wsl_2() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if let Ok(b) = std::fs::read("/proc/sys/kernel/osrelease") {
|
||||||
|
if let Ok(s) = std::str::from_utf8(&b) {
|
||||||
|
let a = s.to_ascii_lowercase();
|
||||||
|
return a.contains("microsoft") || a.contains("wsl");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_wsl_2() -> bool {
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
if let Ok(b) = std::fs::read("/proc/sys/kernel/osrelease") {
|
||||||
|
if let Ok(s) = std::str::from_utf8(&b) {
|
||||||
|
let a = s.to_ascii_lowercase();
|
||||||
|
return a.contains("wsl2");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
|
@ -1,4 +1,7 @@
|
||||||
use crate::common::util::*;
|
use crate::common::util::*;
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::fs::OpenOptions;
|
||||||
|
#[cfg(unix)]
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -26,7 +29,7 @@ fn test_no_options() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(unix)]
|
#[cfg(any(target_vendor = "apple", target_os = "linux", target_os = "android"))]
|
||||||
fn test_no_options_big_input() {
|
fn test_no_options_big_input() {
|
||||||
for &n in &[
|
for &n in &[
|
||||||
0,
|
0,
|
||||||
|
@ -54,7 +57,6 @@ fn test_no_options_big_input() {
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
fn test_fifo_symlink() {
|
fn test_fifo_symlink() {
|
||||||
use std::fs::OpenOptions;
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
|
@ -85,6 +87,74 @@ fn test_fifo_symlink() {
|
||||||
thread.join().unwrap();
|
thread.join().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn test_piped_to_regular_file() {
|
||||||
|
use std::fs::read_to_string;
|
||||||
|
|
||||||
|
for &append in &[true, false] {
|
||||||
|
let s = TestScenario::new(util_name!());
|
||||||
|
let file_path = s.fixtures.plus("file.txt");
|
||||||
|
|
||||||
|
{
|
||||||
|
let file = OpenOptions::new()
|
||||||
|
.create_new(true)
|
||||||
|
.write(true)
|
||||||
|
.append(append)
|
||||||
|
.open(&file_path)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
s.ucmd()
|
||||||
|
.set_stdout(file)
|
||||||
|
.pipe_in_fixture("alpha.txt")
|
||||||
|
.succeeds();
|
||||||
|
}
|
||||||
|
let contents = read_to_string(&file_path).unwrap();
|
||||||
|
assert_eq!(contents, "abcde\nfghij\nklmno\npqrst\nuvwxyz\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn test_piped_to_dev_null() {
|
||||||
|
for &append in &[true, false] {
|
||||||
|
let s = TestScenario::new(util_name!());
|
||||||
|
{
|
||||||
|
let dev_null = OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.append(append)
|
||||||
|
.open("/dev/null")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
s.ucmd()
|
||||||
|
.set_stdout(dev_null)
|
||||||
|
.pipe_in_fixture("alpha.txt")
|
||||||
|
.succeeds();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))]
|
||||||
|
fn test_piped_to_dev_full() {
|
||||||
|
for &append in &[true, false] {
|
||||||
|
let s = TestScenario::new(util_name!());
|
||||||
|
{
|
||||||
|
let dev_full = OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.append(append)
|
||||||
|
.open("/dev/full")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
s.ucmd()
|
||||||
|
.set_stdout(dev_full)
|
||||||
|
.pipe_in_fixture("alpha.txt")
|
||||||
|
.fails()
|
||||||
|
.stderr_contains(&"No space left on device".to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_directory() {
|
fn test_directory() {
|
||||||
let s = TestScenario::new(util_name!());
|
let s = TestScenario::new(util_name!());
|
||||||
|
@ -327,6 +397,7 @@ fn test_dev_full_show_all() {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
fn test_domain_socket() {
|
fn test_domain_socket() {
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
|
use std::sync::{Arc, Barrier};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use tempdir::TempDir;
|
use tempdir::TempDir;
|
||||||
use unix_socket::UnixListener;
|
use unix_socket::UnixListener;
|
||||||
|
@ -335,17 +406,23 @@ fn test_domain_socket() {
|
||||||
let socket_path = dir.path().join("sock");
|
let socket_path = dir.path().join("sock");
|
||||||
let listener = UnixListener::bind(&socket_path).expect("failed to create socket");
|
let listener = UnixListener::bind(&socket_path).expect("failed to create socket");
|
||||||
|
|
||||||
|
// use a barrier to ensure we don't run cat before the listener is setup
|
||||||
|
let barrier = Arc::new(Barrier::new(2));
|
||||||
|
let barrier2 = Arc::clone(&barrier);
|
||||||
|
|
||||||
let thread = thread::spawn(move || {
|
let thread = thread::spawn(move || {
|
||||||
let mut stream = listener.accept().expect("failed to accept connection").0;
|
let mut stream = listener.accept().expect("failed to accept connection").0;
|
||||||
|
barrier2.wait();
|
||||||
stream
|
stream
|
||||||
.write_all(b"a\tb")
|
.write_all(b"a\tb")
|
||||||
.expect("failed to write test data");
|
.expect("failed to write test data");
|
||||||
});
|
});
|
||||||
|
|
||||||
new_ucmd!()
|
let child = new_ucmd!().args(&[socket_path]).run_no_wait();
|
||||||
.args(&[socket_path])
|
barrier.wait();
|
||||||
.succeeds()
|
let stdout = &child.wait_with_output().unwrap().stdout.clone();
|
||||||
.stdout_only("a\tb");
|
let output = String::from_utf8_lossy(&stdout);
|
||||||
|
assert_eq!("a\tb", output);
|
||||||
|
|
||||||
thread.join().unwrap();
|
thread.join().unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,7 +104,7 @@ fn test_reference() {
|
||||||
// skip for root or MS-WSL
|
// skip for root or MS-WSL
|
||||||
// * MS-WSL is bugged (as of 2019-12-25), allowing non-root accounts su-level privileges for `chgrp`
|
// * MS-WSL is bugged (as of 2019-12-25), allowing non-root accounts su-level privileges for `chgrp`
|
||||||
// * for MS-WSL, succeeds and stdout == 'group of /etc retained as root'
|
// * for MS-WSL, succeeds and stdout == 'group of /etc retained as root'
|
||||||
if !(get_effective_gid() == 0 || is_wsl()) {
|
if !(get_effective_gid() == 0 || uucore::os::is_wsl_1()) {
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.arg("-v")
|
.arg("-v")
|
||||||
.arg("--reference=/etc/passwd")
|
.arg("--reference=/etc/passwd")
|
||||||
|
|
|
@ -47,7 +47,7 @@ fn run_single_test(test: &TestCase, at: AtPath, mut ucmd: UCommand) {
|
||||||
ucmd.arg(arg);
|
ucmd.arg(arg);
|
||||||
}
|
}
|
||||||
let r = ucmd.run();
|
let r = ucmd.run();
|
||||||
if !r.success {
|
if !r.succeeded() {
|
||||||
println!("{}", r.stderr_str());
|
println!("{}", r.stderr_str());
|
||||||
panic!("{:?}: failed", ucmd.raw);
|
panic!("{:?}: failed", ucmd.raw);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,11 @@ use crate::common::util::*;
|
||||||
fn test_missing_operand() {
|
fn test_missing_operand() {
|
||||||
let result = new_ucmd!().run();
|
let result = new_ucmd!().run();
|
||||||
|
|
||||||
assert_eq!(
|
assert!(result
|
||||||
true,
|
.stderr_str()
|
||||||
result
|
.starts_with("error: The following required arguments were not provided"));
|
||||||
.stderr
|
|
||||||
.starts_with("error: The following required arguments were not provided")
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(true, result.stderr.contains("<newroot>"));
|
assert!(result.stderr_str().contains("<newroot>"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -20,14 +17,11 @@ fn test_enter_chroot_fails() {
|
||||||
|
|
||||||
at.mkdir("jail");
|
at.mkdir("jail");
|
||||||
|
|
||||||
let result = ucmd.arg("jail").run();
|
let result = ucmd.arg("jail").fails();
|
||||||
|
|
||||||
assert_eq!(
|
assert!(result
|
||||||
true,
|
.stderr_str()
|
||||||
result.stderr.starts_with(
|
.starts_with("chroot: error: cannot chroot to jail: Operation not permitted (os error 1)"));
|
||||||
"chroot: error: cannot chroot to jail: Operation not permitted (os error 1)"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -47,19 +41,18 @@ fn test_invalid_user_spec() {
|
||||||
|
|
||||||
at.mkdir("a");
|
at.mkdir("a");
|
||||||
|
|
||||||
let result = ucmd.arg("a").arg("--userspec=ARABA:").run();
|
let result = ucmd.arg("a").arg("--userspec=ARABA:").fails();
|
||||||
|
|
||||||
assert_eq!(
|
assert!(result
|
||||||
true,
|
.stderr_str()
|
||||||
result.stderr.starts_with("chroot: error: invalid userspec")
|
.starts_with("chroot: error: invalid userspec"));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_preference_of_userspec() {
|
fn test_preference_of_userspec() {
|
||||||
let scene = TestScenario::new(util_name!());
|
let scene = TestScenario::new(util_name!());
|
||||||
let result = scene.cmd("whoami").run();
|
let result = scene.cmd("whoami").run();
|
||||||
if is_ci() && result.stderr.contains("No such user/group") {
|
if is_ci() && result.stderr_str().contains("No such user/group") {
|
||||||
// In the CI, some server are failing to return whoami.
|
// In the CI, some server are failing to return whoami.
|
||||||
// As seems to be a configuration issue, ignoring it
|
// As seems to be a configuration issue, ignoring it
|
||||||
return;
|
return;
|
||||||
|
@ -73,7 +66,7 @@ fn test_preference_of_userspec() {
|
||||||
println!("result.stdout = {}", result.stdout_str());
|
println!("result.stdout = {}", result.stdout_str());
|
||||||
println!("result.stderr = {}", result.stderr_str());
|
println!("result.stderr = {}", result.stderr_str());
|
||||||
|
|
||||||
if is_ci() && result.stderr.contains("cannot find name for user ID") {
|
if is_ci() && result.stderr_str().contains("cannot find name for user ID") {
|
||||||
// In the CI, some server are failing to return id.
|
// In the CI, some server are failing to return id.
|
||||||
// As seems to be a configuration issue, ignoring it
|
// As seems to be a configuration issue, ignoring it
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -42,13 +42,9 @@ static TEST_MOUNT_OTHER_FILESYSTEM_FILE: &str = "mount/DO_NOT_copy_me.txt";
|
||||||
fn test_cp_cp() {
|
fn test_cp_cp() {
|
||||||
let (at, mut ucmd) = at_and_ucmd!();
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
// Invoke our binary to make the copy.
|
// Invoke our binary to make the copy.
|
||||||
let result = ucmd
|
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
.arg(TEST_HELLO_WORLD_SOURCE)
|
|
||||||
.arg(TEST_HELLO_WORLD_DEST)
|
.arg(TEST_HELLO_WORLD_DEST)
|
||||||
.run();
|
.succeeds();
|
||||||
|
|
||||||
// Check that the exit code represents a successful copy.
|
|
||||||
assert!(result.success);
|
|
||||||
|
|
||||||
// Check the content of the destination file that was copied.
|
// Check the content of the destination file that was copied.
|
||||||
assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n");
|
assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n");
|
||||||
|
@ -57,12 +53,9 @@ fn test_cp_cp() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cp_existing_target() {
|
fn test_cp_existing_target() {
|
||||||
let (at, mut ucmd) = at_and_ucmd!();
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
let result = ucmd
|
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
.arg(TEST_HELLO_WORLD_SOURCE)
|
|
||||||
.arg(TEST_EXISTING_FILE)
|
.arg(TEST_EXISTING_FILE)
|
||||||
.run();
|
.succeeds();
|
||||||
|
|
||||||
assert!(result.success);
|
|
||||||
|
|
||||||
// Check the content of the destination file
|
// Check the content of the destination file
|
||||||
assert_eq!(at.read(TEST_EXISTING_FILE), "Hello, World!\n");
|
assert_eq!(at.read(TEST_EXISTING_FILE), "Hello, World!\n");
|
||||||
|
@ -74,52 +67,41 @@ fn test_cp_existing_target() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cp_duplicate_files() {
|
fn test_cp_duplicate_files() {
|
||||||
let (at, mut ucmd) = at_and_ucmd!();
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
let result = ucmd
|
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
.arg(TEST_HELLO_WORLD_SOURCE)
|
|
||||||
.arg(TEST_HELLO_WORLD_SOURCE)
|
.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
.arg(TEST_COPY_TO_FOLDER)
|
.arg(TEST_COPY_TO_FOLDER)
|
||||||
.run();
|
.succeeds()
|
||||||
|
.stderr_contains("specified more than once");
|
||||||
assert!(result.success);
|
|
||||||
assert!(result.stderr.contains("specified more than once"));
|
|
||||||
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n");
|
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cp_multiple_files_target_is_file() {
|
fn test_cp_multiple_files_target_is_file() {
|
||||||
let (_, mut ucmd) = at_and_ucmd!();
|
new_ucmd!()
|
||||||
let result = ucmd
|
|
||||||
.arg(TEST_HELLO_WORLD_SOURCE)
|
.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
.arg(TEST_HELLO_WORLD_SOURCE)
|
.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
.arg(TEST_EXISTING_FILE)
|
.arg(TEST_EXISTING_FILE)
|
||||||
.run();
|
.fails()
|
||||||
|
.stderr_contains("not a directory");
|
||||||
assert!(!result.success);
|
|
||||||
assert!(result.stderr.contains("not a directory"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cp_directory_not_recursive() {
|
fn test_cp_directory_not_recursive() {
|
||||||
let (_, mut ucmd) = at_and_ucmd!();
|
new_ucmd!()
|
||||||
let result = ucmd
|
|
||||||
.arg(TEST_COPY_TO_FOLDER)
|
.arg(TEST_COPY_TO_FOLDER)
|
||||||
.arg(TEST_HELLO_WORLD_DEST)
|
.arg(TEST_HELLO_WORLD_DEST)
|
||||||
.run();
|
.fails()
|
||||||
|
.stderr_contains("omitting directory");
|
||||||
assert!(!result.success);
|
|
||||||
assert!(result.stderr.contains("omitting directory"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cp_multiple_files() {
|
fn test_cp_multiple_files() {
|
||||||
let (at, mut ucmd) = at_and_ucmd!();
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
let result = ucmd
|
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
.arg(TEST_HELLO_WORLD_SOURCE)
|
|
||||||
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
||||||
.arg(TEST_COPY_TO_FOLDER)
|
.arg(TEST_COPY_TO_FOLDER)
|
||||||
.run();
|
.succeeds();
|
||||||
|
|
||||||
assert!(result.success);
|
|
||||||
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n");
|
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n");
|
||||||
assert_eq!(at.read(TEST_HOW_ARE_YOU_DEST), "How are you?\n");
|
assert_eq!(at.read(TEST_HOW_ARE_YOU_DEST), "How are you?\n");
|
||||||
}
|
}
|
||||||
|
@ -129,14 +111,11 @@ fn test_cp_multiple_files() {
|
||||||
#[cfg(not(macos))]
|
#[cfg(not(macos))]
|
||||||
fn test_cp_recurse() {
|
fn test_cp_recurse() {
|
||||||
let (at, mut ucmd) = at_and_ucmd!();
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
ucmd.arg("-r")
|
||||||
let result = ucmd
|
|
||||||
.arg("-r")
|
|
||||||
.arg(TEST_COPY_FROM_FOLDER)
|
.arg(TEST_COPY_FROM_FOLDER)
|
||||||
.arg(TEST_COPY_TO_FOLDER_NEW)
|
.arg(TEST_COPY_TO_FOLDER_NEW)
|
||||||
.run();
|
.succeeds();
|
||||||
|
|
||||||
assert!(result.success);
|
|
||||||
// Check the content of the destination file that was copied.
|
// Check the content of the destination file that was copied.
|
||||||
assert_eq!(at.read(TEST_COPY_TO_FOLDER_NEW_FILE), "Hello, World!\n");
|
assert_eq!(at.read(TEST_COPY_TO_FOLDER_NEW_FILE), "Hello, World!\n");
|
||||||
}
|
}
|
||||||
|
@ -144,14 +123,10 @@ fn test_cp_recurse() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cp_with_dirs_t() {
|
fn test_cp_with_dirs_t() {
|
||||||
let (at, mut ucmd) = at_and_ucmd!();
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
ucmd.arg("-t")
|
||||||
//using -t option
|
|
||||||
let result_to_dir_t = ucmd
|
|
||||||
.arg("-t")
|
|
||||||
.arg(TEST_COPY_TO_FOLDER)
|
.arg(TEST_COPY_TO_FOLDER)
|
||||||
.arg(TEST_HELLO_WORLD_SOURCE)
|
.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
.run();
|
.succeeds();
|
||||||
assert!(result_to_dir_t.success);
|
|
||||||
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n");
|
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,63 +137,52 @@ fn test_cp_with_dirs() {
|
||||||
let scene = TestScenario::new(util_name!());
|
let scene = TestScenario::new(util_name!());
|
||||||
let at = &scene.fixtures;
|
let at = &scene.fixtures;
|
||||||
|
|
||||||
//using -t option
|
scene
|
||||||
let result_to_dir = scene
|
|
||||||
.ucmd()
|
.ucmd()
|
||||||
.arg(TEST_HELLO_WORLD_SOURCE)
|
.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
.arg(TEST_COPY_TO_FOLDER)
|
.arg(TEST_COPY_TO_FOLDER)
|
||||||
.run();
|
.succeeds();
|
||||||
assert!(result_to_dir.success);
|
|
||||||
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n");
|
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n");
|
||||||
|
|
||||||
let result_from_dir = scene
|
scene
|
||||||
.ucmd()
|
.ucmd()
|
||||||
.arg(TEST_COPY_FROM_FOLDER_FILE)
|
.arg(TEST_COPY_FROM_FOLDER_FILE)
|
||||||
.arg(TEST_HELLO_WORLD_DEST)
|
.arg(TEST_HELLO_WORLD_DEST)
|
||||||
.run();
|
.succeeds();
|
||||||
assert!(result_from_dir.success);
|
|
||||||
assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n");
|
assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cp_arg_target_directory() {
|
fn test_cp_arg_target_directory() {
|
||||||
let (at, mut ucmd) = at_and_ucmd!();
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
let result = ucmd
|
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
.arg(TEST_HELLO_WORLD_SOURCE)
|
|
||||||
.arg("-t")
|
.arg("-t")
|
||||||
.arg(TEST_COPY_TO_FOLDER)
|
.arg(TEST_COPY_TO_FOLDER)
|
||||||
.run();
|
.succeeds();
|
||||||
|
|
||||||
assert!(result.success);
|
|
||||||
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n");
|
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cp_arg_no_target_directory() {
|
fn test_cp_arg_no_target_directory() {
|
||||||
let (_, mut ucmd) = at_and_ucmd!();
|
new_ucmd!()
|
||||||
let result = ucmd
|
|
||||||
.arg(TEST_HELLO_WORLD_SOURCE)
|
.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
.arg("-v")
|
.arg("-v")
|
||||||
.arg("-T")
|
.arg("-T")
|
||||||
.arg(TEST_COPY_TO_FOLDER)
|
.arg(TEST_COPY_TO_FOLDER)
|
||||||
.run();
|
.fails()
|
||||||
|
.stderr_contains("cannot overwrite directory");
|
||||||
assert!(!result.success);
|
|
||||||
assert!(result.stderr.contains("cannot overwrite directory"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cp_arg_interactive() {
|
fn test_cp_arg_interactive() {
|
||||||
let (_, mut ucmd) = at_and_ucmd!();
|
new_ucmd!()
|
||||||
let result = ucmd
|
|
||||||
.arg(TEST_HELLO_WORLD_SOURCE)
|
.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
||||||
.arg("-i")
|
.arg("-i")
|
||||||
.pipe_in("N\n")
|
.pipe_in("N\n")
|
||||||
.run();
|
.succeeds()
|
||||||
|
.stderr_contains("Not overwriting");
|
||||||
assert!(result.success);
|
|
||||||
assert!(result.stderr.contains("Not overwriting"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -227,39 +191,33 @@ fn test_cp_arg_link() {
|
||||||
use std::os::linux::fs::MetadataExt;
|
use std::os::linux::fs::MetadataExt;
|
||||||
|
|
||||||
let (at, mut ucmd) = at_and_ucmd!();
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
let result = ucmd
|
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
.arg(TEST_HELLO_WORLD_SOURCE)
|
|
||||||
.arg("--link")
|
.arg("--link")
|
||||||
.arg(TEST_HELLO_WORLD_DEST)
|
.arg(TEST_HELLO_WORLD_DEST)
|
||||||
.run();
|
.succeeds();
|
||||||
|
|
||||||
assert!(result.success);
|
|
||||||
assert_eq!(at.metadata(TEST_HELLO_WORLD_SOURCE).st_nlink(), 2);
|
assert_eq!(at.metadata(TEST_HELLO_WORLD_SOURCE).st_nlink(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cp_arg_symlink() {
|
fn test_cp_arg_symlink() {
|
||||||
let (at, mut ucmd) = at_and_ucmd!();
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
let result = ucmd
|
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
.arg(TEST_HELLO_WORLD_SOURCE)
|
|
||||||
.arg("--symbolic-link")
|
.arg("--symbolic-link")
|
||||||
.arg(TEST_HELLO_WORLD_DEST)
|
.arg(TEST_HELLO_WORLD_DEST)
|
||||||
.run();
|
.succeeds();
|
||||||
|
|
||||||
assert!(result.success);
|
|
||||||
assert!(at.is_symlink(TEST_HELLO_WORLD_DEST));
|
assert!(at.is_symlink(TEST_HELLO_WORLD_DEST));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cp_arg_no_clobber() {
|
fn test_cp_arg_no_clobber() {
|
||||||
let (at, mut ucmd) = at_and_ucmd!();
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
let result = ucmd
|
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
.arg(TEST_HELLO_WORLD_SOURCE)
|
|
||||||
.arg("--no-clobber")
|
.arg("--no-clobber")
|
||||||
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
||||||
.run();
|
.succeeds();
|
||||||
|
|
||||||
assert!(result.success);
|
|
||||||
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "How are you?\n");
|
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "How are you?\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,34 +225,31 @@ fn test_cp_arg_no_clobber() {
|
||||||
fn test_cp_arg_no_clobber_twice() {
|
fn test_cp_arg_no_clobber_twice() {
|
||||||
let scene = TestScenario::new(util_name!());
|
let scene = TestScenario::new(util_name!());
|
||||||
let at = &scene.fixtures;
|
let at = &scene.fixtures;
|
||||||
|
|
||||||
at.touch("source.txt");
|
at.touch("source.txt");
|
||||||
let result = scene
|
scene
|
||||||
.ucmd()
|
.ucmd()
|
||||||
.arg("--no-clobber")
|
.arg("--no-clobber")
|
||||||
.arg("source.txt")
|
.arg("source.txt")
|
||||||
.arg("dest.txt")
|
.arg("dest.txt")
|
||||||
.run();
|
.succeeds()
|
||||||
|
.no_stderr();
|
||||||
|
|
||||||
println!("stderr = {:?}", result.stderr_str());
|
|
||||||
println!("stdout = {:?}", result.stdout_str());
|
|
||||||
assert!(result.success);
|
|
||||||
assert!(result.stderr.is_empty());
|
|
||||||
assert_eq!(at.read("source.txt"), "");
|
assert_eq!(at.read("source.txt"), "");
|
||||||
|
|
||||||
at.append("source.txt", "some-content");
|
at.append("source.txt", "some-content");
|
||||||
let result = scene
|
scene
|
||||||
.ucmd()
|
.ucmd()
|
||||||
.arg("--no-clobber")
|
.arg("--no-clobber")
|
||||||
.arg("source.txt")
|
.arg("source.txt")
|
||||||
.arg("dest.txt")
|
.arg("dest.txt")
|
||||||
.run();
|
.succeeds()
|
||||||
|
.stdout_does_not_contain("Not overwriting");
|
||||||
|
|
||||||
assert!(result.success);
|
|
||||||
assert_eq!(at.read("source.txt"), "some-content");
|
assert_eq!(at.read("source.txt"), "some-content");
|
||||||
// Should be empty as the "no-clobber" should keep
|
// Should be empty as the "no-clobber" should keep
|
||||||
// the previous version
|
// the previous version
|
||||||
assert_eq!(at.read("dest.txt"), "");
|
assert_eq!(at.read("dest.txt"), "");
|
||||||
assert!(!result.stderr.contains("Not overwriting"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -311,16 +266,11 @@ fn test_cp_arg_force() {
|
||||||
permissions.set_readonly(true);
|
permissions.set_readonly(true);
|
||||||
set_permissions(at.plus(TEST_HELLO_WORLD_DEST), permissions).unwrap();
|
set_permissions(at.plus(TEST_HELLO_WORLD_DEST), permissions).unwrap();
|
||||||
|
|
||||||
let result = ucmd
|
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
.arg(TEST_HELLO_WORLD_SOURCE)
|
|
||||||
.arg("--force")
|
.arg("--force")
|
||||||
.arg(TEST_HELLO_WORLD_DEST)
|
.arg(TEST_HELLO_WORLD_DEST)
|
||||||
.run();
|
.succeeds();
|
||||||
|
|
||||||
println!("{:?}", result.stderr_str());
|
|
||||||
println!("{:?}", result.stdout_str());
|
|
||||||
|
|
||||||
assert!(result.success);
|
|
||||||
assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n");
|
assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,13 +292,11 @@ fn test_cp_arg_remove_destination() {
|
||||||
permissions.set_readonly(true);
|
permissions.set_readonly(true);
|
||||||
set_permissions(at.plus(TEST_HELLO_WORLD_DEST), permissions).unwrap();
|
set_permissions(at.plus(TEST_HELLO_WORLD_DEST), permissions).unwrap();
|
||||||
|
|
||||||
let result = ucmd
|
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
.arg(TEST_HELLO_WORLD_SOURCE)
|
|
||||||
.arg("--remove-destination")
|
.arg("--remove-destination")
|
||||||
.arg(TEST_HELLO_WORLD_DEST)
|
.arg(TEST_HELLO_WORLD_DEST)
|
||||||
.run();
|
.succeeds();
|
||||||
|
|
||||||
assert!(result.success);
|
|
||||||
assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n");
|
assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,13 +304,11 @@ fn test_cp_arg_remove_destination() {
|
||||||
fn test_cp_arg_backup() {
|
fn test_cp_arg_backup() {
|
||||||
let (at, mut ucmd) = at_and_ucmd!();
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
let result = ucmd
|
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
.arg(TEST_HELLO_WORLD_SOURCE)
|
|
||||||
.arg("--backup")
|
.arg("--backup")
|
||||||
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
||||||
.run();
|
.succeeds();
|
||||||
|
|
||||||
assert!(result.success);
|
|
||||||
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
|
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
at.read(&*format!("{}~", TEST_HOW_ARE_YOU_SOURCE)),
|
at.read(&*format!("{}~", TEST_HOW_ARE_YOU_SOURCE)),
|
||||||
|
@ -374,14 +320,12 @@ fn test_cp_arg_backup() {
|
||||||
fn test_cp_arg_suffix() {
|
fn test_cp_arg_suffix() {
|
||||||
let (at, mut ucmd) = at_and_ucmd!();
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
let result = ucmd
|
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
.arg(TEST_HELLO_WORLD_SOURCE)
|
|
||||||
.arg("--suffix")
|
.arg("--suffix")
|
||||||
.arg(".bak")
|
.arg(".bak")
|
||||||
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
||||||
.run();
|
.succeeds();
|
||||||
|
|
||||||
assert!(result.success);
|
|
||||||
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
|
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
at.read(&*format!("{}.bak", TEST_HOW_ARE_YOU_SOURCE)),
|
at.read(&*format!("{}.bak", TEST_HOW_ARE_YOU_SOURCE)),
|
||||||
|
@ -391,9 +335,8 @@ fn test_cp_arg_suffix() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cp_deref_conflicting_options() {
|
fn test_cp_deref_conflicting_options() {
|
||||||
let (_at, mut ucmd) = at_and_ucmd!();
|
new_ucmd!()
|
||||||
|
.arg("-LP")
|
||||||
ucmd.arg("-LP")
|
|
||||||
.arg(TEST_COPY_TO_FOLDER)
|
.arg(TEST_COPY_TO_FOLDER)
|
||||||
.arg(TEST_HELLO_WORLD_SOURCE)
|
.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
.fails();
|
.fails();
|
||||||
|
@ -401,8 +344,7 @@ fn test_cp_deref_conflicting_options() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cp_deref() {
|
fn test_cp_deref() {
|
||||||
let scene = TestScenario::new(util_name!());
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
let at = &scene.fixtures;
|
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
let _r = fs::symlink(
|
let _r = fs::symlink(
|
||||||
|
@ -415,16 +357,12 @@ fn test_cp_deref() {
|
||||||
at.subdir.join(TEST_HELLO_WORLD_SOURCE_SYMLINK),
|
at.subdir.join(TEST_HELLO_WORLD_SOURCE_SYMLINK),
|
||||||
);
|
);
|
||||||
//using -L option
|
//using -L option
|
||||||
let result = scene
|
ucmd.arg("-L")
|
||||||
.ucmd()
|
|
||||||
.arg("-L")
|
|
||||||
.arg(TEST_HELLO_WORLD_SOURCE)
|
.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
.arg(TEST_HELLO_WORLD_SOURCE_SYMLINK)
|
.arg(TEST_HELLO_WORLD_SOURCE_SYMLINK)
|
||||||
.arg(TEST_COPY_TO_FOLDER)
|
.arg(TEST_COPY_TO_FOLDER)
|
||||||
.run();
|
.succeeds();
|
||||||
|
|
||||||
// Check that the exit code represents a successful copy.
|
|
||||||
assert!(result.success);
|
|
||||||
let path_to_new_symlink = at
|
let path_to_new_symlink = at
|
||||||
.subdir
|
.subdir
|
||||||
.join(TEST_COPY_TO_FOLDER)
|
.join(TEST_COPY_TO_FOLDER)
|
||||||
|
@ -444,8 +382,7 @@ fn test_cp_deref() {
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cp_no_deref() {
|
fn test_cp_no_deref() {
|
||||||
let scene = TestScenario::new(util_name!());
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
let at = &scene.fixtures;
|
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
let _r = fs::symlink(
|
let _r = fs::symlink(
|
||||||
|
@ -458,16 +395,12 @@ fn test_cp_no_deref() {
|
||||||
at.subdir.join(TEST_HELLO_WORLD_SOURCE_SYMLINK),
|
at.subdir.join(TEST_HELLO_WORLD_SOURCE_SYMLINK),
|
||||||
);
|
);
|
||||||
//using -P option
|
//using -P option
|
||||||
let result = scene
|
ucmd.arg("-P")
|
||||||
.ucmd()
|
|
||||||
.arg("-P")
|
|
||||||
.arg(TEST_HELLO_WORLD_SOURCE)
|
.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
.arg(TEST_HELLO_WORLD_SOURCE_SYMLINK)
|
.arg(TEST_HELLO_WORLD_SOURCE_SYMLINK)
|
||||||
.arg(TEST_COPY_TO_FOLDER)
|
.arg(TEST_COPY_TO_FOLDER)
|
||||||
.run();
|
.succeeds();
|
||||||
|
|
||||||
// Check that the exit code represents a successful copy.
|
|
||||||
assert!(result.success);
|
|
||||||
let path_to_new_symlink = at
|
let path_to_new_symlink = at
|
||||||
.subdir
|
.subdir
|
||||||
.join(TEST_COPY_TO_FOLDER)
|
.join(TEST_COPY_TO_FOLDER)
|
||||||
|
@ -490,14 +423,10 @@ fn test_cp_strip_trailing_slashes() {
|
||||||
let (at, mut ucmd) = at_and_ucmd!();
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
//using --strip-trailing-slashes option
|
//using --strip-trailing-slashes option
|
||||||
let result = ucmd
|
ucmd.arg("--strip-trailing-slashes")
|
||||||
.arg("--strip-trailing-slashes")
|
|
||||||
.arg(format!("{}/", TEST_HELLO_WORLD_SOURCE))
|
.arg(format!("{}/", TEST_HELLO_WORLD_SOURCE))
|
||||||
.arg(TEST_HELLO_WORLD_DEST)
|
.arg(TEST_HELLO_WORLD_DEST)
|
||||||
.run();
|
.succeeds();
|
||||||
|
|
||||||
// Check that the exit code represents a successful copy.
|
|
||||||
assert!(result.success);
|
|
||||||
|
|
||||||
// Check the content of the destination file that was copied.
|
// Check the content of the destination file that was copied.
|
||||||
assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n");
|
assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n");
|
||||||
|
@ -507,14 +436,11 @@ fn test_cp_strip_trailing_slashes() {
|
||||||
fn test_cp_parents() {
|
fn test_cp_parents() {
|
||||||
let (at, mut ucmd) = at_and_ucmd!();
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
let result = ucmd
|
ucmd.arg("--parents")
|
||||||
.arg("--parents")
|
|
||||||
.arg(TEST_COPY_FROM_FOLDER_FILE)
|
.arg(TEST_COPY_FROM_FOLDER_FILE)
|
||||||
.arg(TEST_COPY_TO_FOLDER)
|
.arg(TEST_COPY_TO_FOLDER)
|
||||||
.run();
|
.succeeds();
|
||||||
|
|
||||||
assert!(result.success);
|
|
||||||
// Check the content of the destination file that was copied.
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
at.read(&format!(
|
at.read(&format!(
|
||||||
"{}/{}",
|
"{}/{}",
|
||||||
|
@ -528,14 +454,12 @@ fn test_cp_parents() {
|
||||||
fn test_cp_parents_multiple_files() {
|
fn test_cp_parents_multiple_files() {
|
||||||
let (at, mut ucmd) = at_and_ucmd!();
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
let result = ucmd
|
ucmd.arg("--parents")
|
||||||
.arg("--parents")
|
|
||||||
.arg(TEST_COPY_FROM_FOLDER_FILE)
|
.arg(TEST_COPY_FROM_FOLDER_FILE)
|
||||||
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
||||||
.arg(TEST_COPY_TO_FOLDER)
|
.arg(TEST_COPY_TO_FOLDER)
|
||||||
.run();
|
.succeeds();
|
||||||
|
|
||||||
assert!(result.success);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
at.read(&format!(
|
at.read(&format!(
|
||||||
"{}/{}",
|
"{}/{}",
|
||||||
|
@ -554,20 +478,12 @@ fn test_cp_parents_multiple_files() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cp_parents_dest_not_directory() {
|
fn test_cp_parents_dest_not_directory() {
|
||||||
let (_, mut ucmd) = at_and_ucmd!();
|
new_ucmd!()
|
||||||
|
|
||||||
let result = ucmd
|
|
||||||
.arg("--parents")
|
.arg("--parents")
|
||||||
.arg(TEST_COPY_FROM_FOLDER_FILE)
|
.arg(TEST_COPY_FROM_FOLDER_FILE)
|
||||||
.arg(TEST_HELLO_WORLD_DEST)
|
.arg(TEST_HELLO_WORLD_DEST)
|
||||||
.run();
|
.fails()
|
||||||
println!("{:?}", result);
|
.stderr_contains("with --parents, the destination must be a directory");
|
||||||
|
|
||||||
// Check that we did not succeed in copying.
|
|
||||||
assert!(!result.success);
|
|
||||||
assert!(result
|
|
||||||
.stderr
|
|
||||||
.contains("with --parents, the destination must be a directory"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -594,18 +510,14 @@ fn test_cp_deref_folder_to_folder() {
|
||||||
assert!(env::set_current_dir(&cwd).is_ok());
|
assert!(env::set_current_dir(&cwd).is_ok());
|
||||||
|
|
||||||
//using -P -R option
|
//using -P -R option
|
||||||
let result = scene
|
scene
|
||||||
.ucmd()
|
.ucmd()
|
||||||
.arg("-L")
|
.arg("-L")
|
||||||
.arg("-R")
|
.arg("-R")
|
||||||
.arg("-v")
|
.arg("-v")
|
||||||
.arg(TEST_COPY_FROM_FOLDER)
|
.arg(TEST_COPY_FROM_FOLDER)
|
||||||
.arg(TEST_COPY_TO_FOLDER_NEW)
|
.arg(TEST_COPY_TO_FOLDER_NEW)
|
||||||
.run();
|
.succeeds();
|
||||||
println!("cp output {}", result.stdout_str());
|
|
||||||
|
|
||||||
// Check that the exit code represents a successful copy.
|
|
||||||
assert!(result.success);
|
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
{
|
{
|
||||||
|
@ -698,18 +610,14 @@ fn test_cp_no_deref_folder_to_folder() {
|
||||||
assert!(env::set_current_dir(&cwd).is_ok());
|
assert!(env::set_current_dir(&cwd).is_ok());
|
||||||
|
|
||||||
//using -P -R option
|
//using -P -R option
|
||||||
let result = scene
|
scene
|
||||||
.ucmd()
|
.ucmd()
|
||||||
.arg("-P")
|
.arg("-P")
|
||||||
.arg("-R")
|
.arg("-R")
|
||||||
.arg("-v")
|
.arg("-v")
|
||||||
.arg(TEST_COPY_FROM_FOLDER)
|
.arg(TEST_COPY_FROM_FOLDER)
|
||||||
.arg(TEST_COPY_TO_FOLDER_NEW)
|
.arg(TEST_COPY_TO_FOLDER_NEW)
|
||||||
.run();
|
.succeeds();
|
||||||
println!("cp output {}", result.stdout_str());
|
|
||||||
|
|
||||||
// Check that the exit code represents a successful copy.
|
|
||||||
assert!(result.success);
|
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
{
|
{
|
||||||
|
@ -791,13 +699,11 @@ fn test_cp_archive() {
|
||||||
previous,
|
previous,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let result = ucmd
|
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
.arg(TEST_HELLO_WORLD_SOURCE)
|
|
||||||
.arg("--archive")
|
.arg("--archive")
|
||||||
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
||||||
.run();
|
.succeeds();
|
||||||
|
|
||||||
assert!(result.success);
|
|
||||||
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
|
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
|
||||||
|
|
||||||
let metadata = std_fs::metadata(at.subdir.join(TEST_HELLO_WORLD_SOURCE)).unwrap();
|
let metadata = std_fs::metadata(at.subdir.join(TEST_HELLO_WORLD_SOURCE)).unwrap();
|
||||||
|
@ -807,11 +713,10 @@ fn test_cp_archive() {
|
||||||
let creation2 = metadata2.modified().unwrap();
|
let creation2 = metadata2.modified().unwrap();
|
||||||
|
|
||||||
let scene2 = TestScenario::new("ls");
|
let scene2 = TestScenario::new("ls");
|
||||||
let result = scene2.cmd("ls").arg("-al").arg(at.subdir).run();
|
let result = scene2.cmd("ls").arg("-al").arg(at.subdir).succeeds();
|
||||||
|
|
||||||
println!("ls dest {}", result.stdout_str());
|
println!("ls dest {}", result.stdout_str());
|
||||||
assert_eq!(creation, creation2);
|
assert_eq!(creation, creation2);
|
||||||
assert!(result.success);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -850,11 +755,10 @@ fn test_cp_archive_recursive() {
|
||||||
// Back to the initial cwd (breaks the other tests)
|
// Back to the initial cwd (breaks the other tests)
|
||||||
assert!(env::set_current_dir(&cwd).is_ok());
|
assert!(env::set_current_dir(&cwd).is_ok());
|
||||||
|
|
||||||
let resultg = ucmd
|
ucmd.arg("--archive")
|
||||||
.arg("--archive")
|
|
||||||
.arg(TEST_COPY_TO_FOLDER)
|
.arg(TEST_COPY_TO_FOLDER)
|
||||||
.arg(TEST_COPY_TO_FOLDER_NEW)
|
.arg(TEST_COPY_TO_FOLDER_NEW)
|
||||||
.run();
|
.fails(); // fails for now
|
||||||
|
|
||||||
let scene2 = TestScenario::new("ls");
|
let scene2 = TestScenario::new("ls");
|
||||||
let result = scene2
|
let result = scene2
|
||||||
|
@ -865,7 +769,6 @@ fn test_cp_archive_recursive() {
|
||||||
|
|
||||||
println!("ls dest {}", result.stdout_str());
|
println!("ls dest {}", result.stdout_str());
|
||||||
|
|
||||||
let scene2 = TestScenario::new("ls");
|
|
||||||
let result = scene2
|
let result = scene2
|
||||||
.cmd("ls")
|
.cmd("ls")
|
||||||
.arg("-al")
|
.arg("-al")
|
||||||
|
@ -910,9 +813,6 @@ fn test_cp_archive_recursive() {
|
||||||
.join("2.link")
|
.join("2.link")
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
));
|
));
|
||||||
|
|
||||||
// fails for now
|
|
||||||
assert!(resultg.success);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -928,13 +828,11 @@ fn test_cp_preserve_timestamps() {
|
||||||
previous,
|
previous,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let result = ucmd
|
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
.arg(TEST_HELLO_WORLD_SOURCE)
|
|
||||||
.arg("--preserve=timestamps")
|
.arg("--preserve=timestamps")
|
||||||
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
||||||
.run();
|
.succeeds();
|
||||||
|
|
||||||
assert!(result.success);
|
|
||||||
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
|
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
|
||||||
|
|
||||||
let metadata = std_fs::metadata(at.subdir.join(TEST_HELLO_WORLD_SOURCE)).unwrap();
|
let metadata = std_fs::metadata(at.subdir.join(TEST_HELLO_WORLD_SOURCE)).unwrap();
|
||||||
|
@ -948,7 +846,6 @@ fn test_cp_preserve_timestamps() {
|
||||||
|
|
||||||
println!("ls dest {}", result.stdout_str());
|
println!("ls dest {}", result.stdout_str());
|
||||||
assert_eq!(creation, creation2);
|
assert_eq!(creation, creation2);
|
||||||
assert!(result.success);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -966,13 +863,11 @@ fn test_cp_dont_preserve_timestamps() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
sleep(Duration::from_secs(3));
|
sleep(Duration::from_secs(3));
|
||||||
|
|
||||||
let result = ucmd
|
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
.arg(TEST_HELLO_WORLD_SOURCE)
|
|
||||||
.arg("--no-preserve=timestamps")
|
.arg("--no-preserve=timestamps")
|
||||||
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
||||||
.run();
|
.succeeds();
|
||||||
|
|
||||||
assert!(result.success);
|
|
||||||
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
|
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
|
||||||
|
|
||||||
let metadata = std_fs::metadata(at.subdir.join(TEST_HELLO_WORLD_SOURCE)).unwrap();
|
let metadata = std_fs::metadata(at.subdir.join(TEST_HELLO_WORLD_SOURCE)).unwrap();
|
||||||
|
@ -992,7 +887,6 @@ fn test_cp_dont_preserve_timestamps() {
|
||||||
// Some margins with time check
|
// Some margins with time check
|
||||||
assert!(res.as_secs() > 3595);
|
assert!(res.as_secs() > 3595);
|
||||||
assert!(res.as_secs() < 3605);
|
assert!(res.as_secs() < 3605);
|
||||||
assert!(result.success);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1017,7 +911,7 @@ fn test_cp_one_file_system() {
|
||||||
let scene = TestScenario::new(util_name!());
|
let scene = TestScenario::new(util_name!());
|
||||||
|
|
||||||
// Test must be run as root (or with `sudo -E`)
|
// Test must be run as root (or with `sudo -E`)
|
||||||
if scene.cmd("whoami").run().stdout != "root\n" {
|
if scene.cmd("whoami").run().stdout_str() != "root\n" {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1042,17 +936,16 @@ fn test_cp_one_file_system() {
|
||||||
at_src.touch(TEST_MOUNT_OTHER_FILESYSTEM_FILE);
|
at_src.touch(TEST_MOUNT_OTHER_FILESYSTEM_FILE);
|
||||||
|
|
||||||
// Begin testing -x flag
|
// Begin testing -x flag
|
||||||
let result = scene
|
scene
|
||||||
.ucmd()
|
.ucmd()
|
||||||
.arg("-rx")
|
.arg("-rx")
|
||||||
.arg(TEST_MOUNT_COPY_FROM_FOLDER)
|
.arg(TEST_MOUNT_COPY_FROM_FOLDER)
|
||||||
.arg(TEST_COPY_TO_FOLDER_NEW)
|
.arg(TEST_COPY_TO_FOLDER_NEW)
|
||||||
.run();
|
.succeeds();
|
||||||
|
|
||||||
// Ditch the mount before the asserts
|
// Ditch the mount before the asserts
|
||||||
scene.cmd("umount").arg(mountpoint_path).succeeds();
|
scene.cmd("umount").arg(mountpoint_path).succeeds();
|
||||||
|
|
||||||
assert!(result.success);
|
|
||||||
assert!(!at_dst.file_exists(TEST_MOUNT_OTHER_FILESYSTEM_FILE));
|
assert!(!at_dst.file_exists(TEST_MOUNT_OTHER_FILESYSTEM_FILE));
|
||||||
// Check if the other files were copied from the source folder hirerarchy
|
// Check if the other files were copied from the source folder hirerarchy
|
||||||
for entry in WalkDir::new(at_src.as_string()) {
|
for entry in WalkDir::new(at_src.as_string()) {
|
||||||
|
@ -1072,3 +965,59 @@ fn test_cp_one_file_system() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
|
fn test_cp_reflink_always() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
let result = ucmd
|
||||||
|
.arg("--reflink=always")
|
||||||
|
.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
|
.arg(TEST_EXISTING_FILE)
|
||||||
|
.run();
|
||||||
|
|
||||||
|
if result.succeeded() {
|
||||||
|
// Check the content of the destination file
|
||||||
|
assert_eq!(at.read(TEST_EXISTING_FILE), "Hello, World!\n");
|
||||||
|
} else {
|
||||||
|
// Older Linux versions do not support cloning.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
|
fn test_cp_reflink_auto() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
ucmd.arg("--reflink=auto")
|
||||||
|
.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
|
.arg(TEST_EXISTING_FILE)
|
||||||
|
.succeeds();
|
||||||
|
|
||||||
|
// Check the content of the destination file
|
||||||
|
assert_eq!(at.read(TEST_EXISTING_FILE), "Hello, World!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
|
fn test_cp_reflink_never() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
ucmd.arg("--reflink=never")
|
||||||
|
.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
|
.arg(TEST_EXISTING_FILE)
|
||||||
|
.succeeds();
|
||||||
|
|
||||||
|
// Check the content of the destination file
|
||||||
|
assert_eq!(at.read(TEST_EXISTING_FILE), "Hello, World!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
|
fn test_cp_reflink_bad() {
|
||||||
|
let (_, mut ucmd) = at_and_ucmd!();
|
||||||
|
let _result = ucmd
|
||||||
|
.arg("--reflink=bad")
|
||||||
|
.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
|
.arg(TEST_EXISTING_FILE)
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("invalid argument");
|
||||||
|
}
|
||||||
|
|
|
@ -7,174 +7,147 @@ use rust_users::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_date_email() {
|
fn test_date_email() {
|
||||||
let (_, mut ucmd) = at_and_ucmd!();
|
new_ucmd!().arg("--rfc-email").succeeds();
|
||||||
let result = ucmd.arg("--rfc-email").run();
|
|
||||||
assert!(result.success);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_date_email2() {
|
fn test_date_email2() {
|
||||||
let (_, mut ucmd) = at_and_ucmd!();
|
new_ucmd!().arg("-R").succeeds();
|
||||||
let result = ucmd.arg("-R").run();
|
|
||||||
assert!(result.success);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_date_rfc_3339() {
|
fn test_date_rfc_3339() {
|
||||||
let scene = TestScenario::new(util_name!());
|
let scene = TestScenario::new(util_name!());
|
||||||
|
|
||||||
let mut result = scene.ucmd().arg("--rfc-3339=ns").succeeds();
|
let rfc_regexp = concat!(
|
||||||
|
r#"(\d+)-(0[1-9]|1[012])-(0[1-9]|[12]\d|3[01])\s([01]\d|2[0-3]):"#,
|
||||||
|
r#"([0-5]\d):([0-5]\d|60)(\.\d+)?(([Zz])|([\+|\-]([01]\d|2[0-3])))"#
|
||||||
|
);
|
||||||
|
let re = Regex::new(rfc_regexp).unwrap();
|
||||||
|
|
||||||
// Check that the output matches the regexp
|
// Check that the output matches the regexp
|
||||||
let rfc_regexp = r"(\d+)-(0[1-9]|1[012])-(0[1-9]|[12]\d|3[01])\s([01]\d|2[0-3]):([0-5]\d):([0-5]\d|60)(\.\d+)?(([Zz])|([\+|\-]([01]\d|2[0-3])))";
|
scene
|
||||||
let re = Regex::new(rfc_regexp).unwrap();
|
.ucmd()
|
||||||
assert!(re.is_match(&result.stdout_str().trim()));
|
.arg("--rfc-3339=ns")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_matches(&re);
|
||||||
|
|
||||||
result = scene.ucmd().arg("--rfc-3339=seconds").succeeds();
|
scene
|
||||||
|
.ucmd()
|
||||||
// Check that the output matches the regexp
|
.arg("--rfc-3339=seconds")
|
||||||
let re = Regex::new(rfc_regexp).unwrap();
|
.succeeds()
|
||||||
assert!(re.is_match(&result.stdout_str().trim()));
|
.stdout_matches(&re);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_date_rfc_8601() {
|
fn test_date_rfc_8601() {
|
||||||
let (_, mut ucmd) = at_and_ucmd!();
|
new_ucmd!().arg("--iso-8601=ns").succeeds();
|
||||||
let result = ucmd.arg("--iso-8601=ns").run();
|
|
||||||
assert!(result.success);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_date_rfc_8601_second() {
|
fn test_date_rfc_8601_second() {
|
||||||
let (_, mut ucmd) = at_and_ucmd!();
|
new_ucmd!().arg("--iso-8601=second").succeeds();
|
||||||
let result = ucmd.arg("--iso-8601=second").run();
|
|
||||||
assert!(result.success);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_date_utc() {
|
fn test_date_utc() {
|
||||||
let (_, mut ucmd) = at_and_ucmd!();
|
new_ucmd!().arg("--utc").succeeds();
|
||||||
let result = ucmd.arg("--utc").run();
|
|
||||||
assert!(result.success);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_date_universal() {
|
fn test_date_universal() {
|
||||||
let (_, mut ucmd) = at_and_ucmd!();
|
new_ucmd!().arg("--universal").succeeds();
|
||||||
let result = ucmd.arg("--universal").run();
|
|
||||||
assert!(result.success);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_date_format_y() {
|
fn test_date_format_y() {
|
||||||
let scene = TestScenario::new(util_name!());
|
let scene = TestScenario::new(util_name!());
|
||||||
|
|
||||||
let mut result = scene.ucmd().arg("+%Y").succeeds();
|
|
||||||
|
|
||||||
assert!(result.success);
|
|
||||||
let mut re = Regex::new(r"^\d{4}$").unwrap();
|
let mut re = Regex::new(r"^\d{4}$").unwrap();
|
||||||
assert!(re.is_match(&result.stdout_str().trim()));
|
scene.ucmd().arg("+%Y").succeeds().stdout_matches(&re);
|
||||||
|
|
||||||
result = scene.ucmd().arg("+%y").succeeds();
|
|
||||||
|
|
||||||
assert!(result.success);
|
|
||||||
re = Regex::new(r"^\d{2}$").unwrap();
|
re = Regex::new(r"^\d{2}$").unwrap();
|
||||||
assert!(re.is_match(&result.stdout_str().trim()));
|
scene.ucmd().arg("+%y").succeeds().stdout_matches(&re);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_date_format_m() {
|
fn test_date_format_m() {
|
||||||
let scene = TestScenario::new(util_name!());
|
let scene = TestScenario::new(util_name!());
|
||||||
|
|
||||||
let mut result = scene.ucmd().arg("+%b").succeeds();
|
|
||||||
|
|
||||||
assert!(result.success);
|
|
||||||
let mut re = Regex::new(r"\S+").unwrap();
|
let mut re = Regex::new(r"\S+").unwrap();
|
||||||
assert!(re.is_match(&result.stdout_str().trim()));
|
scene.ucmd().arg("+%b").succeeds().stdout_matches(&re);
|
||||||
|
|
||||||
result = scene.ucmd().arg("+%m").succeeds();
|
|
||||||
|
|
||||||
assert!(result.success);
|
|
||||||
re = Regex::new(r"^\d{2}$").unwrap();
|
re = Regex::new(r"^\d{2}$").unwrap();
|
||||||
assert!(re.is_match(&result.stdout_str().trim()));
|
scene.ucmd().arg("+%m").succeeds().stdout_matches(&re);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_date_format_day() {
|
fn test_date_format_day() {
|
||||||
let scene = TestScenario::new(util_name!());
|
let scene = TestScenario::new(util_name!());
|
||||||
|
|
||||||
let mut result = scene.ucmd().arg("+%a").succeeds();
|
|
||||||
|
|
||||||
assert!(result.success);
|
|
||||||
let mut re = Regex::new(r"\S+").unwrap();
|
let mut re = Regex::new(r"\S+").unwrap();
|
||||||
assert!(re.is_match(&result.stdout_str().trim()));
|
scene.ucmd().arg("+%a").succeeds().stdout_matches(&re);
|
||||||
|
|
||||||
result = scene.ucmd().arg("+%A").succeeds();
|
|
||||||
|
|
||||||
assert!(result.success);
|
|
||||||
|
|
||||||
re = Regex::new(r"\S+").unwrap();
|
re = Regex::new(r"\S+").unwrap();
|
||||||
assert!(re.is_match(&result.stdout_str().trim()));
|
scene.ucmd().arg("+%A").succeeds().stdout_matches(&re);
|
||||||
|
|
||||||
result = scene.ucmd().arg("+%u").succeeds();
|
|
||||||
|
|
||||||
assert!(result.success);
|
|
||||||
re = Regex::new(r"^\d{1}$").unwrap();
|
re = Regex::new(r"^\d{1}$").unwrap();
|
||||||
assert!(re.is_match(&result.stdout_str().trim()));
|
scene.ucmd().arg("+%u").succeeds().stdout_matches(&re);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_date_format_full_day() {
|
fn test_date_format_full_day() {
|
||||||
let scene = TestScenario::new(util_name!());
|
|
||||||
|
|
||||||
let result = scene.ucmd().arg("+'%a %Y-%m-%d'").succeeds();
|
|
||||||
|
|
||||||
assert!(result.success);
|
|
||||||
let re = Regex::new(r"\S+ \d{4}-\d{2}-\d{2}").unwrap();
|
let re = Regex::new(r"\S+ \d{4}-\d{2}-\d{2}").unwrap();
|
||||||
assert!(re.is_match(&result.stdout_str().trim()));
|
new_ucmd!()
|
||||||
|
.arg("+'%a %Y-%m-%d'")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_matches(&re);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(all(unix, not(target_os = "macos")))]
|
#[cfg(all(unix, not(target_os = "macos")))]
|
||||||
fn test_date_set_valid() {
|
fn test_date_set_valid() {
|
||||||
if get_effective_uid() == 0 {
|
if get_effective_uid() == 0 {
|
||||||
let (_, mut ucmd) = at_and_ucmd!();
|
new_ucmd!()
|
||||||
let result = ucmd
|
|
||||||
.arg("--set")
|
.arg("--set")
|
||||||
.arg("2020-03-12 13:30:00+08:00")
|
.arg("2020-03-12 13:30:00+08:00")
|
||||||
.succeeds();
|
.succeeds()
|
||||||
result.no_stdout().no_stderr();
|
.no_stdout()
|
||||||
|
.no_stderr();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(windows, all(unix, not(target_os = "macos"))))]
|
#[cfg(any(windows, all(unix, not(target_os = "macos"))))]
|
||||||
fn test_date_set_invalid() {
|
fn test_date_set_invalid() {
|
||||||
let (_, mut ucmd) = at_and_ucmd!();
|
let result = new_ucmd!().arg("--set").arg("123abcd").fails();
|
||||||
let result = ucmd.arg("--set").arg("123abcd").fails();
|
result.no_stdout();
|
||||||
let result = result.no_stdout();
|
assert!(result.stderr_str().starts_with("date: invalid date "));
|
||||||
assert!(result.stderr.starts_with("date: invalid date "));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(all(unix, not(target_os = "macos")))]
|
#[cfg(all(unix, not(target_os = "macos")))]
|
||||||
fn test_date_set_permissions_error() {
|
fn test_date_set_permissions_error() {
|
||||||
if !(get_effective_uid() == 0 || is_wsl()) {
|
if !(get_effective_uid() == 0 || uucore::os::is_wsl_1()) {
|
||||||
let (_, mut ucmd) = at_and_ucmd!();
|
let result = new_ucmd!()
|
||||||
let result = ucmd.arg("--set").arg("2020-03-11 21:45:00+08:00").fails();
|
.arg("--set")
|
||||||
let result = result.no_stdout();
|
.arg("2020-03-11 21:45:00+08:00")
|
||||||
assert!(result.stderr.starts_with("date: cannot set date: "));
|
.fails();
|
||||||
|
result.no_stdout();
|
||||||
|
assert!(result.stderr_str().starts_with("date: cannot set date: "));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
fn test_date_set_mac_unavailable() {
|
fn test_date_set_mac_unavailable() {
|
||||||
let (_, mut ucmd) = at_and_ucmd!();
|
let result = new_ucmd!()
|
||||||
let result = ucmd.arg("--set").arg("2020-03-11 21:45:00+08:00").fails();
|
.arg("--set")
|
||||||
let result = result.no_stdout();
|
.arg("2020-03-11 21:45:00+08:00")
|
||||||
|
.fails();
|
||||||
|
result.no_stdout();
|
||||||
assert!(result
|
assert!(result
|
||||||
.stderr
|
.stderr_str()
|
||||||
.starts_with("date: setting the date is not supported by macOS"));
|
.starts_with("date: setting the date is not supported by macOS"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,13 +156,12 @@ fn test_date_set_mac_unavailable() {
|
||||||
/// TODO: expected to fail currently; change to succeeds() when required.
|
/// TODO: expected to fail currently; change to succeeds() when required.
|
||||||
fn test_date_set_valid_2() {
|
fn test_date_set_valid_2() {
|
||||||
if get_effective_uid() == 0 {
|
if get_effective_uid() == 0 {
|
||||||
let (_, mut ucmd) = at_and_ucmd!();
|
let result = new_ucmd!()
|
||||||
let result = ucmd
|
|
||||||
.arg("--set")
|
.arg("--set")
|
||||||
.arg("Sat 20 Mar 2021 14:53:01 AWST")
|
.arg("Sat 20 Mar 2021 14:53:01 AWST")
|
||||||
.fails();
|
.fails();
|
||||||
let result = result.no_stdout();
|
result.no_stdout();
|
||||||
assert!(result.stderr.starts_with("date: invalid date "));
|
assert!(result.stderr_str().starts_with("date: invalid date "));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,13 +170,12 @@ fn test_date_set_valid_2() {
|
||||||
/// TODO: expected to fail currently; change to succeeds() when required.
|
/// TODO: expected to fail currently; change to succeeds() when required.
|
||||||
fn test_date_set_valid_3() {
|
fn test_date_set_valid_3() {
|
||||||
if get_effective_uid() == 0 {
|
if get_effective_uid() == 0 {
|
||||||
let (_, mut ucmd) = at_and_ucmd!();
|
let result = new_ucmd!()
|
||||||
let result = ucmd
|
|
||||||
.arg("--set")
|
.arg("--set")
|
||||||
.arg("Sat 20 Mar 2021 14:53:01") // Local timezone
|
.arg("Sat 20 Mar 2021 14:53:01") // Local timezone
|
||||||
.fails();
|
.fails();
|
||||||
let result = result.no_stdout();
|
result.no_stdout();
|
||||||
assert!(result.stderr.starts_with("date: invalid date "));
|
assert!(result.stderr_str().starts_with("date: invalid date "));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,12 +184,11 @@ fn test_date_set_valid_3() {
|
||||||
/// TODO: expected to fail currently; change to succeeds() when required.
|
/// TODO: expected to fail currently; change to succeeds() when required.
|
||||||
fn test_date_set_valid_4() {
|
fn test_date_set_valid_4() {
|
||||||
if get_effective_uid() == 0 {
|
if get_effective_uid() == 0 {
|
||||||
let (_, mut ucmd) = at_and_ucmd!();
|
let result = new_ucmd!()
|
||||||
let result = ucmd
|
|
||||||
.arg("--set")
|
.arg("--set")
|
||||||
.arg("2020-03-11 21:45:00") // Local timezone
|
.arg("2020-03-11 21:45:00") // Local timezone
|
||||||
.fails();
|
.fails();
|
||||||
let result = result.no_stdout();
|
result.no_stdout();
|
||||||
assert!(result.stderr.starts_with("date: invalid date "));
|
assert!(result.stderr_str().starts_with("date: invalid date "));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,30 +2,22 @@ use crate::common::util::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_df_compatible_no_size_arg() {
|
fn test_df_compatible_no_size_arg() {
|
||||||
let (_, mut ucmd) = at_and_ucmd!();
|
new_ucmd!().arg("-a").succeeds();
|
||||||
let result = ucmd.arg("-a").run();
|
|
||||||
assert!(result.success);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_df_compatible() {
|
fn test_df_compatible() {
|
||||||
let (_, mut ucmd) = at_and_ucmd!();
|
new_ucmd!().arg("-ah").succeeds();
|
||||||
let result = ucmd.arg("-ah").run();
|
|
||||||
assert!(result.success);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_df_compatible_type() {
|
fn test_df_compatible_type() {
|
||||||
let (_, mut ucmd) = at_and_ucmd!();
|
new_ucmd!().arg("-aT").succeeds();
|
||||||
let result = ucmd.arg("-aT").run();
|
|
||||||
assert!(result.success);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_df_compatible_si() {
|
fn test_df_compatible_si() {
|
||||||
let (_, mut ucmd) = at_and_ucmd!();
|
new_ucmd!().arg("-aH").succeeds();
|
||||||
let result = ucmd.arg("-aH").run();
|
|
||||||
assert!(result.success);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToDO: more tests...
|
// ToDO: more tests...
|
||||||
|
|
|
@ -10,7 +10,7 @@ fn test_du_basics() {
|
||||||
new_ucmd!().succeeds().no_stderr();
|
new_ucmd!().succeeds().no_stderr();
|
||||||
}
|
}
|
||||||
#[cfg(target_vendor = "apple")]
|
#[cfg(target_vendor = "apple")]
|
||||||
fn _du_basics(s: String) {
|
fn _du_basics(s: &str) {
|
||||||
let answer = "32\t./subdir
|
let answer = "32\t./subdir
|
||||||
8\t./subdir/deeper
|
8\t./subdir/deeper
|
||||||
24\t./subdir/links
|
24\t./subdir/links
|
||||||
|
@ -30,11 +30,18 @@ fn _du_basics(s: &str) {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_du_basics_subdir() {
|
fn test_du_basics_subdir() {
|
||||||
let (_at, mut ucmd) = at_and_ucmd!();
|
let scene = TestScenario::new(util_name!());
|
||||||
|
|
||||||
let result = ucmd.arg(SUB_DIR).run();
|
let result = scene.ucmd().arg(SUB_DIR).succeeds();
|
||||||
assert!(result.success);
|
|
||||||
assert_eq!(result.stderr, "");
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
let result_reference = scene.cmd("du").arg(SUB_DIR).run();
|
||||||
|
if result_reference.succeeded() {
|
||||||
|
assert_eq!(result.stdout_str(), result_reference.stdout_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
_du_basics_subdir(result.stdout_str());
|
_du_basics_subdir(result.stdout_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +56,7 @@ fn _du_basics_subdir(s: &str) {
|
||||||
#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))]
|
#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))]
|
||||||
fn _du_basics_subdir(s: &str) {
|
fn _du_basics_subdir(s: &str) {
|
||||||
// MS-WSL linux has altered expected output
|
// MS-WSL linux has altered expected output
|
||||||
if !is_wsl() {
|
if !uucore::os::is_wsl_1() {
|
||||||
assert_eq!(s, "8\tsubdir/deeper\n");
|
assert_eq!(s, "8\tsubdir/deeper\n");
|
||||||
} else {
|
} else {
|
||||||
assert_eq!(s, "0\tsubdir/deeper\n");
|
assert_eq!(s, "0\tsubdir/deeper\n");
|
||||||
|
@ -58,26 +65,29 @@ fn _du_basics_subdir(s: &str) {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_du_basics_bad_name() {
|
fn test_du_basics_bad_name() {
|
||||||
let (_at, mut ucmd) = at_and_ucmd!();
|
new_ucmd!()
|
||||||
|
.arg("bad_name")
|
||||||
let result = ucmd.arg("bad_name").run();
|
.succeeds() // TODO: replace with ".fails()" once `du` is fixed
|
||||||
assert_eq!(result.stdout_str(), "");
|
.stderr_only("du: error: bad_name: No such file or directory\n");
|
||||||
assert_eq!(
|
|
||||||
result.stderr,
|
|
||||||
"du: error: bad_name: No such file or directory\n"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_du_soft_link() {
|
fn test_du_soft_link() {
|
||||||
let ts = TestScenario::new("du");
|
let scene = TestScenario::new(util_name!());
|
||||||
|
let at = &scene.fixtures;
|
||||||
|
|
||||||
let link = ts.ccmd("ln").arg("-s").arg(SUB_FILE).arg(SUB_LINK).run();
|
at.symlink_file(SUB_FILE, SUB_LINK);
|
||||||
assert!(link.success);
|
|
||||||
|
|
||||||
let result = ts.ucmd().arg(SUB_DIR_LINKS).run();
|
let result = scene.ucmd().arg(SUB_DIR_LINKS).succeeds();
|
||||||
assert!(result.success);
|
|
||||||
assert_eq!(result.stderr, "");
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
let result_reference = scene.cmd("du").arg(SUB_DIR_LINKS).run();
|
||||||
|
if result_reference.succeeded() {
|
||||||
|
assert_eq!(result.stdout_str(), result_reference.stdout_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
_du_soft_link(result.stdout_str());
|
_du_soft_link(result.stdout_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,7 +103,7 @@ fn _du_soft_link(s: &str) {
|
||||||
#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))]
|
#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))]
|
||||||
fn _du_soft_link(s: &str) {
|
fn _du_soft_link(s: &str) {
|
||||||
// MS-WSL linux has altered expected output
|
// MS-WSL linux has altered expected output
|
||||||
if !is_wsl() {
|
if !uucore::os::is_wsl_1() {
|
||||||
assert_eq!(s, "16\tsubdir/links\n");
|
assert_eq!(s, "16\tsubdir/links\n");
|
||||||
} else {
|
} else {
|
||||||
assert_eq!(s, "8\tsubdir/links\n");
|
assert_eq!(s, "8\tsubdir/links\n");
|
||||||
|
@ -102,14 +112,23 @@ fn _du_soft_link(s: &str) {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_du_hard_link() {
|
fn test_du_hard_link() {
|
||||||
let ts = TestScenario::new("du");
|
let scene = TestScenario::new(util_name!());
|
||||||
|
|
||||||
let link = ts.ccmd("ln").arg(SUB_FILE).arg(SUB_LINK).run();
|
let result_ln = scene.cmd("ln").arg(SUB_FILE).arg(SUB_LINK).run();
|
||||||
assert!(link.success);
|
if !result_ln.succeeded() {
|
||||||
|
scene.ccmd("ln").arg(SUB_FILE).arg(SUB_LINK).succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
let result = ts.ucmd().arg(SUB_DIR_LINKS).run();
|
let result = scene.ucmd().arg(SUB_DIR_LINKS).succeeds();
|
||||||
assert!(result.success);
|
|
||||||
assert_eq!(result.stderr, "");
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
let result_reference = scene.cmd("du").arg(SUB_DIR_LINKS).run();
|
||||||
|
if result_reference.succeeded() {
|
||||||
|
assert_eq!(result.stdout_str(), result_reference.stdout_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
// We do not double count hard links as the inodes are identical
|
// We do not double count hard links as the inodes are identical
|
||||||
_du_hard_link(result.stdout_str());
|
_du_hard_link(result.stdout_str());
|
||||||
}
|
}
|
||||||
|
@ -125,7 +144,7 @@ fn _du_hard_link(s: &str) {
|
||||||
#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))]
|
#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))]
|
||||||
fn _du_hard_link(s: &str) {
|
fn _du_hard_link(s: &str) {
|
||||||
// MS-WSL linux has altered expected output
|
// MS-WSL linux has altered expected output
|
||||||
if !is_wsl() {
|
if !uucore::os::is_wsl_1() {
|
||||||
assert_eq!(s, "16\tsubdir/links\n");
|
assert_eq!(s, "16\tsubdir/links\n");
|
||||||
} else {
|
} else {
|
||||||
assert_eq!(s, "8\tsubdir/links\n");
|
assert_eq!(s, "8\tsubdir/links\n");
|
||||||
|
@ -134,11 +153,23 @@ fn _du_hard_link(s: &str) {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_du_d_flag() {
|
fn test_du_d_flag() {
|
||||||
let ts = TestScenario::new("du");
|
let scene = TestScenario::new(util_name!());
|
||||||
|
|
||||||
let result = ts.ucmd().arg("-d").arg("1").run();
|
let result = scene.ucmd().arg("-d1").succeeds();
|
||||||
assert!(result.success);
|
|
||||||
assert_eq!(result.stderr, "");
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
let result_reference = scene.cmd("du").arg("-d1").run();
|
||||||
|
if result_reference.succeeded() {
|
||||||
|
assert_eq!(
|
||||||
|
// TODO: gnu `du` doesn't use trailing "/" here
|
||||||
|
// result.stdout_str(), result_reference.stdout_str()
|
||||||
|
result.stdout_str().trim_end_matches("/\n"),
|
||||||
|
result_reference.stdout_str().trim_end_matches("\n")
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
_du_d_flag(result.stdout_str());
|
_du_d_flag(result.stdout_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,7 +184,7 @@ fn _du_d_flag(s: &str) {
|
||||||
#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))]
|
#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))]
|
||||||
fn _du_d_flag(s: &str) {
|
fn _du_d_flag(s: &str) {
|
||||||
// MS-WSL linux has altered expected output
|
// MS-WSL linux has altered expected output
|
||||||
if !is_wsl() {
|
if !uucore::os::is_wsl_1() {
|
||||||
assert_eq!(s, "28\t./subdir\n36\t./\n");
|
assert_eq!(s, "28\t./subdir\n36\t./\n");
|
||||||
} else {
|
} else {
|
||||||
assert_eq!(s, "8\t./subdir\n8\t./\n");
|
assert_eq!(s, "8\t./subdir\n8\t./\n");
|
||||||
|
@ -162,9 +193,7 @@ fn _du_d_flag(s: &str) {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_du_h_flag_empty_file() {
|
fn test_du_h_flag_empty_file() {
|
||||||
let ts = TestScenario::new("du");
|
new_ucmd!()
|
||||||
|
|
||||||
ts.ucmd()
|
|
||||||
.arg("-h")
|
.arg("-h")
|
||||||
.arg("empty.txt")
|
.arg("empty.txt")
|
||||||
.succeeds()
|
.succeeds()
|
||||||
|
@ -174,54 +203,61 @@ fn test_du_h_flag_empty_file() {
|
||||||
#[cfg(feature = "touch")]
|
#[cfg(feature = "touch")]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_du_time() {
|
fn test_du_time() {
|
||||||
let ts = TestScenario::new("du");
|
let scene = TestScenario::new(util_name!());
|
||||||
|
|
||||||
let touch = ts
|
scene
|
||||||
.ccmd("touch")
|
.ccmd("touch")
|
||||||
.arg("-a")
|
.arg("-a")
|
||||||
.arg("-m")
|
.arg("-m")
|
||||||
.arg("-t")
|
.arg("-t")
|
||||||
.arg("201505150000")
|
.arg("201505150000")
|
||||||
.arg("date_test")
|
.arg("date_test")
|
||||||
.run();
|
.succeeds();
|
||||||
assert!(touch.success);
|
|
||||||
|
|
||||||
let result = ts.ucmd().arg("--time").arg("date_test").run();
|
scene
|
||||||
|
.ucmd()
|
||||||
// cleanup by removing test file
|
.arg("--time")
|
||||||
ts.cmd("rm").arg("date_test").run();
|
.arg("date_test")
|
||||||
|
.succeeds()
|
||||||
assert!(result.success);
|
.stdout_only("0\t2015-05-15 00:00\tdate_test\n");
|
||||||
assert_eq!(result.stderr, "");
|
|
||||||
assert_eq!(result.stdout, "0\t2015-05-15 00:00\tdate_test\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
#[cfg(feature = "chmod")]
|
#[cfg(feature = "chmod")]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_du_no_permission() {
|
fn test_du_no_permission() {
|
||||||
let ts = TestScenario::new("du");
|
let scene = TestScenario::new(util_name!());
|
||||||
|
let at = &scene.fixtures;
|
||||||
|
|
||||||
let chmod = ts.ccmd("chmod").arg("-r").arg(SUB_DIR_LINKS).run();
|
at.mkdir_all(SUB_DIR_LINKS);
|
||||||
println!("chmod output: {:?}", chmod);
|
|
||||||
assert!(chmod.success);
|
|
||||||
let result = ts.ucmd().arg(SUB_DIR_LINKS).run();
|
|
||||||
|
|
||||||
ts.ccmd("chmod").arg("+r").arg(SUB_DIR_LINKS).run();
|
scene.ccmd("chmod").arg("-r").arg(SUB_DIR_LINKS).succeeds();
|
||||||
|
|
||||||
assert!(result.success);
|
let result = scene.ucmd().arg(SUB_DIR_LINKS).run(); // TODO: replace with ".fails()" once `du` is fixed
|
||||||
assert_eq!(
|
result.stderr_contains(
|
||||||
result.stderr,
|
"du: cannot read directory ‘subdir/links‘: Permission denied (os error 13)",
|
||||||
"du: cannot read directory ‘subdir/links‘: Permission denied (os error 13)\n"
|
|
||||||
);
|
);
|
||||||
_du_no_permission(result.stdout);
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
let result_reference = scene.cmd("du").arg(SUB_DIR_LINKS).fails();
|
||||||
|
if result_reference
|
||||||
|
.stderr_str()
|
||||||
|
.contains("du: cannot read directory 'subdir/links': Permission denied")
|
||||||
|
{
|
||||||
|
assert_eq!(result.stdout_str(), result_reference.stdout_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_du_no_permission(result.stdout_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_vendor = "apple")]
|
#[cfg(target_vendor = "apple")]
|
||||||
fn _du_no_permission(s: String) {
|
fn _du_no_permission(s: &str) {
|
||||||
assert_eq!(s, "0\tsubdir/links\n");
|
assert_eq!(s, "0\tsubdir/links\n");
|
||||||
}
|
}
|
||||||
#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))]
|
#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))]
|
||||||
fn _du_no_permission(s: String) {
|
fn _du_no_permission(s: &str) {
|
||||||
assert_eq!(s, "4\tsubdir/links\n");
|
assert_eq!(s, "4\tsubdir/links\n");
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,8 +140,11 @@ fn test_unset_variable() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_fail_null_with_program() {
|
fn test_fail_null_with_program() {
|
||||||
let out = new_ucmd!().arg("--null").arg("cd").fails().stderr;
|
new_ucmd!()
|
||||||
assert!(out.contains("cannot specify --null (-0) with command"));
|
.arg("--null")
|
||||||
|
.arg("cd")
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("cannot specify --null (-0) with command");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
|
|
|
@ -29,7 +29,7 @@ fn test_fmt_w_too_big() {
|
||||||
.run();
|
.run();
|
||||||
//.stdout_is_fixture("call_graph.expected");
|
//.stdout_is_fixture("call_graph.expected");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.stderr.trim(),
|
result.stderr_str().trim(),
|
||||||
"fmt: error: invalid width: '2501': Numerical result out of range"
|
"fmt: error: invalid width: '2501': Numerical result out of range"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ fn test_groups() {
|
||||||
// As seems to be a configuration issue, ignoring it
|
// As seems to be a configuration issue, ignoring it
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
assert!(result.success);
|
result.success();
|
||||||
assert!(!result.stdout_str().trim().is_empty());
|
assert!(!result.stdout_str().trim().is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,16 +30,12 @@ fn test_groups_arg() {
|
||||||
|
|
||||||
println!("result.stdout = {}", result.stdout_str());
|
println!("result.stdout = {}", result.stdout_str());
|
||||||
println!("result.stderr = {}", result.stderr_str());
|
println!("result.stderr = {}", result.stderr_str());
|
||||||
assert!(result.success);
|
result.success();
|
||||||
assert!(!result.stdout_str().is_empty());
|
assert!(!result.stdout_str().is_empty());
|
||||||
let username = result.stdout_str().trim();
|
let username = result.stdout_str().trim();
|
||||||
|
|
||||||
// call groups with the user name to check that we
|
// call groups with the user name to check that we
|
||||||
// are getting something
|
// are getting something
|
||||||
let (_, mut ucmd) = at_and_ucmd!();
|
new_ucmd!().arg(username).succeeds();
|
||||||
let result = ucmd.arg(username).run();
|
|
||||||
println!("result.stdout = {}", result.stdout_str());
|
|
||||||
println!("result.stderr = {}", result.stderr_str());
|
|
||||||
assert!(result.success);
|
|
||||||
assert!(!result.stdout_str().is_empty());
|
assert!(!result.stdout_str().is_empty());
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,14 +156,10 @@ fn test_negative_zero_bytes() {
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_no_such_file_or_directory() {
|
fn test_no_such_file_or_directory() {
|
||||||
let result = new_ucmd!().arg("no_such_file.toml").run();
|
new_ucmd!()
|
||||||
|
.arg("no_such_file.toml")
|
||||||
assert_eq!(
|
.fails()
|
||||||
true,
|
.stderr_contains("cannot open 'no_such_file.toml' for reading: No such file or directory");
|
||||||
result
|
|
||||||
.stderr
|
|
||||||
.contains("cannot open 'no_such_file.toml' for reading: No such file or directory")
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// there was a bug not caught by previous tests
|
// there was a bug not caught by previous tests
|
||||||
|
|
|
@ -11,12 +11,10 @@ use std::thread::sleep;
|
||||||
fn test_install_help() {
|
fn test_install_help() {
|
||||||
let (_, mut ucmd) = at_and_ucmd!();
|
let (_, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
assert!(ucmd
|
ucmd.arg("--help")
|
||||||
.arg("--help")
|
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.no_stderr()
|
.no_stderr()
|
||||||
.stdout
|
.stdout_contains("FLAGS:");
|
||||||
.contains("FLAGS:"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -59,13 +57,11 @@ fn test_install_failing_not_dir() {
|
||||||
at.touch(file1);
|
at.touch(file1);
|
||||||
at.touch(file2);
|
at.touch(file2);
|
||||||
at.touch(file3);
|
at.touch(file3);
|
||||||
assert!(ucmd
|
ucmd.arg(file1)
|
||||||
.arg(file1)
|
|
||||||
.arg(file2)
|
.arg(file2)
|
||||||
.arg(file3)
|
.arg(file3)
|
||||||
.fails()
|
.fails()
|
||||||
.stderr
|
.stderr_contains("not a directory");
|
||||||
.contains("not a directory"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -77,13 +73,11 @@ fn test_install_unimplemented_arg() {
|
||||||
|
|
||||||
at.touch(file);
|
at.touch(file);
|
||||||
at.mkdir(dir);
|
at.mkdir(dir);
|
||||||
assert!(ucmd
|
ucmd.arg(context_arg)
|
||||||
.arg(context_arg)
|
|
||||||
.arg(file)
|
.arg(file)
|
||||||
.arg(dir)
|
.arg(dir)
|
||||||
.fails()
|
.fails()
|
||||||
.stderr
|
.stderr_contains("Unimplemented");
|
||||||
.contains("Unimplemented"));
|
|
||||||
|
|
||||||
assert!(!at.file_exists(&format!("{}/{}", dir, file)));
|
assert!(!at.file_exists(&format!("{}/{}", dir, file)));
|
||||||
}
|
}
|
||||||
|
@ -231,13 +225,11 @@ fn test_install_mode_failing() {
|
||||||
|
|
||||||
at.touch(file);
|
at.touch(file);
|
||||||
at.mkdir(dir);
|
at.mkdir(dir);
|
||||||
assert!(ucmd
|
ucmd.arg(file)
|
||||||
.arg(file)
|
|
||||||
.arg(dir)
|
.arg(dir)
|
||||||
.arg(mode_arg)
|
.arg(mode_arg)
|
||||||
.fails()
|
.fails()
|
||||||
.stderr
|
.stderr_contains("Invalid mode string: invalid digit found in string");
|
||||||
.contains("Invalid mode string: invalid digit found in string"));
|
|
||||||
|
|
||||||
let dest_file = &format!("{}/{}", dir, file);
|
let dest_file = &format!("{}/{}", dir, file);
|
||||||
assert!(at.file_exists(file));
|
assert!(at.file_exists(file));
|
||||||
|
@ -336,7 +328,7 @@ fn test_install_target_new_file_with_owner() {
|
||||||
.arg(format!("{}/{}", dir, file))
|
.arg(format!("{}/{}", dir, file))
|
||||||
.run();
|
.run();
|
||||||
|
|
||||||
if is_ci() && result.stderr.contains("error: no such user:") {
|
if is_ci() && result.stderr_str().contains("error: no such user:") {
|
||||||
// In the CI, some server are failing to return the user id.
|
// In the CI, some server are failing to return the user id.
|
||||||
// As seems to be a configuration issue, ignoring it
|
// As seems to be a configuration issue, ignoring it
|
||||||
return;
|
return;
|
||||||
|
@ -359,7 +351,7 @@ fn test_install_target_new_file_failing_nonexistent_parent() {
|
||||||
ucmd.arg(file1)
|
ucmd.arg(file1)
|
||||||
.arg(format!("{}/{}", dir, file2))
|
.arg(format!("{}/{}", dir, file2))
|
||||||
.fails()
|
.fails()
|
||||||
.stderr_contains(&"not a directory");
|
.stderr_contains(&"No such file or directory");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -619,33 +611,64 @@ fn test_install_and_strip_with_program() {
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
fn test_install_and_strip_with_invalid_program() {
|
fn test_install_and_strip_with_invalid_program() {
|
||||||
let scene = TestScenario::new(util_name!());
|
new_ucmd!()
|
||||||
|
|
||||||
let stderr = scene
|
|
||||||
.ucmd()
|
|
||||||
.arg("-s")
|
.arg("-s")
|
||||||
.arg("--strip-program")
|
.arg("--strip-program")
|
||||||
.arg("/bin/date")
|
.arg("/bin/date")
|
||||||
.arg(strip_source_file())
|
.arg(strip_source_file())
|
||||||
.arg(STRIP_TARGET_FILE)
|
.arg(STRIP_TARGET_FILE)
|
||||||
.fails()
|
.fails()
|
||||||
.stderr;
|
.stderr_contains("strip program failed");
|
||||||
assert!(stderr.contains("strip program failed"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
fn test_install_and_strip_with_non_existent_program() {
|
fn test_install_and_strip_with_non_existent_program() {
|
||||||
let scene = TestScenario::new(util_name!());
|
new_ucmd!()
|
||||||
|
|
||||||
let stderr = scene
|
|
||||||
.ucmd()
|
|
||||||
.arg("-s")
|
.arg("-s")
|
||||||
.arg("--strip-program")
|
.arg("--strip-program")
|
||||||
.arg("/usr/bin/non_existent_program")
|
.arg("/usr/bin/non_existent_program")
|
||||||
.arg(strip_source_file())
|
.arg(strip_source_file())
|
||||||
.arg(STRIP_TARGET_FILE)
|
.arg(STRIP_TARGET_FILE)
|
||||||
.fails()
|
.fails()
|
||||||
.stderr;
|
.stderr_contains("No such file or directory");
|
||||||
assert!(stderr.contains("No such file or directory"));
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_install_creating_leading_dirs() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
let at = &scene.fixtures;
|
||||||
|
|
||||||
|
let source = "create_leading_test_file";
|
||||||
|
let target = "dir1/dir2/dir3/test_file";
|
||||||
|
|
||||||
|
at.touch(source);
|
||||||
|
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-D")
|
||||||
|
.arg(source)
|
||||||
|
.arg(at.plus(target))
|
||||||
|
.succeeds()
|
||||||
|
.no_stderr();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
fn test_install_creating_leading_dir_fails_on_long_name() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
let at = &scene.fixtures;
|
||||||
|
|
||||||
|
let source = "create_leading_test_file";
|
||||||
|
let target = format!("{}/test_file", "d".repeat(libc::PATH_MAX as usize + 1));
|
||||||
|
|
||||||
|
at.touch(source);
|
||||||
|
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-D")
|
||||||
|
.arg(source)
|
||||||
|
.arg(at.plus(target.as_str()))
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("failed to create");
|
||||||
}
|
}
|
||||||
|
|
|
@ -299,13 +299,11 @@ fn test_symlink_overwrite_dir_fail() {
|
||||||
at.touch(path_a);
|
at.touch(path_a);
|
||||||
at.mkdir(path_b);
|
at.mkdir(path_b);
|
||||||
|
|
||||||
assert!(
|
assert!(!ucmd
|
||||||
ucmd.args(&["-s", "-T", path_a, path_b])
|
.args(&["-s", "-T", path_a, path_b])
|
||||||
.fails()
|
.fails()
|
||||||
.stderr
|
.stderr_str()
|
||||||
.len()
|
.is_empty());
|
||||||
> 0
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -358,7 +356,11 @@ fn test_symlink_target_only() {
|
||||||
|
|
||||||
at.mkdir(dir);
|
at.mkdir(dir);
|
||||||
|
|
||||||
assert!(ucmd.args(&["-s", "-t", dir]).fails().stderr.len() > 0);
|
assert!(!ucmd
|
||||||
|
.args(&["-s", "-t", dir])
|
||||||
|
.fails()
|
||||||
|
.stderr_str()
|
||||||
|
.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -9,7 +9,7 @@ fn test_normal() {
|
||||||
for (key, value) in env::vars() {
|
for (key, value) in env::vars() {
|
||||||
println!("{}: {}", key, value);
|
println!("{}: {}", key, value);
|
||||||
}
|
}
|
||||||
if (is_ci() || is_wsl()) && result.stderr_str().contains("error: no login name") {
|
if (is_ci() || uucore::os::is_wsl_1()) && result.stderr_str().contains("error: no login name") {
|
||||||
// ToDO: investigate WSL failure
|
// ToDO: investigate WSL failure
|
||||||
// In the CI, some server are failing to return logname.
|
// In the CI, some server are failing to return logname.
|
||||||
// As seems to be a configuration issue, ignoring it
|
// As seems to be a configuration issue, ignoring it
|
||||||
|
|
|
@ -103,6 +103,20 @@ fn test_ls_width() {
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_only("test-width-1\ntest-width-2\ntest-width-3\ntest-width-4\n");
|
.stdout_only("test-width-1\ntest-width-2\ntest-width-3\ntest-width-4\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-w=bad")
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("invalid line width");
|
||||||
|
|
||||||
|
for option in &["-w 1a", "-w=1a", "--width=1a", "--width 1a"] {
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.args(&option.split(" ").collect::<Vec<_>>())
|
||||||
|
.fails()
|
||||||
|
.stderr_only("ls: error: invalid line width: ‘1a’");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -436,6 +450,39 @@ fn test_ls_deref() {
|
||||||
assert!(!re.is_match(result.stdout_str().trim()));
|
assert!(!re.is_match(result.stdout_str().trim()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ls_sort_none() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
let at = &scene.fixtures;
|
||||||
|
|
||||||
|
at.touch("test-3");
|
||||||
|
at.touch("test-1");
|
||||||
|
at.touch("test-2");
|
||||||
|
|
||||||
|
// Order is not specified so we just check that it doesn't
|
||||||
|
// give any errors.
|
||||||
|
scene.ucmd().arg("--sort=none").succeeds();
|
||||||
|
scene.ucmd().arg("-U").succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ls_sort_name() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
let at = &scene.fixtures;
|
||||||
|
|
||||||
|
at.touch("test-3");
|
||||||
|
at.touch("test-1");
|
||||||
|
at.touch("test-2");
|
||||||
|
|
||||||
|
let sep = if cfg!(unix) { "\n" } else { " " };
|
||||||
|
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("--sort=name")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_is(["test-1", "test-2", "test-3\n"].join(sep));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_ls_order_size() {
|
fn test_ls_order_size() {
|
||||||
let scene = TestScenario::new(util_name!());
|
let scene = TestScenario::new(util_name!());
|
||||||
|
@ -464,6 +511,18 @@ fn test_ls_order_size() {
|
||||||
result.stdout_only("test-1\ntest-2\ntest-3\ntest-4\n");
|
result.stdout_only("test-1\ntest-2\ntest-3\ntest-4\n");
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
result.stdout_only("test-1 test-2 test-3 test-4\n");
|
result.stdout_only("test-1 test-2 test-3 test-4\n");
|
||||||
|
|
||||||
|
let result = scene.ucmd().arg("--sort=size").succeeds();
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n");
|
||||||
|
#[cfg(windows)]
|
||||||
|
result.stdout_only("test-4 test-3 test-2 test-1\n");
|
||||||
|
|
||||||
|
let result = scene.ucmd().arg("--sort=size").arg("-r").succeeds();
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
result.stdout_only("test-1\ntest-2\ntest-3\ntest-4\n");
|
||||||
|
#[cfg(windows)]
|
||||||
|
result.stdout_only("test-1 test-2 test-3 test-4\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -472,13 +531,16 @@ fn test_ls_long_ctime() {
|
||||||
let at = &scene.fixtures;
|
let at = &scene.fixtures;
|
||||||
|
|
||||||
at.touch("test-long-ctime-1");
|
at.touch("test-long-ctime-1");
|
||||||
let result = scene.ucmd().arg("-lc").succeeds();
|
|
||||||
|
for arg in &["-c", "--time=ctime", "--time=status"] {
|
||||||
|
let result = scene.ucmd().arg("-l").arg(arg).succeeds();
|
||||||
|
|
||||||
// Should show the time on Unix, but question marks on windows.
|
// Should show the time on Unix, but question marks on windows.
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
result.stdout_contains(":");
|
result.stdout_contains(":");
|
||||||
#[cfg(not(unix))]
|
#[cfg(not(unix))]
|
||||||
result.stdout_contains("???");
|
result.stdout_contains("???");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -519,15 +581,28 @@ fn test_ls_order_time() {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
result.stdout_only("test-4 test-3 test-2 test-1\n");
|
result.stdout_only("test-4 test-3 test-2 test-1\n");
|
||||||
|
|
||||||
|
let result = scene.ucmd().arg("--sort=time").succeeds();
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n");
|
||||||
|
#[cfg(windows)]
|
||||||
|
result.stdout_only("test-4 test-3 test-2 test-1\n");
|
||||||
|
|
||||||
let result = scene.ucmd().arg("-tr").succeeds();
|
let result = scene.ucmd().arg("-tr").succeeds();
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
result.stdout_only("test-1\ntest-2\ntest-3\ntest-4\n");
|
result.stdout_only("test-1\ntest-2\ntest-3\ntest-4\n");
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
result.stdout_only("test-1 test-2 test-3 test-4\n");
|
result.stdout_only("test-1 test-2 test-3 test-4\n");
|
||||||
|
|
||||||
|
let result = scene.ucmd().arg("--sort=time").arg("-r").succeeds();
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
result.stdout_only("test-1\ntest-2\ntest-3\ntest-4\n");
|
||||||
|
#[cfg(windows)]
|
||||||
|
result.stdout_only("test-1 test-2 test-3 test-4\n");
|
||||||
|
|
||||||
// 3 was accessed last in the read
|
// 3 was accessed last in the read
|
||||||
// So the order should be 2 3 4 1
|
// So the order should be 2 3 4 1
|
||||||
let result = scene.ucmd().arg("-tu").succeeds();
|
for arg in &["-u", "--time=atime", "--time=access", "--time=use"] {
|
||||||
|
let result = scene.ucmd().arg("-t").arg(arg).succeeds();
|
||||||
let file3_access = at.open("test-3").metadata().unwrap().accessed().unwrap();
|
let file3_access = at.open("test-3").metadata().unwrap().accessed().unwrap();
|
||||||
let file4_access = at.open("test-4").metadata().unwrap().accessed().unwrap();
|
let file4_access = at.open("test-4").metadata().unwrap().accessed().unwrap();
|
||||||
|
|
||||||
|
@ -547,6 +622,7 @@ fn test_ls_order_time() {
|
||||||
result.stdout_only("test-4 test-3 test-2 test-1\n");
|
result.stdout_only("test-4 test-3 test-2 test-1\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// test-2 had the last ctime change when the permissions were set
|
// test-2 had the last ctime change when the permissions were set
|
||||||
// So the order should be 2 4 3 1
|
// So the order should be 2 4 3 1
|
||||||
|
@ -621,20 +697,27 @@ fn test_ls_recursive() {
|
||||||
result.stdout_contains(&"a\\b:\nb");
|
result.stdout_contains(&"a\\b:\nb");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_ls_ls_color() {
|
fn test_ls_color() {
|
||||||
let scene = TestScenario::new(util_name!());
|
let scene = TestScenario::new(util_name!());
|
||||||
let at = &scene.fixtures;
|
let at = &scene.fixtures;
|
||||||
at.mkdir("a");
|
at.mkdir("a");
|
||||||
at.mkdir("a/nested_dir");
|
let nested_dir = Path::new("a")
|
||||||
|
.join("nested_dir")
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string();
|
||||||
|
at.mkdir(&nested_dir);
|
||||||
at.mkdir("z");
|
at.mkdir("z");
|
||||||
at.touch(&at.plus_as_string("a/nested_file"));
|
let nested_file = Path::new("a")
|
||||||
|
.join("nested_file")
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string();
|
||||||
|
at.touch(&nested_file);
|
||||||
at.touch("test-color");
|
at.touch("test-color");
|
||||||
|
|
||||||
let a_with_colors = "\x1b[01;34ma\x1b[0m";
|
let a_with_colors = "\x1b[1;34ma\x1b[0m";
|
||||||
let z_with_colors = "\x1b[01;34mz\x1b[0m";
|
let z_with_colors = "\x1b[1;34mz\x1b[0m";
|
||||||
let nested_dir_with_colors = "\x1b[01;34mnested_dir\x1b[0m";
|
let nested_dir_with_colors = "\x1b[1;34mnested_dir\x1b[0m";
|
||||||
|
|
||||||
// Color is disabled by default
|
// Color is disabled by default
|
||||||
let result = scene.ucmd().succeeds();
|
let result = scene.ucmd().succeeds();
|
||||||
|
@ -670,14 +753,6 @@ fn test_ls_ls_color() {
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_contains(nested_dir_with_colors);
|
.stdout_contains(nested_dir_with_colors);
|
||||||
|
|
||||||
// Color has no effect
|
|
||||||
scene
|
|
||||||
.ucmd()
|
|
||||||
.arg("--color=always")
|
|
||||||
.arg("a/nested_file")
|
|
||||||
.succeeds()
|
|
||||||
.stdout_contains("a/nested_file\n");
|
|
||||||
|
|
||||||
// No output
|
// No output
|
||||||
scene
|
scene
|
||||||
.ucmd()
|
.ucmd()
|
||||||
|
@ -817,7 +892,7 @@ fn test_ls_indicator_style() {
|
||||||
let options = vec!["classify", "file-type", "slash"];
|
let options = vec!["classify", "file-type", "slash"];
|
||||||
for opt in options {
|
for opt in options {
|
||||||
// Verify that classify and file-type both contain indicators for symlinks.
|
// Verify that classify and file-type both contain indicators for symlinks.
|
||||||
let result = scene
|
scene
|
||||||
.ucmd()
|
.ucmd()
|
||||||
.arg(format!("--indicator-style={}", opt))
|
.arg(format!("--indicator-style={}", opt))
|
||||||
.succeeds()
|
.succeeds()
|
||||||
|
@ -827,7 +902,7 @@ fn test_ls_indicator_style() {
|
||||||
// Same test as above, but with the alternate flags.
|
// Same test as above, but with the alternate flags.
|
||||||
let options = vec!["--classify", "--file-type", "-p"];
|
let options = vec!["--classify", "--file-type", "-p"];
|
||||||
for opt in options {
|
for opt in options {
|
||||||
let result = scene
|
scene
|
||||||
.ucmd()
|
.ucmd()
|
||||||
.arg(format!("{}", opt))
|
.arg(format!("{}", opt))
|
||||||
.succeeds()
|
.succeeds()
|
||||||
|
@ -838,7 +913,7 @@ fn test_ls_indicator_style() {
|
||||||
let options = vec!["classify", "file-type"];
|
let options = vec!["classify", "file-type"];
|
||||||
for opt in options {
|
for opt in options {
|
||||||
// Verify that classify and file-type both contain indicators for symlinks.
|
// Verify that classify and file-type both contain indicators for symlinks.
|
||||||
let result = scene
|
scene
|
||||||
.ucmd()
|
.ucmd()
|
||||||
.arg(format!("--indicator-style={}", opt))
|
.arg(format!("--indicator-style={}", opt))
|
||||||
.succeeds()
|
.succeeds()
|
||||||
|
@ -962,7 +1037,7 @@ fn test_ls_hidden_windows() {
|
||||||
|
|
||||||
let result = scene.ucmd().succeeds();
|
let result = scene.ucmd().succeeds();
|
||||||
assert!(!result.stdout_str().contains(file));
|
assert!(!result.stdout_str().contains(file));
|
||||||
let result = scene.ucmd().arg("-a").succeeds().stdout_contains(file);
|
scene.ucmd().arg("-a").succeeds().stdout_contains(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1052,9 +1127,11 @@ fn test_ls_quoting_style() {
|
||||||
at.touch("one");
|
at.touch("one");
|
||||||
|
|
||||||
// It seems that windows doesn't allow \n in filenames.
|
// It seems that windows doesn't allow \n in filenames.
|
||||||
|
// And it also doesn't like \, of course.
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
at.touch("one\ntwo");
|
at.touch("one\ntwo");
|
||||||
|
at.touch("one\\two");
|
||||||
// Default is shell-escape
|
// Default is shell-escape
|
||||||
scene
|
scene
|
||||||
.ucmd()
|
.ucmd()
|
||||||
|
@ -1116,6 +1193,42 @@ fn test_ls_quoting_style() {
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_only(format!("{}\n", correct));
|
.stdout_only(format!("{}\n", correct));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (arg, correct) in &[
|
||||||
|
("--quoting-style=literal", "one\\two"),
|
||||||
|
("-N", "one\\two"),
|
||||||
|
("--quoting-style=c", "\"one\\\\two\""),
|
||||||
|
("-Q", "\"one\\\\two\""),
|
||||||
|
("--quote-name", "\"one\\\\two\""),
|
||||||
|
("--quoting-style=escape", "one\\\\two"),
|
||||||
|
("-b", "one\\\\two"),
|
||||||
|
("--quoting-style=shell-escape", "'one\\two'"),
|
||||||
|
("--quoting-style=shell-escape-always", "'one\\two'"),
|
||||||
|
("--quoting-style=shell", "'one\\two'"),
|
||||||
|
("--quoting-style=shell-always", "'one\\two'"),
|
||||||
|
] {
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg(arg)
|
||||||
|
.arg("one\\two")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only(format!("{}\n", correct));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests for a character that forces quotation in shell-style escaping
|
||||||
|
// after a character in a dollar expression
|
||||||
|
at.touch("one\n&two");
|
||||||
|
for (arg, correct) in &[
|
||||||
|
("--quoting-style=shell-escape", "'one'$'\\n''&two'"),
|
||||||
|
("--quoting-style=shell-escape-always", "'one'$'\\n''&two'"),
|
||||||
|
] {
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg(arg)
|
||||||
|
.arg("one\n&two")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only(format!("{}\n", correct));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scene
|
scene
|
||||||
|
@ -1316,6 +1429,43 @@ fn test_ls_ignore_hide() {
|
||||||
.stdout_is("CONTRIBUTING.md\nREADME.md\nREADMECAREFULLY.md\nsome_other_file\n");
|
.stdout_is("CONTRIBUTING.md\nREADME.md\nREADMECAREFULLY.md\nsome_other_file\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ls_ignore_backups() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
let at = &scene.fixtures;
|
||||||
|
|
||||||
|
at.touch("somefile");
|
||||||
|
at.touch("somebackup~");
|
||||||
|
at.touch(".somehiddenfile");
|
||||||
|
at.touch(".somehiddenbackup~");
|
||||||
|
|
||||||
|
scene.ucmd().arg("-B").succeeds().stdout_is("somefile\n");
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("--ignore-backups")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_is("somefile\n");
|
||||||
|
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-aB")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_contains(".somehiddenfile")
|
||||||
|
.stdout_contains("somefile")
|
||||||
|
.stdout_does_not_contain("somebackup")
|
||||||
|
.stdout_does_not_contain(".somehiddenbackup~");
|
||||||
|
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-a")
|
||||||
|
.arg("--ignore-backups")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_contains(".somehiddenfile")
|
||||||
|
.stdout_contains("somefile")
|
||||||
|
.stdout_does_not_contain("somebackup")
|
||||||
|
.stdout_does_not_contain(".somehiddenbackup~");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_ls_directory() {
|
fn test_ls_directory() {
|
||||||
let scene = TestScenario::new(util_name!());
|
let scene = TestScenario::new(util_name!());
|
||||||
|
|
|
@ -19,8 +19,7 @@ fn test_create_one_fifo_with_invalid_mode() {
|
||||||
.arg("-m")
|
.arg("-m")
|
||||||
.arg("invalid")
|
.arg("invalid")
|
||||||
.fails()
|
.fails()
|
||||||
.stderr
|
.stderr_contains("invalid mode");
|
||||||
.contains("invalid mode");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -2,18 +2,15 @@ use crate::common::util::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_more_no_arg() {
|
fn test_more_no_arg() {
|
||||||
let (_, mut ucmd) = at_and_ucmd!();
|
// stderr = more: Reading from stdin isn't supported yet.
|
||||||
let result = ucmd.run();
|
new_ucmd!().fails();
|
||||||
assert!(!result.success);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_more_dir_arg() {
|
fn test_more_dir_arg() {
|
||||||
let (_, mut ucmd) = at_and_ucmd!();
|
let result = new_ucmd!().arg(".").run();
|
||||||
ucmd.arg(".");
|
result.failure();
|
||||||
let result = ucmd.run();
|
|
||||||
assert!(!result.success);
|
|
||||||
const EXPECTED_ERROR_MESSAGE: &str =
|
const EXPECTED_ERROR_MESSAGE: &str =
|
||||||
"more: '.' is a directory.\nTry 'more --help' for more information.";
|
"more: '.' is a directory.\nTry 'more --help' for more information.";
|
||||||
assert_eq!(result.stderr.trim(), EXPECTED_ERROR_MESSAGE);
|
assert_eq!(result.stderr_str().trim(), EXPECTED_ERROR_MESSAGE);
|
||||||
}
|
}
|
||||||
|
|
|
@ -476,16 +476,9 @@ fn test_mv_overwrite_nonempty_dir() {
|
||||||
// GNU: "mv: cannot move ‘a’ to ‘b’: Directory not empty"
|
// GNU: "mv: cannot move ‘a’ to ‘b’: Directory not empty"
|
||||||
|
|
||||||
// Verbose output for the move should not be shown on failure
|
// Verbose output for the move should not be shown on failure
|
||||||
assert!(
|
let result = ucmd.arg("-vT").arg(dir_a).arg(dir_b).fails();
|
||||||
ucmd.arg("-vT")
|
result.no_stdout();
|
||||||
.arg(dir_a)
|
assert!(!result.stderr_str().is_empty());
|
||||||
.arg(dir_b)
|
|
||||||
.fails()
|
|
||||||
.no_stdout()
|
|
||||||
.stderr
|
|
||||||
.len()
|
|
||||||
> 0
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(at.dir_exists(dir_a));
|
assert!(at.dir_exists(dir_a));
|
||||||
assert!(at.dir_exists(dir_b));
|
assert!(at.dir_exists(dir_b));
|
||||||
|
@ -526,15 +519,15 @@ fn test_mv_errors() {
|
||||||
|
|
||||||
// $ mv -T -t a b
|
// $ mv -T -t a b
|
||||||
// mv: cannot combine --target-directory (-t) and --no-target-directory (-T)
|
// mv: cannot combine --target-directory (-t) and --no-target-directory (-T)
|
||||||
let result = scene
|
scene
|
||||||
.ucmd()
|
.ucmd()
|
||||||
.arg("-T")
|
.arg("-T")
|
||||||
.arg("-t")
|
.arg("-t")
|
||||||
.arg(dir)
|
.arg(dir)
|
||||||
.arg(file_a)
|
.arg(file_a)
|
||||||
.arg(file_b)
|
.arg(file_b)
|
||||||
.fails();
|
.fails()
|
||||||
assert!(result.stderr.contains("cannot be used with"));
|
.stderr_contains("cannot be used with");
|
||||||
|
|
||||||
// $ at.touch file && at.mkdir dir
|
// $ at.touch file && at.mkdir dir
|
||||||
// $ mv -T file dir
|
// $ mv -T file dir
|
||||||
|
@ -553,7 +546,13 @@ fn test_mv_errors() {
|
||||||
// $ at.mkdir dir && at.touch file
|
// $ at.mkdir dir && at.touch file
|
||||||
// $ mv dir file
|
// $ mv dir file
|
||||||
// err == mv: cannot overwrite non-directory ‘file’ with directory ‘dir’
|
// err == mv: cannot overwrite non-directory ‘file’ with directory ‘dir’
|
||||||
assert!(scene.ucmd().arg(dir).arg(file_a).fails().stderr.len() > 0);
|
assert!(!scene
|
||||||
|
.ucmd()
|
||||||
|
.arg(dir)
|
||||||
|
.arg(file_a)
|
||||||
|
.fails()
|
||||||
|
.stderr_str()
|
||||||
|
.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -16,7 +16,7 @@ fn test_negative_adjustment() {
|
||||||
|
|
||||||
let res = new_ucmd!().args(&["-n", "-1", "true"]).run();
|
let res = new_ucmd!().args(&["-n", "-1", "true"]).run();
|
||||||
assert!(res
|
assert!(res
|
||||||
.stderr
|
.stderr_str()
|
||||||
.starts_with("nice: warning: setpriority: Permission denied"));
|
.starts_with("nice: warning: setpriority: Permission denied"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,35 +9,28 @@ fn test_output_is_random_permutation() {
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join("\n");
|
.join("\n");
|
||||||
|
|
||||||
let result = new_ucmd!()
|
let result = new_ucmd!().pipe_in(input.as_bytes()).succeeds();
|
||||||
.pipe_in(input.as_bytes())
|
result.no_stderr();
|
||||||
.succeeds()
|
|
||||||
.no_stderr()
|
|
||||||
.stdout
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
let mut result_seq: Vec<i32> = result
|
let mut result_seq: Vec<i32> = result
|
||||||
|
.stdout_str()
|
||||||
.split("\n")
|
.split("\n")
|
||||||
.filter(|x| !x.is_empty())
|
.filter(|x| !x.is_empty())
|
||||||
.map(|x| x.parse().unwrap())
|
.map(|x| x.parse().unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
result_seq.sort();
|
result_seq.sort();
|
||||||
assert_ne!(result, input, "Output is not randomised");
|
assert_ne!(result.stdout_str(), input, "Output is not randomised");
|
||||||
assert_eq!(result_seq, input_seq, "Output is not a permutation");
|
assert_eq!(result_seq, input_seq, "Output is not a permutation");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_zero_termination() {
|
fn test_zero_termination() {
|
||||||
let input_seq = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
let input_seq = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||||
let result = new_ucmd!()
|
let result = new_ucmd!().arg("-z").arg("-i1-10").succeeds();
|
||||||
.arg("-z")
|
result.no_stderr();
|
||||||
.arg("-i1-10")
|
|
||||||
.succeeds()
|
|
||||||
.no_stderr()
|
|
||||||
.stdout
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
let mut result_seq: Vec<i32> = result
|
let mut result_seq: Vec<i32> = result
|
||||||
|
.stdout_str()
|
||||||
.split("\0")
|
.split("\0")
|
||||||
.filter(|x| !x.is_empty())
|
.filter(|x| !x.is_empty())
|
||||||
.map(|x| x.parse().unwrap())
|
.map(|x| x.parse().unwrap())
|
||||||
|
@ -57,12 +50,11 @@ fn test_echo() {
|
||||||
.map(|x| x.to_string())
|
.map(|x| x.to_string())
|
||||||
.collect::<Vec<String>>(),
|
.collect::<Vec<String>>(),
|
||||||
)
|
)
|
||||||
.succeeds()
|
.succeeds();
|
||||||
.no_stderr()
|
result.no_stderr();
|
||||||
.stdout
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
let mut result_seq: Vec<i32> = result
|
let mut result_seq: Vec<i32> = result
|
||||||
|
.stdout_str()
|
||||||
.split("\n")
|
.split("\n")
|
||||||
.filter(|x| !x.is_empty())
|
.filter(|x| !x.is_empty())
|
||||||
.map(|x| x.parse().unwrap())
|
.map(|x| x.parse().unwrap())
|
||||||
|
@ -84,12 +76,11 @@ fn test_head_count() {
|
||||||
let result = new_ucmd!()
|
let result = new_ucmd!()
|
||||||
.args(&["-n", &repeat_limit.to_string()])
|
.args(&["-n", &repeat_limit.to_string()])
|
||||||
.pipe_in(input.as_bytes())
|
.pipe_in(input.as_bytes())
|
||||||
.succeeds()
|
.succeeds();
|
||||||
.no_stderr()
|
result.no_stderr();
|
||||||
.stdout
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
let mut result_seq: Vec<i32> = result
|
let mut result_seq: Vec<i32> = result
|
||||||
|
.stdout_str()
|
||||||
.split("\n")
|
.split("\n")
|
||||||
.filter(|x| !x.is_empty())
|
.filter(|x| !x.is_empty())
|
||||||
.map(|x| x.parse().unwrap())
|
.map(|x| x.parse().unwrap())
|
||||||
|
@ -99,7 +90,7 @@ fn test_head_count() {
|
||||||
assert!(
|
assert!(
|
||||||
result_seq.iter().all(|x| input_seq.contains(x)),
|
result_seq.iter().all(|x| input_seq.contains(x)),
|
||||||
"Output includes element not from input: {}",
|
"Output includes element not from input: {}",
|
||||||
result
|
result.stdout_str()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,12 +108,11 @@ fn test_repeat() {
|
||||||
.arg("-r")
|
.arg("-r")
|
||||||
.args(&["-n", &repeat_limit.to_string()])
|
.args(&["-n", &repeat_limit.to_string()])
|
||||||
.pipe_in(input.as_bytes())
|
.pipe_in(input.as_bytes())
|
||||||
.succeeds()
|
.succeeds();
|
||||||
.no_stderr()
|
result.no_stderr();
|
||||||
.stdout
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
let result_seq: Vec<i32> = result
|
let result_seq: Vec<i32> = result
|
||||||
|
.stdout_str()
|
||||||
.split("\n")
|
.split("\n")
|
||||||
.filter(|x| !x.is_empty())
|
.filter(|x| !x.is_empty())
|
||||||
.map(|x| x.parse().unwrap())
|
.map(|x| x.parse().unwrap())
|
||||||
|
@ -146,14 +136,11 @@ fn test_repeat() {
|
||||||
fn test_file_input() {
|
fn test_file_input() {
|
||||||
let expected_seq = vec![11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
|
let expected_seq = vec![11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
|
||||||
|
|
||||||
let result = new_ucmd!()
|
let result = new_ucmd!().arg("file_input.txt").succeeds();
|
||||||
.arg("file_input.txt")
|
result.no_stderr();
|
||||||
.succeeds()
|
|
||||||
.no_stderr()
|
|
||||||
.stdout
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
let mut result_seq: Vec<i32> = result
|
let mut result_seq: Vec<i32> = result
|
||||||
|
.stdout_str()
|
||||||
.split("\n")
|
.split("\n")
|
||||||
.filter(|x| !x.is_empty())
|
.filter(|x| !x.is_empty())
|
||||||
.map(|x| x.parse().unwrap())
|
.map(|x| x.parse().unwrap())
|
||||||
|
@ -164,52 +151,50 @@ fn test_file_input() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_shuf_echo_and_input_range_not_allowed() {
|
fn test_shuf_echo_and_input_range_not_allowed() {
|
||||||
let result = new_ucmd!().args(&["-e", "0", "-i", "0-2"]).run();
|
new_ucmd!()
|
||||||
|
.args(&["-e", "0", "-i", "0-2"])
|
||||||
assert!(!result.success);
|
.fails()
|
||||||
assert!(result
|
.stderr_contains(
|
||||||
.stderr
|
"The argument '--input-range <LO-HI>' cannot be used with '--echo <ARG>...'",
|
||||||
.contains("The argument '--input-range <LO-HI>' cannot be used with '--echo <ARG>...'"));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_shuf_input_range_and_file_not_allowed() {
|
fn test_shuf_input_range_and_file_not_allowed() {
|
||||||
let result = new_ucmd!().args(&["-i", "0-9", "file"]).run();
|
new_ucmd!()
|
||||||
|
.args(&["-i", "0-9", "file"])
|
||||||
assert!(!result.success);
|
.fails()
|
||||||
assert!(result
|
.stderr_contains("The argument '<file>' cannot be used with '--input-range <LO-HI>'");
|
||||||
.stderr
|
|
||||||
.contains("The argument '<file>' cannot be used with '--input-range <LO-HI>'"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_shuf_invalid_input_range_one() {
|
fn test_shuf_invalid_input_range_one() {
|
||||||
let result = new_ucmd!().args(&["-i", "0"]).run();
|
new_ucmd!()
|
||||||
|
.args(&["-i", "0"])
|
||||||
assert!(!result.success);
|
.fails()
|
||||||
assert!(result.stderr.contains("invalid input range"));
|
.stderr_contains("invalid input range");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_shuf_invalid_input_range_two() {
|
fn test_shuf_invalid_input_range_two() {
|
||||||
let result = new_ucmd!().args(&["-i", "a-9"]).run();
|
new_ucmd!()
|
||||||
|
.args(&["-i", "a-9"])
|
||||||
assert!(!result.success);
|
.fails()
|
||||||
assert!(result.stderr.contains("invalid input range: 'a'"));
|
.stderr_contains("invalid input range: 'a'");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_shuf_invalid_input_range_three() {
|
fn test_shuf_invalid_input_range_three() {
|
||||||
let result = new_ucmd!().args(&["-i", "0-b"]).run();
|
new_ucmd!()
|
||||||
|
.args(&["-i", "0-b"])
|
||||||
assert!(!result.success);
|
.fails()
|
||||||
assert!(result.stderr.contains("invalid input range: 'b'"));
|
.stderr_contains("invalid input range: 'b'");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_shuf_invalid_input_line_count() {
|
fn test_shuf_invalid_input_line_count() {
|
||||||
let result = new_ucmd!().args(&["-n", "a"]).run();
|
new_ucmd!()
|
||||||
|
.args(&["-n", "a"])
|
||||||
assert!(!result.success);
|
.fails()
|
||||||
assert!(result.stderr.contains("invalid line count: 'a'"));
|
.stderr_contains("invalid line count: 'a'");
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,17 @@ use crate::common::util::*;
|
||||||
|
|
||||||
fn test_helper(file_name: &str, args: &str) {
|
fn test_helper(file_name: &str, args: &str) {
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.arg(args)
|
|
||||||
.arg(format!("{}.txt", file_name))
|
.arg(format!("{}.txt", file_name))
|
||||||
|
.args(&args.split(' ').collect::<Vec<&str>>())
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is_fixture(format!("{}.expected", file_name));
|
.stdout_is_fixture(format!("{}.expected", file_name));
|
||||||
|
|
||||||
|
new_ucmd!()
|
||||||
|
.arg(format!("{}.txt", file_name))
|
||||||
|
.arg("--debug")
|
||||||
|
.args(&args.split(' ').collect::<Vec<&str>>())
|
||||||
|
.succeeds()
|
||||||
|
.stdout_is_fixture(format!("{}.expected.debug", file_name));
|
||||||
}
|
}
|
||||||
|
|
||||||
// FYI, the initialization size of our Line struct is 96 bytes.
|
// FYI, the initialization size of our Line struct is 96 bytes.
|
||||||
|
@ -65,11 +72,7 @@ fn test_extsort_as64_bailout() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_multiple_decimals_general() {
|
fn test_multiple_decimals_general() {
|
||||||
new_ucmd!()
|
test_helper("multiple_decimals_general", "-g")
|
||||||
.arg("-g")
|
|
||||||
.arg("multiple_decimals_general.txt")
|
|
||||||
.succeeds()
|
|
||||||
.stdout_is("\n\n\n\n\n\n\n\nCARAvan\n-2028789030\n-896689\n-8.90880\n-1\n-.05\n000\n00000001\n1\n1.040000000\n1.444\n1.58590\n8.013\n45\n46.89\n576,446.88800000\n576,446.890\n 4567.\n4567.1\n4567.34\n\t\t\t\t\t\t\t\t\t\t4567..457\n\t\t\t\t37800\n\t\t\t\t\t\t45670.89079.098\n\t\t\t\t\t\t45670.89079.1\n4798908.340000000000\n4798908.45\n4798908.8909800\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -99,7 +102,7 @@ fn test_check_zero_terminated_success() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_random_shuffle_len() {
|
fn test_random_shuffle_len() {
|
||||||
// check whether output is the same length as the input
|
// check whether output is the same length as the input
|
||||||
const FILE: &'static str = "default_unsorted_ints.expected";
|
const FILE: &str = "default_unsorted_ints.expected";
|
||||||
let (at, _ucmd) = at_and_ucmd!();
|
let (at, _ucmd) = at_and_ucmd!();
|
||||||
let result = new_ucmd!().arg("-R").arg(FILE).run().stdout_move_str();
|
let result = new_ucmd!().arg("-R").arg(FILE).run().stdout_move_str();
|
||||||
let expected = at.read(FILE);
|
let expected = at.read(FILE);
|
||||||
|
@ -111,7 +114,7 @@ fn test_random_shuffle_len() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_random_shuffle_contains_all_lines() {
|
fn test_random_shuffle_contains_all_lines() {
|
||||||
// check whether lines of input are all in output
|
// check whether lines of input are all in output
|
||||||
const FILE: &'static str = "default_unsorted_ints.expected";
|
const FILE: &str = "default_unsorted_ints.expected";
|
||||||
let (at, _ucmd) = at_and_ucmd!();
|
let (at, _ucmd) = at_and_ucmd!();
|
||||||
let result = new_ucmd!().arg("-R").arg(FILE).run().stdout_move_str();
|
let result = new_ucmd!().arg("-R").arg(FILE).run().stdout_move_str();
|
||||||
let expected = at.read(FILE);
|
let expected = at.read(FILE);
|
||||||
|
@ -126,7 +129,22 @@ fn test_random_shuffle_two_runs_not_the_same() {
|
||||||
// check to verify that two random shuffles are not equal; this has the
|
// check to verify that two random shuffles are not equal; this has the
|
||||||
// potential to fail in the very unlikely event that the random order is the same
|
// potential to fail in the very unlikely event that the random order is the same
|
||||||
// as the starting order, or if both random sorts end up having the same order.
|
// as the starting order, or if both random sorts end up having the same order.
|
||||||
const FILE: &'static str = "default_unsorted_ints.expected";
|
const FILE: &str = "default_unsorted_ints.expected";
|
||||||
|
let (at, _ucmd) = at_and_ucmd!();
|
||||||
|
let result = new_ucmd!().arg("-R").arg(FILE).run().stdout_move_str();
|
||||||
|
let expected = at.read(FILE);
|
||||||
|
let unexpected = new_ucmd!().arg("-R").arg(FILE).run().stdout_move_str();
|
||||||
|
|
||||||
|
assert_ne!(result, expected);
|
||||||
|
assert_ne!(result, unexpected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_random_shuffle_contains_two_runs_not_the_same() {
|
||||||
|
// check to verify that two random shuffles are not equal; this has the
|
||||||
|
// potential to fail in the unlikely event that random order is the same
|
||||||
|
// as the starting order, or if both random sorts end up having the same order.
|
||||||
|
const FILE: &str = "default_unsorted_ints.expected";
|
||||||
let (at, _ucmd) = at_and_ucmd!();
|
let (at, _ucmd) = at_and_ucmd!();
|
||||||
let result = new_ucmd!().arg("-R").arg(FILE).run().stdout_move_str();
|
let result = new_ucmd!().arg("-R").arg(FILE).run().stdout_move_str();
|
||||||
let expected = at.read(FILE);
|
let expected = at.read(FILE);
|
||||||
|
@ -228,6 +246,11 @@ fn test_non_printing_chars() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_exponents_positive_general_fixed() {
|
||||||
|
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");
|
||||||
|
@ -344,62 +367,32 @@ fn test_numeric_unique_ints2() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_keys_open_ended() {
|
fn test_keys_open_ended() {
|
||||||
let input = "aa bb cc\ndd aa ff\ngg aa cc\n";
|
test_helper("keys_open_ended", "-k 2.3");
|
||||||
new_ucmd!()
|
|
||||||
.args(&["-k", "2.2"])
|
|
||||||
.pipe_in(input)
|
|
||||||
.succeeds()
|
|
||||||
.stdout_only("gg aa cc\ndd aa ff\naa bb cc\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_keys_closed_range() {
|
fn test_keys_closed_range() {
|
||||||
let input = "aa bb cc\ndd aa ff\ngg aa cc\n";
|
test_helper("keys_closed_range", "-k 2.2,2.2");
|
||||||
new_ucmd!()
|
|
||||||
.args(&["-k", "2.2,2.2"])
|
|
||||||
.pipe_in(input)
|
|
||||||
.succeeds()
|
|
||||||
.stdout_only("dd aa ff\ngg aa cc\naa bb cc\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_keys_multiple_ranges() {
|
fn test_keys_multiple_ranges() {
|
||||||
let input = "aa bb cc\ndd aa ff\ngg aa cc\n";
|
test_helper("keys_multiple_ranges", "-k 2,2 -k 3,3");
|
||||||
new_ucmd!()
|
|
||||||
.args(&["-k", "2,2", "-k", "3,3"])
|
|
||||||
.pipe_in(input)
|
|
||||||
.succeeds()
|
|
||||||
.stdout_only("gg aa cc\ndd aa ff\naa bb cc\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_keys_no_field_match() {
|
fn test_keys_no_field_match() {
|
||||||
let input = "aa aa aa aa\naa bb cc\ndd aa ff\n";
|
test_helper("keys_no_field_match", "-k 4,4");
|
||||||
new_ucmd!()
|
|
||||||
.args(&["-k", "4,4"])
|
|
||||||
.pipe_in(input)
|
|
||||||
.succeeds()
|
|
||||||
.stdout_only("aa bb cc\ndd aa ff\naa aa aa aa\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_keys_no_char_match() {
|
fn test_keys_no_char_match() {
|
||||||
let input = "aaa\nba\nc\n";
|
test_helper("keys_no_char_match", "-k 1.2");
|
||||||
new_ucmd!()
|
|
||||||
.args(&["-k", "1.2"])
|
|
||||||
.pipe_in(input)
|
|
||||||
.succeeds()
|
|
||||||
.stdout_only("c\nba\naaa\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_keys_custom_separator() {
|
fn test_keys_custom_separator() {
|
||||||
let input = "aaxbbxcc\nddxaaxff\nggxaaxcc\n";
|
test_helper("keys_custom_separator", "-k 2.2,2.2 -t x");
|
||||||
new_ucmd!()
|
|
||||||
.args(&["-k", "2.2,2.2", "-t", "x"])
|
|
||||||
.pipe_in(input)
|
|
||||||
.succeeds()
|
|
||||||
.stdout_only("ddxaaxff\nggxaaxcc\naaxbbxcc\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -426,6 +419,13 @@ fn test_keys_invalid_field_zero() {
|
||||||
.stderr_only("sort: error: field index was 0");
|
.stderr_only("sort: error: field index was 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_keys_invalid_char_zero() {
|
||||||
|
new_ucmd!().args(&["-k", "1.0"]).fails().stderr_only(
|
||||||
|
"sort: error: invalid character index 0 in `1.0` for the start position of a field",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_keys_with_options() {
|
fn test_keys_with_options() {
|
||||||
let input = "aa 3 cc\ndd 1 ff\ngg 2 cc\n";
|
let input = "aa 3 cc\ndd 1 ff\ngg 2 cc\n";
|
||||||
|
@ -591,3 +591,39 @@ fn test_check_silent() {
|
||||||
.fails()
|
.fails()
|
||||||
.stdout_is("");
|
.stdout_is("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dictionary_and_nonprinting_conflicts() {
|
||||||
|
let conflicting_args = ["n", "h", "g", "M"];
|
||||||
|
for restricted_arg in &["d", "i"] {
|
||||||
|
for conflicting_arg in &conflicting_args {
|
||||||
|
new_ucmd!()
|
||||||
|
.arg(&format!("-{}{}", restricted_arg, conflicting_arg))
|
||||||
|
.fails();
|
||||||
|
}
|
||||||
|
for conflicting_arg in &conflicting_args {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&[
|
||||||
|
format!("-{}", restricted_arg).as_str(),
|
||||||
|
"-k",
|
||||||
|
&format!("1,1{}", conflicting_arg),
|
||||||
|
])
|
||||||
|
.succeeds();
|
||||||
|
}
|
||||||
|
for conflicting_arg in &conflicting_args {
|
||||||
|
// FIXME: this should ideally fail.
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["-k", &format!("1{},1{}", restricted_arg, conflicting_arg)])
|
||||||
|
.succeeds();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_trailing_separator() {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["-t", "x", "-k", "1,1"])
|
||||||
|
.pipe_in("aax\naaa\n")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_is("aax\naaa\n");
|
||||||
|
}
|
||||||
|
|
|
@ -337,5 +337,5 @@ fn expected_result(args: &[&str]) -> String {
|
||||||
.env("LANGUAGE", "C")
|
.env("LANGUAGE", "C")
|
||||||
.args(args)
|
.args(args)
|
||||||
.run()
|
.run()
|
||||||
.stdout
|
.stdout_move_str()
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,18 +52,19 @@ fn test_single_non_newline_separator_before() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_invalid_input() {
|
fn test_invalid_input() {
|
||||||
let (_, mut ucmd) = at_and_ucmd!();
|
let scene = TestScenario::new(util_name!());
|
||||||
|
let at = &scene.fixtures;
|
||||||
|
|
||||||
ucmd.arg("b")
|
scene
|
||||||
.run()
|
.ucmd()
|
||||||
.stderr
|
.arg("b")
|
||||||
.contains("tac: error: failed to open 'b' for reading");
|
.fails()
|
||||||
|
.stderr_contains("failed to open 'b' for reading: No such file or directory");
|
||||||
let (at, mut ucmd) = at_and_ucmd!();
|
|
||||||
|
|
||||||
at.mkdir("a");
|
at.mkdir("a");
|
||||||
ucmd.arg("a")
|
scene
|
||||||
.run()
|
.ucmd()
|
||||||
.stderr
|
.arg("a")
|
||||||
.contains("tac: error: failed to read 'a'");
|
.fails()
|
||||||
|
.stderr_contains("dir: read error: Invalid argument");
|
||||||
}
|
}
|
||||||
|
|
|
@ -343,3 +343,8 @@ fn test_negative_indexing() {
|
||||||
assert_eq!(positive_lines_index.stdout(), negative_lines_index.stdout());
|
assert_eq!(positive_lines_index.stdout(), negative_lines_index.stdout());
|
||||||
assert_eq!(positive_bytes_index.stdout(), negative_bytes_index.stdout());
|
assert_eq!(positive_bytes_index.stdout(), negative_bytes_index.stdout());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sleep_interval() {
|
||||||
|
new_ucmd!().arg("-s").arg("10").arg(FOOBAR_TXT).succeeds();
|
||||||
|
}
|
||||||
|
|
|
@ -367,7 +367,58 @@ fn test_touch_mtime_dst_succeeds() {
|
||||||
|
|
||||||
let target_time = str_to_filetime("%Y%m%d%H%M", "202103140300");
|
let target_time = str_to_filetime("%Y%m%d%H%M", "202103140300");
|
||||||
let (_, mtime) = get_file_times(&at, file);
|
let (_, mtime) = get_file_times(&at, file);
|
||||||
eprintln!("target_time: {:?}", target_time);
|
|
||||||
eprintln!("mtime: {:?}", mtime);
|
|
||||||
assert!(target_time == mtime);
|
assert!(target_time == mtime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// is_dst_switch_hour returns true if timespec ts is just before the switch
|
||||||
|
// to Daylight Saving Time.
|
||||||
|
// For example, in EST (UTC-5), Timespec { sec: 1583647200, nsec: 0 }
|
||||||
|
// for March 8 2020 01:00:00 AM
|
||||||
|
// is just before the switch because on that day clock jumps by 1 hour,
|
||||||
|
// so 1 minute after 01:59:00 is 03:00:00.
|
||||||
|
fn is_dst_switch_hour(ts: time::Timespec) -> bool {
|
||||||
|
let ts_after = ts + time::Duration::hours(1);
|
||||||
|
let tm = time::at(ts);
|
||||||
|
let tm_after = time::at(ts_after);
|
||||||
|
tm_after.tm_hour == tm.tm_hour + 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// get_dstswitch_hour returns date string for which touch -m -t fails.
|
||||||
|
// For example, in EST (UTC-5), that will be "202003080200" so
|
||||||
|
// touch -m -t 202003080200 somefile
|
||||||
|
// fails (that date/time does not exist).
|
||||||
|
// In other locales it will be a different date/time, and in some locales
|
||||||
|
// it doesn't exist at all, in which case this function will return None.
|
||||||
|
fn get_dstswitch_hour() -> Option<String> {
|
||||||
|
let now = time::now();
|
||||||
|
// Start from January 1, 2020, 00:00.
|
||||||
|
let mut tm = time::strptime("20200101-0000", "%Y%m%d-%H%M").unwrap();
|
||||||
|
tm.tm_isdst = -1;
|
||||||
|
tm.tm_utcoff = now.tm_utcoff;
|
||||||
|
let mut ts = tm.to_timespec();
|
||||||
|
// Loop through all hours in year 2020 until we find the hour just
|
||||||
|
// before the switch to DST.
|
||||||
|
for _i in 0..(366 * 24) {
|
||||||
|
if is_dst_switch_hour(ts) {
|
||||||
|
let mut tm = time::at(ts);
|
||||||
|
tm.tm_hour = tm.tm_hour + 1;
|
||||||
|
let s = time::strftime("%Y%m%d%H%M", &tm).unwrap().to_string();
|
||||||
|
return Some(s);
|
||||||
|
}
|
||||||
|
ts = ts + time::Duration::hours(1);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_touch_mtime_dst_fails() {
|
||||||
|
let (_at, mut ucmd) = at_and_ucmd!();
|
||||||
|
let file = "test_touch_set_mtime_dst_fails";
|
||||||
|
|
||||||
|
match get_dstswitch_hour() {
|
||||||
|
Some(s) => {
|
||||||
|
ucmd.args(&["-m", "-t", &s, file]).fails();
|
||||||
|
}
|
||||||
|
None => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -120,19 +120,15 @@ fn test_truncate_with_set1_shorter_than_set2() {
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_args_fails() {
|
fn missing_args_fails() {
|
||||||
let (_, mut ucmd) = at_and_ucmd!();
|
let (_, mut ucmd) = at_and_ucmd!();
|
||||||
let result = ucmd.run();
|
ucmd.fails().stderr_contains("missing operand");
|
||||||
|
|
||||||
assert!(!result.success);
|
|
||||||
assert!(result.stderr.contains("missing operand"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_required_second_arg_fails() {
|
fn missing_required_second_arg_fails() {
|
||||||
let (_, mut ucmd) = at_and_ucmd!();
|
let (_, mut ucmd) = at_and_ucmd!();
|
||||||
let result = ucmd.args(&["foo"]).run();
|
ucmd.args(&["foo"])
|
||||||
|
.fails()
|
||||||
assert!(!result.success);
|
.stderr_contains("missing operand after");
|
||||||
assert!(result.stderr.contains("missing operand after"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -53,6 +53,16 @@ fn test_decrease_file_size() {
|
||||||
assert!(file.seek(SeekFrom::Current(0)).unwrap() == 6);
|
assert!(file.seek(SeekFrom::Current(0)).unwrap() == 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_space_in_size() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
let mut file = at.make_file(TFILE2);
|
||||||
|
file.write_all(b"1234567890").unwrap();
|
||||||
|
ucmd.args(&["--size", " 4", TFILE2]).succeeds();
|
||||||
|
file.seek(SeekFrom::End(0)).unwrap();
|
||||||
|
assert!(file.seek(SeekFrom::Current(0)).unwrap() == 4);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_failed() {
|
fn test_failed() {
|
||||||
new_ucmd!().fails();
|
new_ucmd!().fails();
|
||||||
|
|
|
@ -18,33 +18,35 @@ fn test_sort_self_loop() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_no_such_file() {
|
fn test_no_such_file() {
|
||||||
let result = new_ucmd!().arg("invalid_file_txt").run();
|
new_ucmd!()
|
||||||
|
.arg("invalid_file_txt")
|
||||||
assert_eq!(true, result.stderr.contains("No such file or directory"));
|
.fails()
|
||||||
|
.stderr_contains("No such file or directory");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_version_flag() {
|
fn test_version_flag() {
|
||||||
let version_short = new_ucmd!().arg("-V").run();
|
let version_short = new_ucmd!().arg("-V").succeeds();
|
||||||
let version_long = new_ucmd!().arg("--version").run();
|
let version_long = new_ucmd!().arg("--version").succeeds();
|
||||||
|
|
||||||
assert_eq!(version_short.stdout(), version_long.stdout());
|
assert_eq!(version_short.stdout_str(), version_long.stdout_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_help_flag() {
|
fn test_help_flag() {
|
||||||
let help_short = new_ucmd!().arg("-h").run();
|
let help_short = new_ucmd!().arg("-h").succeeds();
|
||||||
let help_long = new_ucmd!().arg("--help").run();
|
let help_long = new_ucmd!().arg("--help").succeeds();
|
||||||
|
|
||||||
assert_eq!(help_short.stdout(), help_long.stdout());
|
assert_eq!(help_short.stdout_str(), help_long.stdout_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_multiple_arguments() {
|
fn test_multiple_arguments() {
|
||||||
let result = new_ucmd!()
|
new_ucmd!()
|
||||||
.arg("call_graph.txt")
|
.arg("call_graph.txt")
|
||||||
.arg("invalid_file.txt")
|
.arg("invalid_file")
|
||||||
.run();
|
.fails()
|
||||||
|
.stderr_contains(
|
||||||
assert_eq!(true, result.stderr.contains("error: Found argument 'invalid_file.txt' which wasn't expected, or isn't valid in this context"))
|
"Found argument 'invalid_file' which wasn't expected, or isn't valid in this context",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ fn test_heading() {
|
||||||
for opt in vec!["-H"] {
|
for opt in vec!["-H"] {
|
||||||
// allow whitespace variation
|
// allow whitespace variation
|
||||||
// * minor whitespace differences occur between platform built-in outputs; specifically number of TABs between "TIME" and "COMMENT" may be variant
|
// * minor whitespace differences occur between platform built-in outputs; specifically number of TABs between "TIME" and "COMMENT" may be variant
|
||||||
let actual = new_ucmd!().arg(opt).run().stdout;
|
let actual = new_ucmd!().arg(opt).run().stdout_move_str();
|
||||||
let expect = expected_result(opt);
|
let expect = expected_result(opt);
|
||||||
println!("actual: {:?}", actual);
|
println!("actual: {:?}", actual);
|
||||||
println!("expect: {:?}", expect);
|
println!("expect: {:?}", expect);
|
||||||
|
@ -80,5 +80,5 @@ fn expected_result(arg: &str) -> String {
|
||||||
.env("LANGUAGE", "C")
|
.env("LANGUAGE", "C")
|
||||||
.args(&[arg])
|
.args(&[arg])
|
||||||
.run()
|
.run()
|
||||||
.stdout
|
.stdout_move_str()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
use libc;
|
use libc;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
use std::env;
|
use std::env;
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
|
@ -42,22 +43,6 @@ pub fn is_ci() -> bool {
|
||||||
.eq_ignore_ascii_case("true")
|
.eq_ignore_ascii_case("true")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test if the program is running under WSL
|
|
||||||
// ref: <https://github.com/microsoft/WSL/issues/4555> @@ <https://archive.is/dP0bz>
|
|
||||||
// ToDO: test on WSL2 which likely doesn't need special handling; plan change to `is_wsl_1()` if WSL2 is less needy
|
|
||||||
pub fn is_wsl() -> bool {
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
{
|
|
||||||
if let Ok(b) = std::fs::read("/proc/sys/kernel/osrelease") {
|
|
||||||
if let Ok(s) = std::str::from_utf8(&b) {
|
|
||||||
let a = s.to_ascii_lowercase();
|
|
||||||
return a.contains("microsoft") || a.contains("wsl");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read a test scenario fixture, returning its bytes
|
/// Read a test scenario fixture, returning its bytes
|
||||||
fn read_scenario_fixture<S: AsRef<OsStr>>(tmpd: &Option<Rc<TempDir>>, file_rel_path: S) -> Vec<u8> {
|
fn read_scenario_fixture<S: AsRef<OsStr>>(tmpd: &Option<Rc<TempDir>>, file_rel_path: S) -> Vec<u8> {
|
||||||
let tmpdir_path = tmpd.as_ref().unwrap().as_ref().path();
|
let tmpdir_path = tmpd.as_ref().unwrap().as_ref().path();
|
||||||
|
@ -74,11 +59,11 @@ pub struct CmdResult {
|
||||||
code: Option<i32>,
|
code: Option<i32>,
|
||||||
/// zero-exit from running the Command?
|
/// zero-exit from running the Command?
|
||||||
/// see [`success`]
|
/// see [`success`]
|
||||||
pub success: bool,
|
success: bool,
|
||||||
/// captured standard output after running the Command
|
/// captured standard output after running the Command
|
||||||
pub stdout: String,
|
stdout: String,
|
||||||
/// captured standard error after running the Command
|
/// captured standard error after running the Command
|
||||||
pub stderr: String,
|
stderr: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CmdResult {
|
impl CmdResult {
|
||||||
|
@ -237,7 +222,7 @@ impl CmdResult {
|
||||||
/// like stdout_is(...), but expects the contents of the file at the provided relative path
|
/// like stdout_is(...), but expects the contents of the file at the provided relative path
|
||||||
pub fn stdout_is_fixture<T: AsRef<OsStr>>(&self, file_rel_path: T) -> &CmdResult {
|
pub fn stdout_is_fixture<T: AsRef<OsStr>>(&self, file_rel_path: T) -> &CmdResult {
|
||||||
let contents = read_scenario_fixture(&self.tmpd, file_rel_path);
|
let contents = read_scenario_fixture(&self.tmpd, file_rel_path);
|
||||||
self.stdout_is_bytes(contents)
|
self.stdout_is(String::from_utf8(contents).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// asserts that the command resulted in stderr stream output that equals the
|
/// asserts that the command resulted in stderr stream output that equals the
|
||||||
|
@ -261,7 +246,7 @@ impl CmdResult {
|
||||||
/// Like stdout_is_fixture, but for stderr
|
/// Like stdout_is_fixture, but for stderr
|
||||||
pub fn stderr_is_fixture<T: AsRef<OsStr>>(&self, file_rel_path: T) -> &CmdResult {
|
pub fn stderr_is_fixture<T: AsRef<OsStr>>(&self, file_rel_path: T) -> &CmdResult {
|
||||||
let contents = read_scenario_fixture(&self.tmpd, file_rel_path);
|
let contents = read_scenario_fixture(&self.tmpd, file_rel_path);
|
||||||
self.stderr_is_bytes(contents)
|
self.stderr_is(String::from_utf8(contents).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// asserts that
|
/// asserts that
|
||||||
|
@ -329,14 +314,14 @@ impl CmdResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stdout_matches(&self, regex: ®ex::Regex) -> &CmdResult {
|
pub fn stdout_matches(&self, regex: ®ex::Regex) -> &CmdResult {
|
||||||
if !regex.is_match(self.stdout_str()) {
|
if !regex.is_match(self.stdout_str().trim()) {
|
||||||
panic!("Stdout does not match regex:\n{}", self.stdout_str())
|
panic!("Stdout does not match regex:\n{}", self.stdout_str())
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stdout_does_not_match(&self, regex: ®ex::Regex) -> &CmdResult {
|
pub fn stdout_does_not_match(&self, regex: ®ex::Regex) -> &CmdResult {
|
||||||
if regex.is_match(self.stdout_str()) {
|
if regex.is_match(self.stdout_str().trim()) {
|
||||||
panic!("Stdout matches regex:\n{}", self.stdout_str())
|
panic!("Stdout matches regex:\n{}", self.stdout_str())
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
|
@ -635,7 +620,7 @@ impl TestScenario {
|
||||||
},
|
},
|
||||||
util_name: String::from(util_name),
|
util_name: String::from(util_name),
|
||||||
fixtures: AtPath::new(tmpd.as_ref().path()),
|
fixtures: AtPath::new(tmpd.as_ref().path()),
|
||||||
tmpd: tmpd,
|
tmpd,
|
||||||
};
|
};
|
||||||
let mut fixture_path_builder = env::current_dir().unwrap();
|
let mut fixture_path_builder = env::current_dir().unwrap();
|
||||||
fixture_path_builder.push(TESTS_DIR);
|
fixture_path_builder.push(TESTS_DIR);
|
||||||
|
@ -696,8 +681,11 @@ pub struct UCommand {
|
||||||
comm_string: String,
|
comm_string: String,
|
||||||
tmpd: Option<Rc<TempDir>>,
|
tmpd: Option<Rc<TempDir>>,
|
||||||
has_run: bool,
|
has_run: bool,
|
||||||
stdin: Option<Vec<u8>>,
|
|
||||||
ignore_stdin_write_error: bool,
|
ignore_stdin_write_error: bool,
|
||||||
|
stdin: Option<Stdio>,
|
||||||
|
stdout: Option<Stdio>,
|
||||||
|
stderr: Option<Stdio>,
|
||||||
|
bytes_into_stdin: Option<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UCommand {
|
impl UCommand {
|
||||||
|
@ -726,8 +714,11 @@ impl UCommand {
|
||||||
cmd
|
cmd
|
||||||
},
|
},
|
||||||
comm_string: String::from(arg.as_ref().to_str().unwrap()),
|
comm_string: String::from(arg.as_ref().to_str().unwrap()),
|
||||||
stdin: None,
|
|
||||||
ignore_stdin_write_error: false,
|
ignore_stdin_write_error: false,
|
||||||
|
bytes_into_stdin: None,
|
||||||
|
stdin: None,
|
||||||
|
stdout: None,
|
||||||
|
stderr: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -738,6 +729,21 @@ impl UCommand {
|
||||||
ucmd
|
ucmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_stdin<T: Into<Stdio>>(&mut self, stdin: T) -> &mut UCommand {
|
||||||
|
self.stdin = Some(stdin.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_stdout<T: Into<Stdio>>(&mut self, stdout: T) -> &mut UCommand {
|
||||||
|
self.stdout = Some(stdout.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_stderr<T: Into<Stdio>>(&mut self, stderr: T) -> &mut UCommand {
|
||||||
|
self.stderr = Some(stderr.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Add a parameter to the invocation. Path arguments are treated relative
|
/// Add a parameter to the invocation. Path arguments are treated relative
|
||||||
/// to the test environment directory.
|
/// to the test environment directory.
|
||||||
pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut UCommand {
|
pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut UCommand {
|
||||||
|
@ -767,10 +773,10 @@ impl UCommand {
|
||||||
|
|
||||||
/// provides stdinput to feed in to the command when spawned
|
/// provides stdinput to feed in to the command when spawned
|
||||||
pub fn pipe_in<T: Into<Vec<u8>>>(&mut self, input: T) -> &mut UCommand {
|
pub fn pipe_in<T: Into<Vec<u8>>>(&mut self, input: T) -> &mut UCommand {
|
||||||
if self.stdin.is_some() {
|
if self.bytes_into_stdin.is_some() {
|
||||||
panic!("{}", MULTIPLE_STDIN_MEANINGLESS);
|
panic!("{}", MULTIPLE_STDIN_MEANINGLESS);
|
||||||
}
|
}
|
||||||
self.stdin = Some(input.into());
|
self.bytes_into_stdin = Some(input.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -784,7 +790,7 @@ impl UCommand {
|
||||||
/// This is typically useful to test non-standard workflows
|
/// This is typically useful to test non-standard workflows
|
||||||
/// like feeding something to a command that does not read it
|
/// like feeding something to a command that does not read it
|
||||||
pub fn ignore_stdin_write_error(&mut self) -> &mut UCommand {
|
pub fn ignore_stdin_write_error(&mut self) -> &mut UCommand {
|
||||||
if self.stdin.is_none() {
|
if self.bytes_into_stdin.is_none() {
|
||||||
panic!("{}", NO_STDIN_MEANINGLESS);
|
panic!("{}", NO_STDIN_MEANINGLESS);
|
||||||
}
|
}
|
||||||
self.ignore_stdin_write_error = true;
|
self.ignore_stdin_write_error = true;
|
||||||
|
@ -813,13 +819,13 @@ impl UCommand {
|
||||||
log_info("run", &self.comm_string);
|
log_info("run", &self.comm_string);
|
||||||
let mut child = self
|
let mut child = self
|
||||||
.raw
|
.raw
|
||||||
.stdin(Stdio::piped())
|
.stdin(self.stdin.take().unwrap_or_else(|| Stdio::piped()))
|
||||||
.stdout(Stdio::piped())
|
.stdout(self.stdout.take().unwrap_or_else(|| Stdio::piped()))
|
||||||
.stderr(Stdio::piped())
|
.stderr(self.stderr.take().unwrap_or_else(|| Stdio::piped()))
|
||||||
.spawn()
|
.spawn()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
if let Some(ref input) = self.stdin {
|
if let Some(ref input) = self.bytes_into_stdin {
|
||||||
let write_result = child
|
let write_result = child
|
||||||
.stdin
|
.stdin
|
||||||
.take()
|
.take()
|
||||||
|
|
200
tests/fixtures/sort/default_unsorted_ints.expected.debug
vendored
Normal file
200
tests/fixtures/sort/default_unsorted_ints.expected.debug
vendored
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
1
|
||||||
|
_
|
||||||
|
10
|
||||||
|
__
|
||||||
|
100
|
||||||
|
___
|
||||||
|
11
|
||||||
|
__
|
||||||
|
12
|
||||||
|
__
|
||||||
|
13
|
||||||
|
__
|
||||||
|
14
|
||||||
|
__
|
||||||
|
15
|
||||||
|
__
|
||||||
|
16
|
||||||
|
__
|
||||||
|
17
|
||||||
|
__
|
||||||
|
18
|
||||||
|
__
|
||||||
|
19
|
||||||
|
__
|
||||||
|
2
|
||||||
|
_
|
||||||
|
20
|
||||||
|
__
|
||||||
|
21
|
||||||
|
__
|
||||||
|
22
|
||||||
|
__
|
||||||
|
23
|
||||||
|
__
|
||||||
|
24
|
||||||
|
__
|
||||||
|
25
|
||||||
|
__
|
||||||
|
26
|
||||||
|
__
|
||||||
|
27
|
||||||
|
__
|
||||||
|
28
|
||||||
|
__
|
||||||
|
29
|
||||||
|
__
|
||||||
|
3
|
||||||
|
_
|
||||||
|
30
|
||||||
|
__
|
||||||
|
31
|
||||||
|
__
|
||||||
|
32
|
||||||
|
__
|
||||||
|
33
|
||||||
|
__
|
||||||
|
34
|
||||||
|
__
|
||||||
|
35
|
||||||
|
__
|
||||||
|
36
|
||||||
|
__
|
||||||
|
37
|
||||||
|
__
|
||||||
|
38
|
||||||
|
__
|
||||||
|
39
|
||||||
|
__
|
||||||
|
4
|
||||||
|
_
|
||||||
|
40
|
||||||
|
__
|
||||||
|
41
|
||||||
|
__
|
||||||
|
42
|
||||||
|
__
|
||||||
|
43
|
||||||
|
__
|
||||||
|
44
|
||||||
|
__
|
||||||
|
45
|
||||||
|
__
|
||||||
|
46
|
||||||
|
__
|
||||||
|
47
|
||||||
|
__
|
||||||
|
48
|
||||||
|
__
|
||||||
|
49
|
||||||
|
__
|
||||||
|
5
|
||||||
|
_
|
||||||
|
50
|
||||||
|
__
|
||||||
|
51
|
||||||
|
__
|
||||||
|
52
|
||||||
|
__
|
||||||
|
53
|
||||||
|
__
|
||||||
|
54
|
||||||
|
__
|
||||||
|
55
|
||||||
|
__
|
||||||
|
56
|
||||||
|
__
|
||||||
|
57
|
||||||
|
__
|
||||||
|
58
|
||||||
|
__
|
||||||
|
59
|
||||||
|
__
|
||||||
|
6
|
||||||
|
_
|
||||||
|
60
|
||||||
|
__
|
||||||
|
61
|
||||||
|
__
|
||||||
|
62
|
||||||
|
__
|
||||||
|
63
|
||||||
|
__
|
||||||
|
64
|
||||||
|
__
|
||||||
|
65
|
||||||
|
__
|
||||||
|
66
|
||||||
|
__
|
||||||
|
67
|
||||||
|
__
|
||||||
|
68
|
||||||
|
__
|
||||||
|
69
|
||||||
|
__
|
||||||
|
7
|
||||||
|
_
|
||||||
|
70
|
||||||
|
__
|
||||||
|
71
|
||||||
|
__
|
||||||
|
72
|
||||||
|
__
|
||||||
|
73
|
||||||
|
__
|
||||||
|
74
|
||||||
|
__
|
||||||
|
75
|
||||||
|
__
|
||||||
|
76
|
||||||
|
__
|
||||||
|
77
|
||||||
|
__
|
||||||
|
78
|
||||||
|
__
|
||||||
|
79
|
||||||
|
__
|
||||||
|
8
|
||||||
|
_
|
||||||
|
80
|
||||||
|
__
|
||||||
|
81
|
||||||
|
__
|
||||||
|
82
|
||||||
|
__
|
||||||
|
83
|
||||||
|
__
|
||||||
|
84
|
||||||
|
__
|
||||||
|
85
|
||||||
|
__
|
||||||
|
86
|
||||||
|
__
|
||||||
|
87
|
||||||
|
__
|
||||||
|
88
|
||||||
|
__
|
||||||
|
89
|
||||||
|
__
|
||||||
|
9
|
||||||
|
_
|
||||||
|
90
|
||||||
|
__
|
||||||
|
91
|
||||||
|
__
|
||||||
|
92
|
||||||
|
__
|
||||||
|
93
|
||||||
|
__
|
||||||
|
94
|
||||||
|
__
|
||||||
|
95
|
||||||
|
__
|
||||||
|
96
|
||||||
|
__
|
||||||
|
97
|
||||||
|
__
|
||||||
|
98
|
||||||
|
__
|
||||||
|
99
|
||||||
|
__
|
9
tests/fixtures/sort/dictionary_order.expected.debug
vendored
Normal file
9
tests/fixtures/sort/dictionary_order.expected.debug
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
bbb
|
||||||
|
___
|
||||||
|
___
|
||||||
|
./bbc
|
||||||
|
_____
|
||||||
|
_____
|
||||||
|
bbd
|
||||||
|
___
|
||||||
|
___
|
36
tests/fixtures/sort/exponents-positive-numeric.expected.debug
vendored
Normal file
36
tests/fixtures/sort/exponents-positive-numeric.expected.debug
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
+100000
|
||||||
|
^ no match for key
|
||||||
|
_______
|
||||||
|
10E
|
||||||
|
__
|
||||||
|
___
|
||||||
|
50e10
|
||||||
|
__
|
||||||
|
_____
|
||||||
|
100E6
|
||||||
|
___
|
||||||
|
_____
|
||||||
|
1000EDKLD
|
||||||
|
____
|
||||||
|
_________
|
||||||
|
10000K78
|
||||||
|
_____
|
||||||
|
________
|
19
tests/fixtures/sort/exponents_general.expected
vendored
Normal file
19
tests/fixtures/sort/exponents_general.expected
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
5.5.5.5
|
||||||
|
10E
|
||||||
|
1000EDKLD
|
||||||
|
10000K78
|
||||||
|
+100000
|
||||||
|
+100000
|
||||||
|
100E6
|
||||||
|
100E6
|
||||||
|
10e10e10e10
|
||||||
|
50e10
|
||||||
|
50e10
|
57
tests/fixtures/sort/exponents_general.expected.debug
vendored
Normal file
57
tests/fixtures/sort/exponents_general.expected.debug
vendored
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
5.5.5.5
|
||||||
|
___
|
||||||
|
_______
|
||||||
|
10E
|
||||||
|
__
|
||||||
|
___
|
||||||
|
1000EDKLD
|
||||||
|
____
|
||||||
|
_________
|
||||||
|
10000K78
|
||||||
|
_____
|
||||||
|
________
|
||||||
|
+100000
|
||||||
|
_______
|
||||||
|
_______
|
||||||
|
+100000
|
||||||
|
_______
|
||||||
|
_______
|
||||||
|
100E6
|
||||||
|
_____
|
||||||
|
_____
|
||||||
|
100E6
|
||||||
|
_____
|
||||||
|
_____
|
||||||
|
10e10e10e10
|
||||||
|
_____
|
||||||
|
___________
|
||||||
|
50e10
|
||||||
|
_____
|
||||||
|
_____
|
||||||
|
50e10
|
||||||
|
_____
|
||||||
|
_____
|
19
tests/fixtures/sort/exponents_general.txt
vendored
Normal file
19
tests/fixtures/sort/exponents_general.txt
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
100E6
|
||||||
|
|
||||||
|
50e10
|
||||||
|
+100000
|
||||||
|
|
||||||
|
10000K78
|
||||||
|
10E
|
||||||
|
|
||||||
|
|
||||||
|
1000EDKLD
|
||||||
|
|
||||||
|
|
||||||
|
100E6
|
||||||
|
|
||||||
|
50e10
|
||||||
|
+100000
|
||||||
|
|
||||||
|
10e10e10e10
|
||||||
|
5.5.5.5
|
33
tests/fixtures/sort/human-numeric-whitespace.expected.debug
vendored
Normal file
33
tests/fixtures/sort/human-numeric-whitespace.expected.debug
vendored
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
456K
|
||||||
|
____
|
||||||
|
____
|
||||||
|
4568K
|
||||||
|
_____
|
||||||
|
_____
|
||||||
|
>>>456M
|
||||||
|
____
|
||||||
|
_______
|
||||||
|
6.2G
|
||||||
|
____
|
||||||
|
__________________
|
33
tests/fixtures/sort/human_block_sizes.expected.debug
vendored
Normal file
33
tests/fixtures/sort/human_block_sizes.expected.debug
vendored
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
844K
|
||||||
|
____
|
||||||
|
____
|
||||||
|
981K
|
||||||
|
____
|
||||||
|
____
|
||||||
|
11M
|
||||||
|
___
|
||||||
|
___
|
||||||
|
13M
|
||||||
|
___
|
||||||
|
___
|
||||||
|
14M
|
||||||
|
___
|
||||||
|
___
|
||||||
|
16M
|
||||||
|
___
|
||||||
|
___
|
||||||
|
18M
|
||||||
|
___
|
||||||
|
___
|
||||||
|
19M
|
||||||
|
___
|
||||||
|
___
|
||||||
|
20M
|
||||||
|
___
|
||||||
|
___
|
||||||
|
981T
|
||||||
|
____
|
||||||
|
____
|
||||||
|
20P
|
||||||
|
___
|
||||||
|
___
|
21
tests/fixtures/sort/ignore_case.expected.debug
vendored
Normal file
21
tests/fixtures/sort/ignore_case.expected.debug
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
aaa
|
||||||
|
___
|
||||||
|
___
|
||||||
|
BBB
|
||||||
|
___
|
||||||
|
___
|
||||||
|
ccc
|
||||||
|
___
|
||||||
|
___
|
||||||
|
DDD
|
||||||
|
___
|
||||||
|
___
|
||||||
|
eee
|
||||||
|
___
|
||||||
|
___
|
||||||
|
FFF
|
||||||
|
___
|
||||||
|
___
|
||||||
|
ggg
|
||||||
|
___
|
||||||
|
___
|
6
tests/fixtures/sort/keys_closed_range.expected
vendored
Normal file
6
tests/fixtures/sort/keys_closed_range.expected
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
dd aa ff
|
||||||
|
gg aa cc
|
||||||
|
aa bb cc
|
||||||
|
èè éé èè
|
||||||
|
👩🔬 👩🔬 👩🔬
|
||||||
|
💣💣 💣💣 💣💣
|
18
tests/fixtures/sort/keys_closed_range.expected.debug
vendored
Normal file
18
tests/fixtures/sort/keys_closed_range.expected.debug
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
dd aa ff
|
||||||
|
_
|
||||||
|
________
|
||||||
|
gg aa cc
|
||||||
|
_
|
||||||
|
________
|
||||||
|
aa bb cc
|
||||||
|
_
|
||||||
|
________
|
||||||
|
èè éé èè
|
||||||
|
_
|
||||||
|
________
|
||||||
|
👩🔬 👩🔬 👩🔬
|
||||||
|
__
|
||||||
|
______________
|
||||||
|
💣💣 💣💣 💣💣
|
||||||
|
__
|
||||||
|
______________
|
6
tests/fixtures/sort/keys_closed_range.txt
vendored
Normal file
6
tests/fixtures/sort/keys_closed_range.txt
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
aa bb cc
|
||||||
|
dd aa ff
|
||||||
|
gg aa cc
|
||||||
|
èè éé èè
|
||||||
|
💣💣 💣💣 💣💣
|
||||||
|
👩🔬 👩🔬 👩🔬
|
3
tests/fixtures/sort/keys_custom_separator.expected
vendored
Normal file
3
tests/fixtures/sort/keys_custom_separator.expected
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
ddxaaxff
|
||||||
|
ggxaaxcc
|
||||||
|
aaxbbxcc
|
9
tests/fixtures/sort/keys_custom_separator.expected.debug
vendored
Normal file
9
tests/fixtures/sort/keys_custom_separator.expected.debug
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
ddxaaxff
|
||||||
|
_
|
||||||
|
________
|
||||||
|
ggxaaxcc
|
||||||
|
_
|
||||||
|
________
|
||||||
|
aaxbbxcc
|
||||||
|
_
|
||||||
|
________
|
3
tests/fixtures/sort/keys_custom_separator.txt
vendored
Normal file
3
tests/fixtures/sort/keys_custom_separator.txt
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
aaxbbxcc
|
||||||
|
ddxaaxff
|
||||||
|
ggxaaxcc
|
6
tests/fixtures/sort/keys_multiple_ranges.expected
vendored
Normal file
6
tests/fixtures/sort/keys_multiple_ranges.expected
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
gg aa cc
|
||||||
|
dd aa ff
|
||||||
|
aa bb cc
|
||||||
|
èè éé èè
|
||||||
|
👩🔬 👩🔬 👩🔬
|
||||||
|
💣💣 💣💣 💣💣
|
24
tests/fixtures/sort/keys_multiple_ranges.expected.debug
vendored
Normal file
24
tests/fixtures/sort/keys_multiple_ranges.expected.debug
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
gg aa cc
|
||||||
|
___
|
||||||
|
___
|
||||||
|
________
|
||||||
|
dd aa ff
|
||||||
|
___
|
||||||
|
___
|
||||||
|
________
|
||||||
|
aa bb cc
|
||||||
|
___
|
||||||
|
___
|
||||||
|
________
|
||||||
|
èè éé èè
|
||||||
|
___
|
||||||
|
___
|
||||||
|
________
|
||||||
|
👩🔬 👩🔬 👩🔬
|
||||||
|
_____
|
||||||
|
_____
|
||||||
|
______________
|
||||||
|
💣💣 💣💣 💣💣
|
||||||
|
_____
|
||||||
|
_____
|
||||||
|
______________
|
6
tests/fixtures/sort/keys_multiple_ranges.txt
vendored
Normal file
6
tests/fixtures/sort/keys_multiple_ranges.txt
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
aa bb cc
|
||||||
|
dd aa ff
|
||||||
|
gg aa cc
|
||||||
|
èè éé èè
|
||||||
|
💣💣 💣💣 💣💣
|
||||||
|
👩🔬 👩🔬 👩🔬
|
3
tests/fixtures/sort/keys_no_char_match.expected
vendored
Normal file
3
tests/fixtures/sort/keys_no_char_match.expected
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
c
|
||||||
|
ba
|
||||||
|
aaa
|
9
tests/fixtures/sort/keys_no_char_match.expected.debug
vendored
Normal file
9
tests/fixtures/sort/keys_no_char_match.expected.debug
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
c
|
||||||
|
^ no match for key
|
||||||
|
_
|
||||||
|
ba
|
||||||
|
_
|
||||||
|
__
|
||||||
|
aaa
|
||||||
|
__
|
||||||
|
___
|
3
tests/fixtures/sort/keys_no_char_match.txt
vendored
Normal file
3
tests/fixtures/sort/keys_no_char_match.txt
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
aaa
|
||||||
|
ba
|
||||||
|
c
|
6
tests/fixtures/sort/keys_no_field_match.expected
vendored
Normal file
6
tests/fixtures/sort/keys_no_field_match.expected
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
aa bb cc
|
||||||
|
dd aa ff
|
||||||
|
gg aa cc
|
||||||
|
èè éé èè
|
||||||
|
👩🔬 👩🔬 👩🔬
|
||||||
|
💣💣 💣💣 💣💣
|
18
tests/fixtures/sort/keys_no_field_match.expected.debug
vendored
Normal file
18
tests/fixtures/sort/keys_no_field_match.expected.debug
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
aa bb cc
|
||||||
|
^ no match for key
|
||||||
|
________
|
||||||
|
dd aa ff
|
||||||
|
^ no match for key
|
||||||
|
________
|
||||||
|
gg aa cc
|
||||||
|
^ no match for key
|
||||||
|
________
|
||||||
|
èè éé èè
|
||||||
|
^ no match for key
|
||||||
|
________
|
||||||
|
👩🔬 👩🔬 👩🔬
|
||||||
|
^ no match for key
|
||||||
|
______________
|
||||||
|
💣💣 💣💣 💣💣
|
||||||
|
^ no match for key
|
||||||
|
______________
|
6
tests/fixtures/sort/keys_no_field_match.txt
vendored
Normal file
6
tests/fixtures/sort/keys_no_field_match.txt
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
aa bb cc
|
||||||
|
dd aa ff
|
||||||
|
gg aa cc
|
||||||
|
èè éé èè
|
||||||
|
💣💣 💣💣 💣💣
|
||||||
|
👩🔬 👩🔬 👩🔬
|
6
tests/fixtures/sort/keys_open_ended.expected
vendored
Normal file
6
tests/fixtures/sort/keys_open_ended.expected
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
gg aa cc
|
||||||
|
dd aa ff
|
||||||
|
aa bb cc
|
||||||
|
èè éé èè
|
||||||
|
👩🔬 👩🔬 👩🔬
|
||||||
|
💣💣 💣💣 💣💣
|
18
tests/fixtures/sort/keys_open_ended.expected.debug
vendored
Normal file
18
tests/fixtures/sort/keys_open_ended.expected.debug
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
gg aa cc
|
||||||
|
____
|
||||||
|
________
|
||||||
|
dd aa ff
|
||||||
|
____
|
||||||
|
________
|
||||||
|
aa bb cc
|
||||||
|
____
|
||||||
|
________
|
||||||
|
èè éé èè
|
||||||
|
____
|
||||||
|
________
|
||||||
|
👩🔬 👩🔬 👩🔬
|
||||||
|
_______
|
||||||
|
______________
|
||||||
|
💣💣 💣💣 💣💣
|
||||||
|
_______
|
||||||
|
______________
|
6
tests/fixtures/sort/keys_open_ended.txt
vendored
Normal file
6
tests/fixtures/sort/keys_open_ended.txt
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
aa bb cc
|
||||||
|
dd aa ff
|
||||||
|
gg aa cc
|
||||||
|
èè éé èè
|
||||||
|
💣💣 💣💣 💣💣
|
||||||
|
👩🔬 👩🔬 👩🔬
|
90
tests/fixtures/sort/mixed_floats_ints_chars_numeric.expected.debug
vendored
Normal file
90
tests/fixtures/sort/mixed_floats_ints_chars_numeric.expected.debug
vendored
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
-2028789030
|
||||||
|
___________
|
||||||
|
___________
|
||||||
|
-896689
|
||||||
|
_______
|
||||||
|
_______
|
||||||
|
-8.90880
|
||||||
|
________
|
||||||
|
________
|
||||||
|
-1
|
||||||
|
__
|
||||||
|
__
|
||||||
|
-.05
|
||||||
|
____
|
||||||
|
____
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
000
|
||||||
|
___
|
||||||
|
___
|
||||||
|
CARAvan
|
||||||
|
^ no match for key
|
||||||
|
_______
|
||||||
|
00000001
|
||||||
|
________
|
||||||
|
________
|
||||||
|
1
|
||||||
|
_
|
||||||
|
_
|
||||||
|
1.040000000
|
||||||
|
___________
|
||||||
|
___________
|
||||||
|
1.444
|
||||||
|
_____
|
||||||
|
_____
|
||||||
|
1.58590
|
||||||
|
_______
|
||||||
|
_______
|
||||||
|
8.013
|
||||||
|
_____
|
||||||
|
_____
|
||||||
|
45
|
||||||
|
__
|
||||||
|
__
|
||||||
|
46.89
|
||||||
|
_____
|
||||||
|
_____
|
||||||
|
4567.
|
||||||
|
_____
|
||||||
|
____________________
|
||||||
|
>>>>37800
|
||||||
|
_____
|
||||||
|
_________
|
||||||
|
576,446.88800000
|
||||||
|
________________
|
||||||
|
________________
|
||||||
|
576,446.890
|
||||||
|
___________
|
||||||
|
___________
|
||||||
|
4798908.340000000000
|
||||||
|
____________________
|
||||||
|
____________________
|
||||||
|
4798908.45
|
||||||
|
__________
|
||||||
|
__________
|
||||||
|
4798908.8909800
|
||||||
|
_______________
|
||||||
|
_______________
|
60
tests/fixtures/sort/mixed_floats_ints_chars_numeric_stable.expected.debug
vendored
Normal file
60
tests/fixtures/sort/mixed_floats_ints_chars_numeric_stable.expected.debug
vendored
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
-2028789030
|
||||||
|
___________
|
||||||
|
-896689
|
||||||
|
_______
|
||||||
|
-8.90880
|
||||||
|
________
|
||||||
|
-1
|
||||||
|
__
|
||||||
|
-.05
|
||||||
|
____
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
CARAvan
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
000
|
||||||
|
___
|
||||||
|
1
|
||||||
|
_
|
||||||
|
00000001
|
||||||
|
________
|
||||||
|
1.040000000
|
||||||
|
___________
|
||||||
|
1.444
|
||||||
|
_____
|
||||||
|
1.58590
|
||||||
|
_______
|
||||||
|
8.013
|
||||||
|
_____
|
||||||
|
45
|
||||||
|
__
|
||||||
|
46.89
|
||||||
|
_____
|
||||||
|
4567.
|
||||||
|
_____
|
||||||
|
>>>>37800
|
||||||
|
_____
|
||||||
|
576,446.88800000
|
||||||
|
________________
|
||||||
|
576,446.890
|
||||||
|
___________
|
||||||
|
4798908.340000000000
|
||||||
|
____________________
|
||||||
|
4798908.45
|
||||||
|
__________
|
||||||
|
4798908.8909800
|
||||||
|
_______________
|
40
tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique.expected.debug
vendored
Normal file
40
tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique.expected.debug
vendored
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
-2028789030
|
||||||
|
___________
|
||||||
|
-896689
|
||||||
|
_______
|
||||||
|
-8.90880
|
||||||
|
________
|
||||||
|
-1
|
||||||
|
__
|
||||||
|
-.05
|
||||||
|
____
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
1
|
||||||
|
_
|
||||||
|
1.040000000
|
||||||
|
___________
|
||||||
|
1.444
|
||||||
|
_____
|
||||||
|
1.58590
|
||||||
|
_______
|
||||||
|
8.013
|
||||||
|
_____
|
||||||
|
45
|
||||||
|
__
|
||||||
|
46.89
|
||||||
|
_____
|
||||||
|
4567.
|
||||||
|
_____
|
||||||
|
>>>>37800
|
||||||
|
_____
|
||||||
|
576,446.88800000
|
||||||
|
________________
|
||||||
|
576,446.890
|
||||||
|
___________
|
||||||
|
4798908.340000000000
|
||||||
|
____________________
|
||||||
|
4798908.45
|
||||||
|
__________
|
||||||
|
4798908.8909800
|
||||||
|
_______________
|
40
tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse.expected.debug
vendored
Normal file
40
tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse.expected.debug
vendored
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
4798908.8909800
|
||||||
|
_______________
|
||||||
|
4798908.45
|
||||||
|
__________
|
||||||
|
4798908.340000000000
|
||||||
|
____________________
|
||||||
|
576,446.890
|
||||||
|
___________
|
||||||
|
576,446.88800000
|
||||||
|
________________
|
||||||
|
>>>>37800
|
||||||
|
_____
|
||||||
|
4567.
|
||||||
|
_____
|
||||||
|
46.89
|
||||||
|
_____
|
||||||
|
45
|
||||||
|
__
|
||||||
|
8.013
|
||||||
|
_____
|
||||||
|
1.58590
|
||||||
|
_______
|
||||||
|
1.444
|
||||||
|
_____
|
||||||
|
1.040000000
|
||||||
|
___________
|
||||||
|
1
|
||||||
|
_
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
-.05
|
||||||
|
____
|
||||||
|
-1
|
||||||
|
__
|
||||||
|
-8.90880
|
||||||
|
________
|
||||||
|
-896689
|
||||||
|
_______
|
||||||
|
-2028789030
|
||||||
|
___________
|
30
tests/fixtures/sort/month_default.expected.debug
vendored
Normal file
30
tests/fixtures/sort/month_default.expected.debug
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
N/A Ut enim ad minim veniam, quis
|
||||||
|
^ no match for key
|
||||||
|
_________________________________
|
||||||
|
Jan Lorem ipsum dolor sit amet
|
||||||
|
___
|
||||||
|
______________________________
|
||||||
|
mar laboris nisi ut aliquip ex ea
|
||||||
|
___
|
||||||
|
_________________________________
|
||||||
|
May sed do eiusmod tempor incididunt
|
||||||
|
___
|
||||||
|
____________________________________
|
||||||
|
JUN nostrud exercitation ullamco
|
||||||
|
___
|
||||||
|
________________________________
|
||||||
|
Jul 1 should remain 2,1,3
|
||||||
|
___
|
||||||
|
_________________________
|
||||||
|
Jul 2 these three lines
|
||||||
|
___
|
||||||
|
_______________________
|
||||||
|
Jul 3 if --stable is provided
|
||||||
|
___
|
||||||
|
_____________________________
|
||||||
|
Oct ut labore et dolore magna aliqua
|
||||||
|
___
|
||||||
|
____________________________________
|
||||||
|
Dec consectetur adipiscing elit
|
||||||
|
___
|
||||||
|
_______________________________
|
20
tests/fixtures/sort/month_stable.expected.debug
vendored
Normal file
20
tests/fixtures/sort/month_stable.expected.debug
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
N/A Ut enim ad minim veniam, quis
|
||||||
|
^ no match for key
|
||||||
|
Jan Lorem ipsum dolor sit amet
|
||||||
|
___
|
||||||
|
mar laboris nisi ut aliquip ex ea
|
||||||
|
___
|
||||||
|
May sed do eiusmod tempor incididunt
|
||||||
|
___
|
||||||
|
JUN nostrud exercitation ullamco
|
||||||
|
___
|
||||||
|
Jul 2 these three lines
|
||||||
|
___
|
||||||
|
Jul 1 should remain 2,1,3
|
||||||
|
___
|
||||||
|
Jul 3 if --stable is provided
|
||||||
|
___
|
||||||
|
Oct ut labore et dolore magna aliqua
|
||||||
|
___
|
||||||
|
Dec consectetur adipiscing elit
|
||||||
|
___
|
12
tests/fixtures/sort/months-dedup.expected.debug
vendored
Normal file
12
tests/fixtures/sort/months-dedup.expected.debug
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
JAN
|
||||||
|
___
|
||||||
|
apr
|
||||||
|
___
|
||||||
|
MAY
|
||||||
|
___
|
||||||
|
JUNNNN
|
||||||
|
___
|
||||||
|
AUG
|
||||||
|
___
|
24
tests/fixtures/sort/months-whitespace.expected.debug
vendored
Normal file
24
tests/fixtures/sort/months-whitespace.expected.debug
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
|
||||||
|
^ no match for key
|
||||||
|
^ no match for key
|
||||||
|
JAN
|
||||||
|
___
|
||||||
|
___
|
||||||
|
FEb
|
||||||
|
___
|
||||||
|
_____
|
||||||
|
apr
|
||||||
|
___
|
||||||
|
____
|
||||||
|
apr
|
||||||
|
___
|
||||||
|
____
|
||||||
|
>>>JUNNNN
|
||||||
|
___
|
||||||
|
_________
|
||||||
|
AUG
|
||||||
|
___
|
||||||
|
____
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue