mirror of
https://github.com/uutils/coreutils
synced 2024-11-17 02:08:09 +00:00
Merge branch 'master' of https://github.com/uutils/coreutils into sort-no-json-extsort
This commit is contained in:
commit
e99f157e6a
35 changed files with 1775 additions and 795 deletions
16
Cargo.lock
generated
16
Cargo.lock
generated
|
@ -106,9 +106,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "0.2.15"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a40b47ad93e1a5404e6c18dec46b628214fee441c70f4ab5d6942142cc268a3d"
|
||||
checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"memchr 2.4.0",
|
||||
|
@ -1277,9 +1277,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.5.1"
|
||||
version = "1.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a068b905b8cb93815aa3069ae48653d90f382308aebd1d33d940ac1f1d771e2d"
|
||||
checksum = "ce5f1ceb7f74abbce32601642fcf8e8508a8a8991e0621c7d750295b9095702b"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr 2.4.0",
|
||||
|
@ -1297,9 +1297,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.24"
|
||||
version = "0.6.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00efb87459ba4f6fb2169d20f68565555688e1250ee6825cdf6254f8b48fafb2"
|
||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
|
@ -1682,6 +1682,8 @@ dependencies = [
|
|||
name = "uu_base64"
|
||||
version = "0.0.6"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"uu_base32",
|
||||
"uucore",
|
||||
"uucore_procs",
|
||||
]
|
||||
|
@ -2044,6 +2046,7 @@ dependencies = [
|
|||
name = "uu_link"
|
||||
version = "0.0.6"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"libc",
|
||||
"uucore",
|
||||
"uucore_procs",
|
||||
|
@ -2241,6 +2244,7 @@ dependencies = [
|
|||
name = "uu_pinky"
|
||||
version = "0.0.6"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"uucore",
|
||||
"uucore_procs",
|
||||
]
|
||||
|
|
|
@ -7,19 +7,14 @@
|
|||
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use std::io::{stdin, Read};
|
||||
|
||||
use uucore::encoding::Format;
|
||||
use uucore::InvalidEncodingHandling;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{stdin, BufReader};
|
||||
use std::path::Path;
|
||||
pub mod base_common;
|
||||
|
||||
use clap::{App, Arg};
|
||||
|
||||
mod base_common;
|
||||
|
||||
static SUMMARY: &str = "Base32 encode or decode FILE, or standard input, to standard output.";
|
||||
static LONG_HELP: &str = "
|
||||
static ABOUT: &str = "
|
||||
With no FILE, or when FILE is -, read standard input.
|
||||
|
||||
The data are encoded as described for the base32 alphabet in RFC
|
||||
|
@ -30,126 +25,41 @@ static LONG_HELP: &str = "
|
|||
";
|
||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
static BASE_CMD_PARSE_ERROR: i32 = 1;
|
||||
|
||||
fn get_usage() -> String {
|
||||
format!("{0} [OPTION]... [FILE]", executable!())
|
||||
}
|
||||
|
||||
pub mod options {
|
||||
pub static DECODE: &str = "decode";
|
||||
pub static WRAP: &str = "wrap";
|
||||
pub static IGNORE_GARBAGE: &str = "ignore-garbage";
|
||||
pub static FILE: &str = "file";
|
||||
}
|
||||
|
||||
struct Config {
|
||||
decode: bool,
|
||||
ignore_garbage: bool,
|
||||
wrap_cols: Option<usize>,
|
||||
to_read: Option<String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
fn from(options: clap::ArgMatches) -> Config {
|
||||
let file: Option<String> = match options.values_of(options::FILE) {
|
||||
Some(mut values) => {
|
||||
let name = values.next().unwrap();
|
||||
if values.len() != 0 {
|
||||
crash!(3, "extra operand ‘{}’", name);
|
||||
}
|
||||
|
||||
if name == "-" {
|
||||
None
|
||||
} else {
|
||||
if !Path::exists(Path::new(name)) {
|
||||
crash!(2, "{}: No such file or directory", name);
|
||||
}
|
||||
Some(name.to_owned())
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
let cols = match options.value_of(options::WRAP) {
|
||||
Some(num) => match num.parse::<usize>() {
|
||||
Ok(n) => Some(n),
|
||||
Err(e) => {
|
||||
crash!(1, "invalid wrap size: ‘{}’: {}", num, e);
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
|
||||
Config {
|
||||
decode: options.is_present(options::DECODE),
|
||||
ignore_garbage: options.is_present(options::IGNORE_GARBAGE),
|
||||
wrap_cols: cols,
|
||||
to_read: file,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
let format = Format::Base32;
|
||||
let usage = get_usage();
|
||||
let app = App::new(executable!())
|
||||
.version(VERSION)
|
||||
.about(SUMMARY)
|
||||
.usage(&usage[..])
|
||||
.about(LONG_HELP)
|
||||
// Format arguments.
|
||||
.arg(
|
||||
Arg::with_name(options::DECODE)
|
||||
.short("d")
|
||||
.long(options::DECODE)
|
||||
.help("decode data"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::IGNORE_GARBAGE)
|
||||
.short("i")
|
||||
.long(options::IGNORE_GARBAGE)
|
||||
.help("when decoding, ignore non-alphabetic characters"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::WRAP)
|
||||
.short("w")
|
||||
.long(options::WRAP)
|
||||
.takes_value(true)
|
||||
.help(
|
||||
"wrap encoded lines after COLS character (default 76, 0 to disable wrapping)",
|
||||
),
|
||||
)
|
||||
// "multiple" arguments are used to check whether there is more than one
|
||||
// file passed in.
|
||||
.arg(Arg::with_name(options::FILE).index(1).multiple(true));
|
||||
let name = executable!();
|
||||
|
||||
let arg_list = args
|
||||
.collect_str(InvalidEncodingHandling::ConvertLossy)
|
||||
.accept_any();
|
||||
let config: Config = Config::from(app.get_matches_from(arg_list));
|
||||
match config.to_read {
|
||||
// Read from file.
|
||||
Some(name) => {
|
||||
let file_buf = safe_unwrap!(File::open(Path::new(&name)));
|
||||
let mut input = BufReader::new(file_buf);
|
||||
base_common::handle_input(
|
||||
&mut input,
|
||||
format,
|
||||
config.wrap_cols,
|
||||
config.ignore_garbage,
|
||||
config.decode,
|
||||
);
|
||||
let config_result: Result<base_common::Config, String> =
|
||||
base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage);
|
||||
|
||||
if config_result.is_err() {
|
||||
match config_result {
|
||||
Ok(_) => panic!(),
|
||||
Err(s) => crash!(BASE_CMD_PARSE_ERROR, "{}", s),
|
||||
}
|
||||
// stdin
|
||||
None => {
|
||||
base_common::handle_input(
|
||||
&mut stdin().lock(),
|
||||
format,
|
||||
config.wrap_cols,
|
||||
config.ignore_garbage,
|
||||
config.decode,
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Create a reference to stdin so we can return a locked stdin from
|
||||
// parse_base_cmd_args
|
||||
let stdin_raw = stdin();
|
||||
let config = config_result.unwrap();
|
||||
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw);
|
||||
|
||||
base_common::handle_input(
|
||||
&mut input,
|
||||
format,
|
||||
config.wrap_cols,
|
||||
config.ignore_garbage,
|
||||
config.decode,
|
||||
name,
|
||||
);
|
||||
|
||||
0
|
||||
}
|
||||
|
|
|
@ -10,6 +10,122 @@
|
|||
use std::io::{stdout, Read, Write};
|
||||
|
||||
use uucore::encoding::{wrap_print, Data, Format};
|
||||
use uucore::InvalidEncodingHandling;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, Stdin};
|
||||
use std::path::Path;
|
||||
|
||||
use clap::{App, Arg};
|
||||
|
||||
// Config.
|
||||
pub struct Config {
|
||||
pub decode: bool,
|
||||
pub ignore_garbage: bool,
|
||||
pub wrap_cols: Option<usize>,
|
||||
pub to_read: Option<String>,
|
||||
}
|
||||
|
||||
pub mod options {
|
||||
pub static DECODE: &str = "decode";
|
||||
pub static WRAP: &str = "wrap";
|
||||
pub static IGNORE_GARBAGE: &str = "ignore-garbage";
|
||||
pub static FILE: &str = "file";
|
||||
}
|
||||
|
||||
impl Config {
|
||||
fn from(options: clap::ArgMatches) -> Result<Config, String> {
|
||||
let file: Option<String> = match options.values_of(options::FILE) {
|
||||
Some(mut values) => {
|
||||
let name = values.next().unwrap();
|
||||
if values.len() != 0 {
|
||||
return Err(format!("extra operand ‘{}’", name));
|
||||
}
|
||||
|
||||
if name == "-" {
|
||||
None
|
||||
} else {
|
||||
if !Path::exists(Path::new(name)) {
|
||||
return Err(format!("{}: No such file or directory", name));
|
||||
}
|
||||
Some(name.to_owned())
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
let cols = match options.value_of(options::WRAP) {
|
||||
Some(num) => match num.parse::<usize>() {
|
||||
Ok(n) => Some(n),
|
||||
Err(e) => {
|
||||
return Err(format!("Invalid wrap size: ‘{}’: {}", num, e));
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
|
||||
Ok(Config {
|
||||
decode: options.is_present(options::DECODE),
|
||||
ignore_garbage: options.is_present(options::IGNORE_GARBAGE),
|
||||
wrap_cols: cols,
|
||||
to_read: file,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_base_cmd_args(
|
||||
args: impl uucore::Args,
|
||||
name: &str,
|
||||
version: &str,
|
||||
about: &str,
|
||||
usage: &str,
|
||||
) -> Result<Config, String> {
|
||||
let app = App::new(name)
|
||||
.version(version)
|
||||
.about(about)
|
||||
.usage(usage)
|
||||
// Format arguments.
|
||||
.arg(
|
||||
Arg::with_name(options::DECODE)
|
||||
.short("d")
|
||||
.long(options::DECODE)
|
||||
.help("decode data"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::IGNORE_GARBAGE)
|
||||
.short("i")
|
||||
.long(options::IGNORE_GARBAGE)
|
||||
.help("when decoding, ignore non-alphabetic characters"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::WRAP)
|
||||
.short("w")
|
||||
.long(options::WRAP)
|
||||
.takes_value(true)
|
||||
.help(
|
||||
"wrap encoded lines after COLS character (default 76, 0 to disable wrapping)",
|
||||
),
|
||||
)
|
||||
// "multiple" arguments are used to check whether there is more than one
|
||||
// file passed in.
|
||||
.arg(Arg::with_name(options::FILE).index(1).multiple(true));
|
||||
let arg_list = args
|
||||
.collect_str(InvalidEncodingHandling::ConvertLossy)
|
||||
.accept_any();
|
||||
Config::from(app.get_matches_from(arg_list))
|
||||
}
|
||||
|
||||
pub fn get_input<'a>(config: &Config, stdin_ref: &'a Stdin) -> Box<dyn Read + 'a> {
|
||||
match &config.to_read {
|
||||
Some(name) => {
|
||||
let file_buf = safe_unwrap!(File::open(Path::new(name)));
|
||||
Box::new(BufReader::new(file_buf)) // as Box<dyn Read>
|
||||
}
|
||||
None => {
|
||||
Box::new(stdin_ref.lock()) // as Box<dyn Read>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_input<R: Read>(
|
||||
input: &mut R,
|
||||
|
@ -17,6 +133,7 @@ pub fn handle_input<R: Read>(
|
|||
line_wrap: Option<usize>,
|
||||
ignore_garbage: bool,
|
||||
decode: bool,
|
||||
name: &str,
|
||||
) {
|
||||
let mut data = Data::new(input, format).ignore_garbage(ignore_garbage);
|
||||
if let Some(wrap) = line_wrap {
|
||||
|
@ -31,10 +148,14 @@ pub fn handle_input<R: Read>(
|
|||
Ok(s) => {
|
||||
if stdout().write_all(&s).is_err() {
|
||||
// on windows console, writing invalid utf8 returns an error
|
||||
crash!(1, "Cannot write non-utf8 data");
|
||||
eprintln!("{}: error: Cannot write non-utf8 data", name);
|
||||
exit!(1)
|
||||
}
|
||||
}
|
||||
Err(_) => crash!(1, "invalid input"),
|
||||
Err(_) => {
|
||||
eprintln!("{}: error: invalid input", name);
|
||||
exit!(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,8 +15,10 @@ edition = "2018"
|
|||
path = "src/base64.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33"
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features = ["encoding"] }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
uu_base32 = { version=">=0.0.6", package="uu_base32", path="../base32"}
|
||||
|
||||
[[bin]]
|
||||
name = "base64"
|
||||
|
|
|
@ -8,14 +8,14 @@
|
|||
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use uu_base32::base_common;
|
||||
|
||||
use uucore::encoding::Format;
|
||||
use uucore::InvalidEncodingHandling;
|
||||
|
||||
mod base_common;
|
||||
use std::io::{stdin, Read};
|
||||
|
||||
static SYNTAX: &str = "[OPTION]... [FILE]";
|
||||
static SUMMARY: &str = "Base64 encode or decode FILE, or standard input, to standard output.";
|
||||
static LONG_HELP: &str = "
|
||||
static ABOUT: &str = "
|
||||
With no FILE, or when FILE is -, read standard input.
|
||||
|
||||
The data are encoded as described for the base64 alphabet in RFC
|
||||
|
@ -24,14 +24,42 @@ static LONG_HELP: &str = "
|
|||
to attempt to recover from any other non-alphabet bytes in the
|
||||
encoded stream.
|
||||
";
|
||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
static BASE_CMD_PARSE_ERROR: i32 = 1;
|
||||
|
||||
fn get_usage() -> String {
|
||||
format!("{0} [OPTION]... [FILE]", executable!())
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
base_common::execute(
|
||||
args.collect_str(InvalidEncodingHandling::Ignore)
|
||||
.accept_any(),
|
||||
SYNTAX,
|
||||
SUMMARY,
|
||||
LONG_HELP,
|
||||
Format::Base64,
|
||||
)
|
||||
let format = Format::Base64;
|
||||
let usage = get_usage();
|
||||
let name = executable!();
|
||||
let config_result: Result<base_common::Config, String> =
|
||||
base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage);
|
||||
|
||||
if config_result.is_err() {
|
||||
match config_result {
|
||||
Ok(_) => panic!(),
|
||||
Err(s) => crash!(BASE_CMD_PARSE_ERROR, "{}", s),
|
||||
}
|
||||
}
|
||||
|
||||
// Create a reference to stdin so we can return a locked stdin from
|
||||
// parse_base_cmd_args
|
||||
let stdin_raw = stdin();
|
||||
let config = config_result.unwrap();
|
||||
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw);
|
||||
|
||||
base_common::handle_input(
|
||||
&mut input,
|
||||
format,
|
||||
config.wrap_cols,
|
||||
config.ignore_garbage,
|
||||
config.decode,
|
||||
name,
|
||||
);
|
||||
|
||||
0
|
||||
}
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// (c) Jordy Dickinson <jordy.dickinson@gmail.com>
|
||||
// (c) Jian Zeng <anonymousknight96@gmail.com>
|
||||
// (c) Alex Lyon <arcterus@mail.com>
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE file
|
||||
// that was distributed with this source code.
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{stdin, stdout, BufReader, Read, Write};
|
||||
use std::path::Path;
|
||||
|
||||
use uucore::encoding::{wrap_print, Data, Format};
|
||||
|
||||
pub fn execute(
|
||||
args: Vec<String>,
|
||||
syntax: &str,
|
||||
summary: &str,
|
||||
long_help: &str,
|
||||
format: Format,
|
||||
) -> i32 {
|
||||
let matches = app!(syntax, summary, long_help)
|
||||
.optflag("d", "decode", "decode data")
|
||||
.optflag(
|
||||
"i",
|
||||
"ignore-garbage",
|
||||
"when decoding, ignore non-alphabetic characters",
|
||||
)
|
||||
.optopt(
|
||||
"w",
|
||||
"wrap",
|
||||
"wrap encoded lines after COLS character (default 76, 0 to disable wrapping)",
|
||||
"COLS",
|
||||
)
|
||||
.parse(args);
|
||||
|
||||
let line_wrap = matches.opt_str("wrap").map(|s| match s.parse() {
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
crash!(1, "invalid wrap size: ‘{}’: {}", s, e);
|
||||
}
|
||||
});
|
||||
let ignore_garbage = matches.opt_present("ignore-garbage");
|
||||
let decode = matches.opt_present("decode");
|
||||
|
||||
if matches.free.len() > 1 {
|
||||
show_usage_error!("extra operand ‘{}’", matches.free[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if matches.free.is_empty() || &matches.free[0][..] == "-" {
|
||||
let stdin_raw = stdin();
|
||||
handle_input(
|
||||
&mut stdin_raw.lock(),
|
||||
format,
|
||||
line_wrap,
|
||||
ignore_garbage,
|
||||
decode,
|
||||
);
|
||||
} else {
|
||||
let path = Path::new(matches.free[0].as_str());
|
||||
let file_buf = safe_unwrap!(File::open(&path));
|
||||
let mut input = BufReader::new(file_buf);
|
||||
handle_input(&mut input, format, line_wrap, ignore_garbage, decode);
|
||||
};
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
fn handle_input<R: Read>(
|
||||
input: &mut R,
|
||||
format: Format,
|
||||
line_wrap: Option<usize>,
|
||||
ignore_garbage: bool,
|
||||
decode: bool,
|
||||
) {
|
||||
let mut data = Data::new(input, format).ignore_garbage(ignore_garbage);
|
||||
if let Some(wrap) = line_wrap {
|
||||
data = data.line_wrap(wrap);
|
||||
}
|
||||
|
||||
if !decode {
|
||||
let encoded = data.encode();
|
||||
wrap_print(&data, encoded);
|
||||
} else {
|
||||
match data.decode() {
|
||||
Ok(s) => {
|
||||
if stdout().write_all(&s).is_err() {
|
||||
// on windows console, writing invalid utf8 returns an error
|
||||
crash!(1, "Cannot write non-utf8 data");
|
||||
}
|
||||
}
|
||||
Err(_) => crash!(1, "invalid input"),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -81,7 +81,7 @@ fn copy_exact(read_fd: RawFd, write_fd: RawFd, num_bytes: usize) -> nix::Result<
|
|||
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])?;
|
||||
let written = unistd::write(write_fd, &buf[..read])?;
|
||||
left -= written;
|
||||
if left == 0 {
|
||||
break;
|
||||
|
|
|
@ -551,6 +551,10 @@ impl FromStr for Attribute {
|
|||
fn add_all_attributes() -> Vec<Attribute> {
|
||||
use Attribute::*;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let attr = vec![Ownership, Timestamps, Context, Xattr, Links];
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let mut attr = vec![Ownership, Timestamps, Context, Xattr, Links];
|
||||
|
||||
#[cfg(unix)]
|
||||
|
|
|
@ -348,7 +348,7 @@ fn set_system_datetime(_date: DateTime<Utc>) -> i32 {
|
|||
#[cfg(target_os = "macos")]
|
||||
fn set_system_datetime(_date: DateTime<Utc>) -> i32 {
|
||||
eprintln!("date: setting the date is not supported by macOS");
|
||||
return 1;
|
||||
1
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
|
|
|
@ -12,37 +12,42 @@ use clap::{App, Arg};
|
|||
use std::path::Path;
|
||||
use uucore::InvalidEncodingHandling;
|
||||
|
||||
static NAME: &str = "dirname";
|
||||
static SYNTAX: &str = "[OPTION] NAME...";
|
||||
static SUMMARY: &str = "strip last component from file name";
|
||||
static ABOUT: &str = "strip last component from file name";
|
||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
static LONG_HELP: &str = "
|
||||
Output each NAME with its last non-slash component and trailing slashes
|
||||
removed; if NAME contains no /'s, output '.' (meaning the current
|
||||
directory).
|
||||
";
|
||||
|
||||
mod options {
|
||||
pub const ZERO: &str = "zero";
|
||||
pub const DIR: &str = "dir";
|
||||
}
|
||||
|
||||
fn get_usage() -> String {
|
||||
format!("{0} [OPTION] NAME...", executable!())
|
||||
}
|
||||
|
||||
fn get_long_usage() -> String {
|
||||
String::from(
|
||||
"Output each NAME with its last non-slash component and trailing slashes
|
||||
removed; if NAME contains no /'s, output '.' (meaning the current directory).",
|
||||
)
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
let args = args
|
||||
.collect_str(InvalidEncodingHandling::ConvertLossy)
|
||||
.accept_any();
|
||||
|
||||
let usage = get_usage();
|
||||
let after_help = get_long_usage();
|
||||
|
||||
let matches = App::new(executable!())
|
||||
.name(NAME)
|
||||
.usage(SYNTAX)
|
||||
.about(SUMMARY)
|
||||
.after_help(LONG_HELP)
|
||||
.about(ABOUT)
|
||||
.usage(&usage[..])
|
||||
.after_help(&after_help[..])
|
||||
.version(VERSION)
|
||||
.arg(
|
||||
Arg::with_name(options::ZERO)
|
||||
.short(options::ZERO)
|
||||
.long(options::ZERO)
|
||||
.short("z")
|
||||
.takes_value(false)
|
||||
.help("separate output with NUL rather than newline"),
|
||||
)
|
||||
.arg(Arg::with_name(options::DIR).hidden(true).multiple(true))
|
||||
|
|
|
@ -172,7 +172,7 @@ pub fn factor(mut n: u64) -> Factors {
|
|||
#[cfg(feature = "coz")]
|
||||
coz::end!("factorization");
|
||||
|
||||
return r;
|
||||
r
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -18,6 +18,7 @@ path = "src/link.rs"
|
|||
libc = "0.2.42"
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
clap = "2.33"
|
||||
|
||||
[[bin]]
|
||||
name = "link"
|
||||
|
|
|
@ -8,14 +8,21 @@
|
|||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use clap::{App, Arg};
|
||||
use std::fs::hard_link;
|
||||
use std::io::Error;
|
||||
use std::path::Path;
|
||||
use uucore::InvalidEncodingHandling;
|
||||
|
||||
static SYNTAX: &str = "[OPTIONS] FILE1 FILE2";
|
||||
static SUMMARY: &str = "Create a link named FILE2 to FILE1";
|
||||
static LONG_HELP: &str = "";
|
||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
static ABOUT: &str = "Call the link function to create a link named FILE2 to an existing FILE1.";
|
||||
|
||||
pub mod options {
|
||||
pub static FILES: &str = "FILES";
|
||||
}
|
||||
|
||||
fn get_usage() -> String {
|
||||
format!("{0} FILE1 FILE2", executable!())
|
||||
}
|
||||
|
||||
pub fn normalize_error_message(e: Error) -> String {
|
||||
match e.raw_os_error() {
|
||||
|
@ -25,16 +32,27 @@ pub fn normalize_error_message(e: Error) -> String {
|
|||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
let matches = app!(SYNTAX, SUMMARY, LONG_HELP).parse(
|
||||
args.collect_str(InvalidEncodingHandling::Ignore)
|
||||
.accept_any(),
|
||||
);
|
||||
if matches.free.len() != 2 {
|
||||
crash!(1, "{}", msg_wrong_number_of_arguments!(2));
|
||||
}
|
||||
let usage = get_usage();
|
||||
let matches = App::new(executable!())
|
||||
.version(VERSION)
|
||||
.about(ABOUT)
|
||||
.usage(&usage[..])
|
||||
.arg(
|
||||
Arg::with_name(options::FILES)
|
||||
.hidden(true)
|
||||
.required(true)
|
||||
.min_values(2)
|
||||
.max_values(2)
|
||||
.takes_value(true),
|
||||
)
|
||||
.get_matches_from(args);
|
||||
|
||||
let old = Path::new(&matches.free[0]);
|
||||
let new = Path::new(&matches.free[1]);
|
||||
let files: Vec<_> = matches
|
||||
.values_of_os(options::FILES)
|
||||
.unwrap_or_default()
|
||||
.collect();
|
||||
let old = Path::new(files[0]);
|
||||
let new = Path::new(files[1]);
|
||||
|
||||
match hard_link(old, new) {
|
||||
Ok(_) => 0,
|
||||
|
|
|
@ -36,6 +36,8 @@ args="$@"
|
|||
hyperfine "ls $args" "target/release/coreutils ls $args"
|
||||
```
|
||||
|
||||
**Note**: No localization is currently implemented. This means that the comparison above is not really fair. We can fix this by setting `LC_ALL=C`, so GNU `ls` can ignore localization.
|
||||
|
||||
## Checking system call count
|
||||
|
||||
- Another thing to look at would be system calls count using strace (on linux) or equivalent on other operating systems.
|
||||
|
|
|
@ -39,8 +39,6 @@ use std::{
|
|||
time::Duration,
|
||||
};
|
||||
|
||||
use chrono;
|
||||
|
||||
use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
|
||||
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
@ -1122,14 +1120,21 @@ impl PathData {
|
|||
fn new(
|
||||
p_buf: PathBuf,
|
||||
file_type: Option<std::io::Result<FileType>>,
|
||||
file_name: Option<String>,
|
||||
config: &Config,
|
||||
command_line: bool,
|
||||
) -> Self {
|
||||
let name = p_buf
|
||||
.file_name()
|
||||
.unwrap_or_else(|| p_buf.iter().next_back().unwrap())
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
// We cannot use `Path::ends_with` or `Path::Components`, because they remove occurrences of '.'
|
||||
// For '..', the filename is None
|
||||
let name = if let Some(name) = file_name {
|
||||
name
|
||||
} else {
|
||||
p_buf
|
||||
.file_name()
|
||||
.unwrap_or_else(|| p_buf.iter().next_back().unwrap())
|
||||
.to_string_lossy()
|
||||
.into_owned()
|
||||
};
|
||||
let must_dereference = match &config.dereference {
|
||||
Dereference::All => true,
|
||||
Dereference::Args => command_line,
|
||||
|
@ -1192,7 +1197,7 @@ fn list(locs: Vec<String>, config: Config) -> i32 {
|
|||
continue;
|
||||
}
|
||||
|
||||
let path_data = PathData::new(p, None, &config, true);
|
||||
let path_data = PathData::new(p, None, None, &config, true);
|
||||
|
||||
let show_dir_contents = if let Some(ft) = path_data.file_type() {
|
||||
!config.directory && ft.is_dir()
|
||||
|
@ -1237,14 +1242,8 @@ fn sort_entries(entries: &mut Vec<PathData>, config: &Config) {
|
|||
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
|
||||
Sort::Name => entries.sort_by_cached_key(|k| {
|
||||
let has_dot: bool = k.file_name.starts_with('.');
|
||||
let filename_nodot: &str = &k.file_name[if has_dot { 1 } else { 0 }..];
|
||||
// We want hidden files to appear before regular files of the same
|
||||
// name, so we need to negate the "has_dot" variable.
|
||||
(filename_nodot.to_lowercase(), !has_dot)
|
||||
}),
|
||||
Sort::Version => entries.sort_by(|k, j| version_cmp::version_cmp(&k.p_buf, &j.p_buf)),
|
||||
Sort::Name => entries.sort_by(|a, b| a.file_name.cmp(&b.file_name)),
|
||||
Sort::Version => entries.sort_by(|a, b| version_cmp::version_cmp(&a.p_buf, &b.p_buf)),
|
||||
Sort::Extension => entries.sort_by(|a, b| {
|
||||
a.p_buf
|
||||
.extension()
|
||||
|
@ -1283,8 +1282,14 @@ fn should_display(entry: &DirEntry, config: &Config) -> bool {
|
|||
fn enter_directory(dir: &PathData, config: &Config, out: &mut BufWriter<Stdout>) {
|
||||
let mut entries: Vec<_> = if config.files == Files::All {
|
||||
vec![
|
||||
PathData::new(dir.p_buf.join("."), None, config, false),
|
||||
PathData::new(dir.p_buf.join(".."), None, config, false),
|
||||
PathData::new(
|
||||
dir.p_buf.clone(),
|
||||
Some(Ok(*dir.file_type().unwrap())),
|
||||
Some(".".into()),
|
||||
config,
|
||||
false,
|
||||
),
|
||||
PathData::new(dir.p_buf.join(".."), None, Some("..".into()), config, false),
|
||||
]
|
||||
} else {
|
||||
vec![]
|
||||
|
@ -1293,7 +1298,7 @@ fn enter_directory(dir: &PathData, config: &Config, out: &mut BufWriter<Stdout>)
|
|||
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))
|
||||
.map(|e| PathData::new(DirEntry::path(&e), Some(e.file_type()), None, config, false))
|
||||
.collect();
|
||||
|
||||
sort_entries(&mut temp, config);
|
||||
|
|
|
@ -17,6 +17,7 @@ path = "src/pinky.rs"
|
|||
[dependencies]
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["utmpx", "entries"] }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
clap = "2.33.3"
|
||||
|
||||
[[bin]]
|
||||
name = "pinky"
|
||||
|
|
|
@ -19,67 +19,110 @@ use std::io::BufReader;
|
|||
use std::fs::File;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
use clap::{App, Arg};
|
||||
use std::path::PathBuf;
|
||||
use uucore::InvalidEncodingHandling;
|
||||
|
||||
static SYNTAX: &str = "[OPTION]... [USER]...";
|
||||
static SUMMARY: &str = "A lightweight 'finger' program; print user information.";
|
||||
|
||||
const BUFSIZE: usize = 1024;
|
||||
|
||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
static ABOUT: &str = "pinky - lightweight finger";
|
||||
|
||||
mod options {
|
||||
pub const LONG_FORMAT: &str = "long_format";
|
||||
pub const OMIT_HOME_DIR: &str = "omit_home_dir";
|
||||
pub const OMIT_PROJECT_FILE: &str = "omit_project_file";
|
||||
pub const OMIT_PLAN_FILE: &str = "omit_plan_file";
|
||||
pub const SHORT_FORMAT: &str = "short_format";
|
||||
pub const OMIT_HEADINGS: &str = "omit_headings";
|
||||
pub const OMIT_NAME: &str = "omit_name";
|
||||
pub const OMIT_NAME_HOST: &str = "omit_name_host";
|
||||
pub const OMIT_NAME_HOST_TIME: &str = "omit_name_host_time";
|
||||
pub const USER: &str = "user";
|
||||
}
|
||||
|
||||
fn get_usage() -> String {
|
||||
format!("{0} [OPTION]... [USER]...", executable!())
|
||||
}
|
||||
|
||||
fn get_long_usage() -> String {
|
||||
format!(
|
||||
"A lightweight 'finger' program; print user information.\n\
|
||||
The utmp file will be {}.",
|
||||
utmpx::DEFAULT_FILE
|
||||
)
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
let args = args
|
||||
.collect_str(InvalidEncodingHandling::Ignore)
|
||||
.accept_any();
|
||||
|
||||
let long_help = &format!(
|
||||
"
|
||||
-l produce long format output for the specified USERs
|
||||
-b omit the user's home directory and shell in long format
|
||||
-h omit the user's project file in long format
|
||||
-p omit the user's plan file in long format
|
||||
-s do short format output, this is the default
|
||||
-f omit the line of column headings in short format
|
||||
-w omit the user's full name in short format
|
||||
-i omit the user's full name and remote host in short format
|
||||
-q omit the user's full name, remote host and idle time
|
||||
in short format
|
||||
--help display this help and exit
|
||||
--version output version information and exit
|
||||
let usage = get_usage();
|
||||
let after_help = get_long_usage();
|
||||
|
||||
The utmp file will be {}",
|
||||
utmpx::DEFAULT_FILE
|
||||
);
|
||||
let mut opts = app!(SYNTAX, SUMMARY, &long_help);
|
||||
opts.optflag(
|
||||
"l",
|
||||
"",
|
||||
"produce long format output for the specified USERs",
|
||||
);
|
||||
opts.optflag(
|
||||
"b",
|
||||
"",
|
||||
"omit the user's home directory and shell in long format",
|
||||
);
|
||||
opts.optflag("h", "", "omit the user's project file in long format");
|
||||
opts.optflag("p", "", "omit the user's plan file in long format");
|
||||
opts.optflag("s", "", "do short format output, this is the default");
|
||||
opts.optflag("f", "", "omit the line of column headings in short format");
|
||||
opts.optflag("w", "", "omit the user's full name in short format");
|
||||
opts.optflag(
|
||||
"i",
|
||||
"",
|
||||
"omit the user's full name and remote host in short format",
|
||||
);
|
||||
opts.optflag(
|
||||
"q",
|
||||
"",
|
||||
"omit the user's full name, remote host and idle time in short format",
|
||||
);
|
||||
opts.optflag("", "help", "display this help and exit");
|
||||
opts.optflag("", "version", "output version information and exit");
|
||||
let matches = App::new(executable!())
|
||||
.version(VERSION)
|
||||
.about(ABOUT)
|
||||
.usage(&usage[..])
|
||||
.after_help(&after_help[..])
|
||||
.arg(
|
||||
Arg::with_name(options::LONG_FORMAT)
|
||||
.short("l")
|
||||
.requires(options::USER)
|
||||
.help("produce long format output for the specified USERs"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::OMIT_HOME_DIR)
|
||||
.short("b")
|
||||
.help("omit the user's home directory and shell in long format"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::OMIT_PROJECT_FILE)
|
||||
.short("h")
|
||||
.help("omit the user's project file in long format"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::OMIT_PLAN_FILE)
|
||||
.short("p")
|
||||
.help("omit the user's plan file in long format"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::SHORT_FORMAT)
|
||||
.short("s")
|
||||
.help("do short format output, this is the default"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::OMIT_HEADINGS)
|
||||
.short("f")
|
||||
.help("omit the line of column headings in short format"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::OMIT_NAME)
|
||||
.short("w")
|
||||
.help("omit the user's full name in short format"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::OMIT_NAME_HOST)
|
||||
.short("i")
|
||||
.help("omit the user's full name and remote host in short format"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::OMIT_NAME_HOST_TIME)
|
||||
.short("q")
|
||||
.help("omit the user's full name, remote host and idle time in short format"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::USER)
|
||||
.takes_value(true)
|
||||
.multiple(true),
|
||||
)
|
||||
.get_matches_from(args);
|
||||
|
||||
let matches = opts.parse(args);
|
||||
let users: Vec<String> = matches
|
||||
.values_of(options::USER)
|
||||
.map(|v| v.map(ToString::to_string).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
// If true, display the hours:minutes since each user has touched
|
||||
// the keyboard, or blank if within the last minute, or days followed
|
||||
|
@ -87,45 +130,40 @@ The utmp file will be {}",
|
|||
let mut include_idle = true;
|
||||
|
||||
// If true, display a line at the top describing each field.
|
||||
let include_heading = !matches.opt_present("f");
|
||||
let include_heading = !matches.is_present(options::OMIT_HEADINGS);
|
||||
|
||||
// if true, display the user's full name from pw_gecos.
|
||||
let mut include_fullname = true;
|
||||
|
||||
// if true, display the user's ~/.project file when doing long format.
|
||||
let include_project = !matches.opt_present("h");
|
||||
let include_project = !matches.is_present(options::OMIT_PROJECT_FILE);
|
||||
|
||||
// if true, display the user's ~/.plan file when doing long format.
|
||||
let include_plan = !matches.opt_present("p");
|
||||
let include_plan = !matches.is_present(options::OMIT_PLAN_FILE);
|
||||
|
||||
// if true, display the user's home directory and shell
|
||||
// when doing long format.
|
||||
let include_home_and_shell = !matches.opt_present("b");
|
||||
let include_home_and_shell = !matches.is_present(options::OMIT_HOME_DIR);
|
||||
|
||||
// if true, use the "short" output format.
|
||||
let do_short_format = !matches.opt_present("l");
|
||||
let do_short_format = !matches.is_present(options::LONG_FORMAT);
|
||||
|
||||
/* if true, display the ut_host field. */
|
||||
let mut include_where = true;
|
||||
|
||||
if matches.opt_present("w") {
|
||||
if matches.is_present(options::OMIT_NAME) {
|
||||
include_fullname = false;
|
||||
}
|
||||
if matches.opt_present("i") {
|
||||
if matches.is_present(options::OMIT_NAME_HOST) {
|
||||
include_fullname = false;
|
||||
include_where = false;
|
||||
}
|
||||
if matches.opt_present("q") {
|
||||
if matches.is_present(options::OMIT_NAME_HOST_TIME) {
|
||||
include_fullname = false;
|
||||
include_idle = false;
|
||||
include_where = false;
|
||||
}
|
||||
|
||||
if !do_short_format && matches.free.is_empty() {
|
||||
show_usage_error!("no username specified; at least one must be specified when using -l");
|
||||
return 1;
|
||||
}
|
||||
|
||||
let pk = Pinky {
|
||||
include_idle,
|
||||
include_heading,
|
||||
|
@ -134,7 +172,7 @@ The utmp file will be {}",
|
|||
include_plan,
|
||||
include_home_and_shell,
|
||||
include_where,
|
||||
names: matches.free,
|
||||
names: users,
|
||||
};
|
||||
|
||||
if do_short_format {
|
||||
|
|
64
src/uu/sort/src/custom_str_cmp.rs
Normal file
64
src/uu/sort/src/custom_str_cmp.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
// * This file is part of the uutils coreutils package.
|
||||
// *
|
||||
// * (c) Michael Debertol <michael.debertol..AT..gmail.com>
|
||||
// *
|
||||
// * For the full copyright and license information, please view the LICENSE
|
||||
// * file that was distributed with this source code.
|
||||
|
||||
//! Custom string comparisons.
|
||||
//!
|
||||
//! The goal is to compare strings without transforming them first (i.e. not allocating new strings)
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
fn filter_char(c: char, ignore_non_printing: bool, ignore_non_dictionary: bool) -> bool {
|
||||
if ignore_non_dictionary && !(c.is_ascii_alphanumeric() || c.is_ascii_whitespace()) {
|
||||
return false;
|
||||
}
|
||||
if ignore_non_printing && (c.is_ascii_control() || !c.is_ascii()) {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn cmp_chars(a: char, b: char, ignore_case: bool) -> Ordering {
|
||||
if ignore_case {
|
||||
a.to_ascii_uppercase().cmp(&b.to_ascii_uppercase())
|
||||
} else {
|
||||
a.cmp(&b)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn custom_str_cmp(
|
||||
a: &str,
|
||||
b: &str,
|
||||
ignore_non_printing: bool,
|
||||
ignore_non_dictionary: bool,
|
||||
ignore_case: bool,
|
||||
) -> Ordering {
|
||||
if !(ignore_case || ignore_non_dictionary || ignore_non_printing) {
|
||||
// There are no custom settings. Fall back to the default strcmp, which is faster.
|
||||
return a.cmp(&b);
|
||||
}
|
||||
let mut a_chars = a
|
||||
.chars()
|
||||
.filter(|&c| filter_char(c, ignore_non_printing, ignore_non_dictionary));
|
||||
let mut b_chars = b
|
||||
.chars()
|
||||
.filter(|&c| filter_char(c, ignore_non_printing, ignore_non_dictionary));
|
||||
loop {
|
||||
let a_char = a_chars.next();
|
||||
let b_char = b_chars.next();
|
||||
match (a_char, b_char) {
|
||||
(None, None) => return Ordering::Equal,
|
||||
(Some(_), None) => return Ordering::Greater,
|
||||
(None, Some(_)) => return Ordering::Less,
|
||||
(Some(a_char), Some(b_char)) => {
|
||||
let ordering = cmp_chars(a_char, b_char, ignore_case);
|
||||
if ordering != Ordering::Equal {
|
||||
return ordering;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,11 +15,13 @@
|
|||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
mod custom_str_cmp;
|
||||
mod external_sort;
|
||||
mod numeric_str_cmp;
|
||||
|
||||
use clap::{App, Arg};
|
||||
use external_sort::ext_sort;
|
||||
use custom_str_cmp::custom_str_cmp;
|
||||
use fnv::FnvHasher;
|
||||
use itertools::Itertools;
|
||||
use numeric_str_cmp::{numeric_str_cmp, NumInfo, NumInfoParseSettings};
|
||||
|
@ -205,33 +207,23 @@ impl From<&GlobalSettings> for KeySettings {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
/// Represents the string selected by a FieldSelector.
|
||||
enum SelectionRange {
|
||||
/// If we had to transform this selection, we have to store a new string.
|
||||
String(String),
|
||||
/// If there was no transformation, we can store an index into the line.
|
||||
ByIndex(Range<usize>),
|
||||
struct SelectionRange {
|
||||
range: Range<usize>,
|
||||
}
|
||||
|
||||
impl SelectionRange {
|
||||
fn new(range: Range<usize>) -> Self {
|
||||
Self { range }
|
||||
}
|
||||
|
||||
/// Gets the actual string slice represented by this Selection.
|
||||
fn get_str<'a>(&'a self, line: &'a str) -> &'a str {
|
||||
match self {
|
||||
SelectionRange::String(string) => string.as_str(),
|
||||
SelectionRange::ByIndex(range) => &line[range.to_owned()],
|
||||
}
|
||||
fn get_str<'a>(&self, line: &'a str) -> &'a str {
|
||||
&line[self.range.to_owned()]
|
||||
}
|
||||
|
||||
fn shorten(&mut self, new_range: Range<usize>) {
|
||||
match self {
|
||||
SelectionRange::String(string) => {
|
||||
string.drain(new_range.end..);
|
||||
string.drain(..new_range.start);
|
||||
}
|
||||
SelectionRange::ByIndex(range) => {
|
||||
range.end = range.start + new_range.end;
|
||||
range.start += new_range.start;
|
||||
}
|
||||
}
|
||||
self.range.end = self.range.start + new_range.end;
|
||||
self.range.start += new_range.start;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -302,14 +294,8 @@ impl Line {
|
|||
.selectors
|
||||
.iter()
|
||||
.map(|selector| {
|
||||
let range = selector.get_selection(&line, fields.as_deref());
|
||||
let mut range = if let Some(transformed) =
|
||||
transform(&line[range.to_owned()], &selector.settings)
|
||||
{
|
||||
SelectionRange::String(transformed)
|
||||
} else {
|
||||
SelectionRange::ByIndex(range)
|
||||
};
|
||||
let mut range =
|
||||
SelectionRange::new(selector.get_selection(&line, fields.as_deref()));
|
||||
let num_cache = if selector.settings.mode == SortMode::Numeric
|
||||
|| selector.settings.mode == SortMode::HumanNumeric
|
||||
{
|
||||
|
@ -459,34 +445,6 @@ impl Line {
|
|||
}
|
||||
}
|
||||
|
||||
/// Transform this line. Returns None if there's no need to transform.
|
||||
fn transform(line: &str, settings: &KeySettings) -> Option<String> {
|
||||
let mut transformed = None;
|
||||
if settings.ignore_case {
|
||||
transformed = Some(line.to_uppercase());
|
||||
}
|
||||
if settings.ignore_blanks {
|
||||
transformed = Some(
|
||||
transformed
|
||||
.as_deref()
|
||||
.unwrap_or(line)
|
||||
.trim_start()
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
if settings.dictionary_order {
|
||||
transformed = Some(remove_nondictionary_chars(
|
||||
transformed.as_deref().unwrap_or(line),
|
||||
));
|
||||
}
|
||||
if settings.ignore_non_printing {
|
||||
transformed = Some(remove_nonprinting_chars(
|
||||
transformed.as_deref().unwrap_or(line),
|
||||
));
|
||||
}
|
||||
transformed
|
||||
}
|
||||
|
||||
/// Tokenize a line into fields.
|
||||
fn tokenize(line: &str, separator: Option<char>) -> Vec<Field> {
|
||||
if let Some(separator) = separator {
|
||||
|
@ -1294,12 +1252,18 @@ fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering
|
|||
(b_str, b_selection.num_cache.as_num_info()),
|
||||
),
|
||||
SortMode::GeneralNumeric => general_numeric_compare(
|
||||
general_f64_parse(&a_str[get_leading_gen(a_str)]),
|
||||
general_f64_parse(&b_str[get_leading_gen(b_str)]),
|
||||
a_selection.num_cache.as_f64(),
|
||||
b_selection.num_cache.as_f64(),
|
||||
),
|
||||
SortMode::Month => month_compare(a_str, b_str),
|
||||
SortMode::Version => version_compare(a_str, b_str),
|
||||
SortMode::Default => default_compare(a_str, b_str),
|
||||
SortMode::Default => custom_str_cmp(
|
||||
a_str,
|
||||
b_str,
|
||||
settings.ignore_non_printing,
|
||||
settings.dictionary_order,
|
||||
settings.ignore_case,
|
||||
),
|
||||
}
|
||||
};
|
||||
if cmp != Ordering::Equal {
|
||||
|
@ -1311,7 +1275,7 @@ fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering
|
|||
let cmp = if global_settings.random || global_settings.stable || global_settings.unique {
|
||||
Ordering::Equal
|
||||
} else {
|
||||
default_compare(&a.line, &b.line)
|
||||
a.line.cmp(&b.line)
|
||||
};
|
||||
|
||||
if global_settings.reverse {
|
||||
|
@ -1321,13 +1285,6 @@ fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering
|
|||
}
|
||||
}
|
||||
|
||||
// Test output against BSDs and GNU with their locale
|
||||
// env var set to lc_ctype=utf-8 to enjoy the exact same output.
|
||||
#[inline(always)]
|
||||
fn default_compare(a: &str, b: &str) -> Ordering {
|
||||
a.cmp(b)
|
||||
}
|
||||
|
||||
// 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
|
||||
// scientific notation, so we strip those lines only after the end of the following numeric string.
|
||||
|
@ -1514,22 +1471,6 @@ fn version_compare(a: &str, b: &str) -> Ordering {
|
|||
}
|
||||
}
|
||||
|
||||
fn remove_nondictionary_chars(s: &str) -> String {
|
||||
// According to GNU, dictionary chars are those of ASCII
|
||||
// and a blank is a space or a tab
|
||||
s.chars()
|
||||
.filter(|c| c.is_ascii_alphanumeric() || c.is_ascii_whitespace())
|
||||
.collect::<String>()
|
||||
}
|
||||
|
||||
fn remove_nonprinting_chars(s: &str) -> String {
|
||||
// However, GNU says nonprinting chars are more permissive.
|
||||
// All of ASCII except control chars ie, escape, newline
|
||||
s.chars()
|
||||
.filter(|c| c.is_ascii() && !c.is_ascii_control())
|
||||
.collect::<String>()
|
||||
}
|
||||
|
||||
fn print_sorted<T: Iterator<Item = Line>>(iter: T, settings: &GlobalSettings) {
|
||||
let mut file: Box<dyn Write> = match settings.outfile {
|
||||
Some(ref filename) => match File::create(Path::new(&filename)) {
|
||||
|
@ -1596,14 +1537,6 @@ mod tests {
|
|||
assert_eq!(Ordering::Equal, random_shuffle(a, b, c));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_compare() {
|
||||
let a = "your own";
|
||||
let b = "your place";
|
||||
|
||||
assert_eq!(Ordering::Less, default_compare(a, b));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_month_compare() {
|
||||
let a = "JaN";
|
||||
|
|
292
src/uu/test/src/parser.rs
Normal file
292
src/uu/test/src/parser.rs
Normal file
|
@ -0,0 +1,292 @@
|
|||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// (c) Daniel Rocco <drocco@gmail.com>
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use std::ffi::OsString;
|
||||
use std::iter::Peekable;
|
||||
|
||||
/// Represents a parsed token from a test expression
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Symbol {
|
||||
LParen,
|
||||
Bang,
|
||||
BoolOp(OsString),
|
||||
Literal(OsString),
|
||||
StringOp(OsString),
|
||||
IntOp(OsString),
|
||||
FileOp(OsString),
|
||||
StrlenOp(OsString),
|
||||
FiletestOp(OsString),
|
||||
None,
|
||||
}
|
||||
|
||||
impl Symbol {
|
||||
/// Create a new Symbol from an OsString.
|
||||
///
|
||||
/// Returns Symbol::None in place of None
|
||||
fn new(token: Option<OsString>) -> Symbol {
|
||||
match token {
|
||||
Some(s) => match s.to_string_lossy().as_ref() {
|
||||
"(" => Symbol::LParen,
|
||||
"!" => Symbol::Bang,
|
||||
"-a" | "-o" => Symbol::BoolOp(s),
|
||||
"=" | "!=" => Symbol::StringOp(s),
|
||||
"-eq" | "-ge" | "-gt" | "-le" | "-lt" | "-ne" => Symbol::IntOp(s),
|
||||
"-ef" | "-nt" | "-ot" => Symbol::FileOp(s),
|
||||
"-n" | "-z" => Symbol::StrlenOp(s),
|
||||
"-b" | "-c" | "-d" | "-e" | "-f" | "-g" | "-G" | "-h" | "-k" | "-L" | "-O"
|
||||
| "-p" | "-r" | "-s" | "-S" | "-t" | "-u" | "-w" | "-x" => Symbol::FiletestOp(s),
|
||||
_ => Symbol::Literal(s),
|
||||
},
|
||||
None => Symbol::None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert this Symbol into a Symbol::Literal, useful for cases where
|
||||
/// test treats an operator as a string operand (test has no reserved
|
||||
/// words).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `self` is Symbol::None
|
||||
fn into_literal(self) -> Symbol {
|
||||
Symbol::Literal(match self {
|
||||
Symbol::LParen => OsString::from("("),
|
||||
Symbol::Bang => OsString::from("!"),
|
||||
Symbol::BoolOp(s)
|
||||
| Symbol::Literal(s)
|
||||
| Symbol::StringOp(s)
|
||||
| Symbol::IntOp(s)
|
||||
| Symbol::FileOp(s)
|
||||
| Symbol::StrlenOp(s)
|
||||
| Symbol::FiletestOp(s) => s,
|
||||
Symbol::None => panic!(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Recursive descent parser for test, which converts a list of OsStrings
|
||||
/// (typically command line arguments) into a stack of Symbols in postfix
|
||||
/// order.
|
||||
///
|
||||
/// Grammar:
|
||||
///
|
||||
/// EXPR → TERM | EXPR BOOLOP EXPR
|
||||
/// TERM → ( EXPR )
|
||||
/// TERM → ( )
|
||||
/// TERM → ! EXPR
|
||||
/// TERM → UOP str
|
||||
/// UOP → STRLEN | FILETEST
|
||||
/// TERM → str OP str
|
||||
/// TERM → str | 𝜖
|
||||
/// OP → STRINGOP | INTOP | FILEOP
|
||||
/// STRINGOP → = | !=
|
||||
/// INTOP → -eq | -ge | -gt | -le | -lt | -ne
|
||||
/// FILEOP → -ef | -nt | -ot
|
||||
/// STRLEN → -n | -z
|
||||
/// FILETEST → -b | -c | -d | -e | -f | -g | -G | -h | -k | -L | -O | -p |
|
||||
/// -r | -s | -S | -t | -u | -w | -x
|
||||
/// BOOLOP → -a | -o
|
||||
///
|
||||
#[derive(Debug)]
|
||||
struct Parser {
|
||||
tokens: Peekable<std::vec::IntoIter<OsString>>,
|
||||
pub stack: Vec<Symbol>,
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
/// Construct a new Parser from a `Vec<OsString>` of tokens.
|
||||
fn new(tokens: Vec<OsString>) -> Parser {
|
||||
Parser {
|
||||
tokens: tokens.into_iter().peekable(),
|
||||
stack: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch the next token from the input stream as a Symbol.
|
||||
fn next_token(&mut self) -> Symbol {
|
||||
Symbol::new(self.tokens.next())
|
||||
}
|
||||
|
||||
/// Peek at the next token from the input stream, returning it as a Symbol.
|
||||
/// The stream is unchanged and will return the same Symbol on subsequent
|
||||
/// calls to `next()` or `peek()`.
|
||||
fn peek(&mut self) -> Symbol {
|
||||
Symbol::new(self.tokens.peek().map(|s| s.to_os_string()))
|
||||
}
|
||||
|
||||
/// Test if the next token in the stream is a BOOLOP (-a or -o), without
|
||||
/// removing the token from the stream.
|
||||
fn peek_is_boolop(&mut self) -> bool {
|
||||
if let Symbol::BoolOp(_) = self.peek() {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse an expression.
|
||||
///
|
||||
/// EXPR → TERM | EXPR BOOLOP EXPR
|
||||
fn expr(&mut self) {
|
||||
if !self.peek_is_boolop() {
|
||||
self.term();
|
||||
}
|
||||
self.maybe_boolop();
|
||||
}
|
||||
|
||||
/// Parse a term token and possible subsequent symbols: "(", "!", UOP,
|
||||
/// literal, or None.
|
||||
fn term(&mut self) {
|
||||
let symbol = self.next_token();
|
||||
|
||||
match symbol {
|
||||
Symbol::LParen => self.lparen(),
|
||||
Symbol::Bang => self.bang(),
|
||||
Symbol::StrlenOp(_) => self.uop(symbol),
|
||||
Symbol::FiletestOp(_) => self.uop(symbol),
|
||||
Symbol::None => self.stack.push(symbol),
|
||||
literal => self.literal(literal),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a (possibly) parenthesized expression.
|
||||
///
|
||||
/// test has no reserved keywords, so "(" will be interpreted as a literal
|
||||
/// if it is followed by nothing or a comparison operator OP.
|
||||
fn lparen(&mut self) {
|
||||
match self.peek() {
|
||||
// lparen is a literal when followed by nothing or comparison
|
||||
Symbol::None | Symbol::StringOp(_) | Symbol::IntOp(_) | Symbol::FileOp(_) => {
|
||||
self.literal(Symbol::Literal(OsString::from("(")));
|
||||
}
|
||||
// empty parenthetical
|
||||
Symbol::Literal(s) if s == ")" => {}
|
||||
_ => {
|
||||
self.expr();
|
||||
match self.next_token() {
|
||||
Symbol::Literal(s) if s == ")" => (),
|
||||
_ => panic!("expected ‘)’"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a (possibly) negated expression.
|
||||
///
|
||||
/// Example cases:
|
||||
///
|
||||
/// * `! =`: negate the result of the implicit string length test of `=`
|
||||
/// * `! = foo`: compare the literal strings `!` and `foo`
|
||||
/// * `! <expr>`: negate the result of the expression
|
||||
///
|
||||
fn bang(&mut self) {
|
||||
if let Symbol::StringOp(_) | Symbol::IntOp(_) | Symbol::FileOp(_) = self.peek() {
|
||||
// we need to peek ahead one more token to disambiguate the first
|
||||
// two cases listed above: case 1 — `! <OP as literal>` — and
|
||||
// case 2: `<! as literal> OP str`.
|
||||
let peek2 = self.tokens.clone().nth(1);
|
||||
|
||||
if peek2.is_none() {
|
||||
// op is literal
|
||||
let op = self.next_token().into_literal();
|
||||
self.stack.push(op);
|
||||
self.stack.push(Symbol::Bang);
|
||||
} else {
|
||||
// bang is literal; parsing continues with op
|
||||
self.literal(Symbol::Literal(OsString::from("!")));
|
||||
}
|
||||
} else {
|
||||
self.expr();
|
||||
self.stack.push(Symbol::Bang);
|
||||
}
|
||||
}
|
||||
|
||||
/// Peek at the next token and parse it as a BOOLOP or string literal,
|
||||
/// as appropriate.
|
||||
fn maybe_boolop(&mut self) {
|
||||
if self.peek_is_boolop() {
|
||||
let token = self.tokens.next().unwrap(); // safe because we peeked
|
||||
|
||||
// BoolOp by itself interpreted as Literal
|
||||
if let Symbol::None = self.peek() {
|
||||
self.literal(Symbol::Literal(token))
|
||||
} else {
|
||||
self.boolop(Symbol::BoolOp(token))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a Boolean expression.
|
||||
///
|
||||
/// Logical and (-a) has higher precedence than or (-o), so in an
|
||||
/// expression like `foo -o '' -a ''`, the and subexpression is evaluated
|
||||
/// first.
|
||||
fn boolop(&mut self, op: Symbol) {
|
||||
if op == Symbol::BoolOp(OsString::from("-a")) {
|
||||
self.term();
|
||||
self.stack.push(op);
|
||||
self.maybe_boolop();
|
||||
} else {
|
||||
self.expr();
|
||||
self.stack.push(op);
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a (possible) unary argument test (string length or file
|
||||
/// attribute check).
|
||||
///
|
||||
/// If a UOP is followed by nothing it is interpreted as a literal string.
|
||||
fn uop(&mut self, op: Symbol) {
|
||||
match self.next_token() {
|
||||
Symbol::None => self.stack.push(op.into_literal()),
|
||||
symbol => {
|
||||
self.stack.push(symbol.into_literal());
|
||||
self.stack.push(op);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a string literal, optionally followed by a comparison operator
|
||||
/// and a second string literal.
|
||||
fn literal(&mut self, token: Symbol) {
|
||||
self.stack.push(token.into_literal());
|
||||
|
||||
// EXPR → str OP str
|
||||
match self.peek() {
|
||||
Symbol::StringOp(_) | Symbol::IntOp(_) | Symbol::FileOp(_) => {
|
||||
let op = self.next_token();
|
||||
|
||||
match self.next_token() {
|
||||
Symbol::None => panic!("missing argument after {:?}", op),
|
||||
token => self.stack.push(token.into_literal()),
|
||||
}
|
||||
|
||||
self.stack.push(op);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parser entry point: parse the token stream `self.tokens`, storing the
|
||||
/// resulting `Symbol` stack in `self.stack`.
|
||||
fn parse(&mut self) -> Result<(), String> {
|
||||
self.expr();
|
||||
|
||||
match self.tokens.next() {
|
||||
Some(token) => Err(format!("extra argument ‘{}’", token.to_string_lossy())),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the token stream `args`, returning a `Symbol` stack representing the
|
||||
/// operations to perform in postfix order.
|
||||
pub fn parse(args: Vec<OsString>) -> Result<Vec<Symbol>, String> {
|
||||
let mut p = Parser::new(args);
|
||||
p.parse()?;
|
||||
Ok(p.stack)
|
||||
}
|
|
@ -1,144 +1,154 @@
|
|||
// * This file is part of the uutils coreutils package.
|
||||
// *
|
||||
// * (c) mahkoh (ju.orth [at] gmail [dot] com)
|
||||
// *
|
||||
// * For the full copyright and license information, please view the LICENSE
|
||||
// * file that was distributed with this source code.
|
||||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// (c) mahkoh (ju.orth [at] gmail [dot] com)
|
||||
// (c) Daniel Rocco <drocco@gmail.com>
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore (ToDO) retval paren prec subprec cond
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::str::from_utf8;
|
||||
mod parser;
|
||||
|
||||
static NAME: &str = "test";
|
||||
use parser::{parse, Symbol};
|
||||
use std::ffi::{OsStr, OsString};
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
let args: Vec<_> = args.collect();
|
||||
// This is completely disregarding valid windows paths that aren't valid unicode
|
||||
let args = args
|
||||
.iter()
|
||||
.map(|a| a.to_str().unwrap().as_bytes())
|
||||
.collect::<Vec<&[u8]>>();
|
||||
if args.is_empty() {
|
||||
return 2;
|
||||
}
|
||||
let args = if !args[0].ends_with(NAME.as_bytes()) {
|
||||
&args[1..]
|
||||
} else {
|
||||
&args[..]
|
||||
};
|
||||
let args = match args[0] {
|
||||
b"[" => match args[args.len() - 1] {
|
||||
b"]" => &args[1..args.len() - 1],
|
||||
_ => return 2,
|
||||
},
|
||||
_ => &args[1..args.len()],
|
||||
};
|
||||
let mut error = false;
|
||||
let retval = 1 - parse_expr(args, &mut error) as i32;
|
||||
if error {
|
||||
2
|
||||
} else {
|
||||
retval
|
||||
}
|
||||
}
|
||||
// TODO: handle being called as `[`
|
||||
let args: Vec<_> = args.skip(1).collect();
|
||||
|
||||
fn one(args: &[&[u8]]) -> bool {
|
||||
!args[0].is_empty()
|
||||
}
|
||||
let result = parse(args).and_then(|mut stack| eval(&mut stack));
|
||||
|
||||
fn two(args: &[&[u8]], error: &mut bool) -> bool {
|
||||
match args[0] {
|
||||
b"!" => !one(&args[1..]),
|
||||
b"-b" => path(args[1], PathCondition::BlockSpecial),
|
||||
b"-c" => path(args[1], PathCondition::CharacterSpecial),
|
||||
b"-d" => path(args[1], PathCondition::Directory),
|
||||
b"-e" => path(args[1], PathCondition::Exists),
|
||||
b"-f" => path(args[1], PathCondition::Regular),
|
||||
b"-g" => path(args[1], PathCondition::GroupIdFlag),
|
||||
b"-h" => path(args[1], PathCondition::SymLink),
|
||||
b"-L" => path(args[1], PathCondition::SymLink),
|
||||
b"-n" => one(&args[1..]),
|
||||
b"-p" => path(args[1], PathCondition::Fifo),
|
||||
b"-r" => path(args[1], PathCondition::Readable),
|
||||
b"-S" => path(args[1], PathCondition::Socket),
|
||||
b"-s" => path(args[1], PathCondition::NonEmpty),
|
||||
b"-t" => isatty(args[1]),
|
||||
b"-u" => path(args[1], PathCondition::UserIdFlag),
|
||||
b"-w" => path(args[1], PathCondition::Writable),
|
||||
b"-x" => path(args[1], PathCondition::Executable),
|
||||
b"-z" => !one(&args[1..]),
|
||||
_ => {
|
||||
*error = true;
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn three(args: &[&[u8]], error: &mut bool) -> bool {
|
||||
match args[1] {
|
||||
b"=" => args[0] == args[2],
|
||||
b"==" => args[0] == args[2],
|
||||
b"!=" => args[0] != args[2],
|
||||
b"-eq" => integers(args[0], args[2], IntegerCondition::Equal),
|
||||
b"-ne" => integers(args[0], args[2], IntegerCondition::Unequal),
|
||||
b"-gt" => integers(args[0], args[2], IntegerCondition::Greater),
|
||||
b"-ge" => integers(args[0], args[2], IntegerCondition::GreaterEqual),
|
||||
b"-lt" => integers(args[0], args[2], IntegerCondition::Less),
|
||||
b"-le" => integers(args[0], args[2], IntegerCondition::LessEqual),
|
||||
_ => match args[0] {
|
||||
b"!" => !two(&args[1..], error),
|
||||
_ => {
|
||||
*error = true;
|
||||
false
|
||||
match result {
|
||||
Ok(result) => {
|
||||
if result {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn four(args: &[&[u8]], error: &mut bool) -> bool {
|
||||
match args[0] {
|
||||
b"!" => !three(&args[1..], error),
|
||||
_ => {
|
||||
*error = true;
|
||||
false
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("test: {}", e);
|
||||
2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum IntegerCondition {
|
||||
Equal,
|
||||
Unequal,
|
||||
Greater,
|
||||
GreaterEqual,
|
||||
Less,
|
||||
LessEqual,
|
||||
}
|
||||
/// Evaluate a stack of Symbols, returning the result of the evaluation or
|
||||
/// an error message if evaluation failed.
|
||||
fn eval(stack: &mut Vec<Symbol>) -> Result<bool, String> {
|
||||
macro_rules! pop_literal {
|
||||
() => {
|
||||
match stack.pop() {
|
||||
Some(Symbol::Literal(s)) => s,
|
||||
_ => panic!(),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn integers(a: &[u8], b: &[u8], cond: IntegerCondition) -> bool {
|
||||
let (a, b): (&str, &str) = match (from_utf8(a), from_utf8(b)) {
|
||||
(Ok(a), Ok(b)) => (a, b),
|
||||
_ => return false,
|
||||
};
|
||||
let (a, b): (i64, i64) = match (a.parse(), b.parse()) {
|
||||
(Ok(a), Ok(b)) => (a, b),
|
||||
_ => return false,
|
||||
};
|
||||
match cond {
|
||||
IntegerCondition::Equal => a == b,
|
||||
IntegerCondition::Unequal => a != b,
|
||||
IntegerCondition::Greater => a > b,
|
||||
IntegerCondition::GreaterEqual => a >= b,
|
||||
IntegerCondition::Less => a < b,
|
||||
IntegerCondition::LessEqual => a <= b,
|
||||
let s = stack.pop();
|
||||
|
||||
match s {
|
||||
Some(Symbol::Bang) => {
|
||||
let result = eval(stack)?;
|
||||
|
||||
Ok(!result)
|
||||
}
|
||||
Some(Symbol::StringOp(op)) => {
|
||||
let b = stack.pop();
|
||||
let a = stack.pop();
|
||||
Ok(if op == "=" { a == b } else { a != b })
|
||||
}
|
||||
Some(Symbol::IntOp(op)) => {
|
||||
let b = pop_literal!();
|
||||
let a = pop_literal!();
|
||||
|
||||
Ok(integers(&a, &b, &op)?)
|
||||
}
|
||||
Some(Symbol::FileOp(_op)) => unimplemented!(),
|
||||
Some(Symbol::StrlenOp(op)) => {
|
||||
let s = match stack.pop() {
|
||||
Some(Symbol::Literal(s)) => s,
|
||||
Some(Symbol::None) => OsString::from(""),
|
||||
None => {
|
||||
return Ok(true);
|
||||
}
|
||||
_ => {
|
||||
return Err(format!("missing argument after ‘{:?}’", op));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(if op == "-z" {
|
||||
s.is_empty()
|
||||
} else {
|
||||
!s.is_empty()
|
||||
})
|
||||
}
|
||||
Some(Symbol::FiletestOp(op)) => {
|
||||
let op = op.to_string_lossy();
|
||||
|
||||
let f = pop_literal!();
|
||||
|
||||
Ok(match op.as_ref() {
|
||||
"-b" => path(&f, PathCondition::BlockSpecial),
|
||||
"-c" => path(&f, PathCondition::CharacterSpecial),
|
||||
"-d" => path(&f, PathCondition::Directory),
|
||||
"-e" => path(&f, PathCondition::Exists),
|
||||
"-f" => path(&f, PathCondition::Regular),
|
||||
"-g" => path(&f, PathCondition::GroupIdFlag),
|
||||
"-h" => path(&f, PathCondition::SymLink),
|
||||
"-L" => path(&f, PathCondition::SymLink),
|
||||
"-p" => path(&f, PathCondition::Fifo),
|
||||
"-r" => path(&f, PathCondition::Readable),
|
||||
"-S" => path(&f, PathCondition::Socket),
|
||||
"-s" => path(&f, PathCondition::NonEmpty),
|
||||
"-t" => isatty(&f)?,
|
||||
"-u" => path(&f, PathCondition::UserIdFlag),
|
||||
"-w" => path(&f, PathCondition::Writable),
|
||||
"-x" => path(&f, PathCondition::Executable),
|
||||
_ => panic!(),
|
||||
})
|
||||
}
|
||||
Some(Symbol::Literal(s)) => Ok(!s.is_empty()),
|
||||
Some(Symbol::None) => Ok(false),
|
||||
Some(Symbol::BoolOp(op)) => {
|
||||
let b = eval(stack)?;
|
||||
let a = eval(stack)?;
|
||||
|
||||
Ok(if op == "-a" { a && b } else { a || b })
|
||||
}
|
||||
None => Ok(false),
|
||||
_ => Err("expected value".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn isatty(fd: &[u8]) -> bool {
|
||||
from_utf8(fd)
|
||||
.ok()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.map_or(false, |i| {
|
||||
fn integers(a: &OsStr, b: &OsStr, cond: &OsStr) -> Result<bool, String> {
|
||||
let format_err = |value| format!("invalid integer ‘{}’", value);
|
||||
|
||||
let a = a.to_string_lossy();
|
||||
let a: i64 = a.parse().map_err(|_| format_err(a))?;
|
||||
|
||||
let b = b.to_string_lossy();
|
||||
let b: i64 = b.parse().map_err(|_| format_err(b))?;
|
||||
|
||||
let cond = cond.to_string_lossy();
|
||||
Ok(match cond.as_ref() {
|
||||
"-eq" => a == b,
|
||||
"-ne" => a != b,
|
||||
"-gt" => a > b,
|
||||
"-ge" => a >= b,
|
||||
"-lt" => a < b,
|
||||
"-le" => a <= b,
|
||||
_ => return Err(format!("unknown operator ‘{}’", cond)),
|
||||
})
|
||||
}
|
||||
|
||||
fn isatty(fd: &OsStr) -> Result<bool, String> {
|
||||
let fd = fd.to_string_lossy();
|
||||
|
||||
fd.parse()
|
||||
.map_err(|_| format!("invalid integer ‘{}’", fd))
|
||||
.map(|i| {
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
unsafe {
|
||||
libc::isatty(i) == 1
|
||||
|
@ -148,173 +158,6 @@ fn isatty(fd: &[u8]) -> bool {
|
|||
})
|
||||
}
|
||||
|
||||
fn dispatch(args: &mut &[&[u8]], error: &mut bool) -> bool {
|
||||
let (val, idx) = match args.len() {
|
||||
0 => {
|
||||
*error = true;
|
||||
(false, 0)
|
||||
}
|
||||
1 => (one(*args), 1),
|
||||
2 => dispatch_two(args, error),
|
||||
3 => dispatch_three(args, error),
|
||||
_ => dispatch_four(args, error),
|
||||
};
|
||||
*args = &(*args)[idx..];
|
||||
val
|
||||
}
|
||||
|
||||
fn dispatch_two(args: &mut &[&[u8]], error: &mut bool) -> (bool, usize) {
|
||||
let val = two(*args, error);
|
||||
if *error {
|
||||
*error = false;
|
||||
(one(*args), 1)
|
||||
} else {
|
||||
(val, 2)
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_three(args: &mut &[&[u8]], error: &mut bool) -> (bool, usize) {
|
||||
let val = three(*args, error);
|
||||
if *error {
|
||||
*error = false;
|
||||
dispatch_two(args, error)
|
||||
} else {
|
||||
(val, 3)
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_four(args: &mut &[&[u8]], error: &mut bool) -> (bool, usize) {
|
||||
let val = four(*args, error);
|
||||
if *error {
|
||||
*error = false;
|
||||
dispatch_three(args, error)
|
||||
} else {
|
||||
(val, 4)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum Precedence {
|
||||
Unknown = 0,
|
||||
Paren, // FIXME: this is useless (parentheses have not been implemented)
|
||||
Or,
|
||||
And,
|
||||
BUnOp,
|
||||
BinOp,
|
||||
UnOp,
|
||||
}
|
||||
|
||||
fn parse_expr(mut args: &[&[u8]], error: &mut bool) -> bool {
|
||||
if args.is_empty() {
|
||||
false
|
||||
} else {
|
||||
let hashmap = setup_hashmap();
|
||||
let lhs = dispatch(&mut args, error);
|
||||
|
||||
if !args.is_empty() {
|
||||
parse_expr_helper(&hashmap, &mut args, lhs, Precedence::Unknown, error)
|
||||
} else {
|
||||
lhs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_expr_helper<'a>(
|
||||
hashmap: &HashMap<&'a [u8], Precedence>,
|
||||
args: &mut &[&'a [u8]],
|
||||
mut lhs: bool,
|
||||
min_prec: Precedence,
|
||||
error: &mut bool,
|
||||
) -> bool {
|
||||
let mut prec = *hashmap.get(&args[0]).unwrap_or_else(|| {
|
||||
*error = true;
|
||||
&min_prec
|
||||
});
|
||||
while !*error && !args.is_empty() && prec as usize >= min_prec as usize {
|
||||
let op = args[0];
|
||||
*args = &(*args)[1..];
|
||||
let mut rhs = dispatch(args, error);
|
||||
while !args.is_empty() {
|
||||
let subprec = *hashmap.get(&args[0]).unwrap_or_else(|| {
|
||||
*error = true;
|
||||
&min_prec
|
||||
});
|
||||
if subprec as usize <= prec as usize || *error {
|
||||
break;
|
||||
}
|
||||
rhs = parse_expr_helper(hashmap, args, rhs, subprec, error);
|
||||
}
|
||||
lhs = match prec {
|
||||
Precedence::UnOp | Precedence::BUnOp => {
|
||||
*error = true;
|
||||
false
|
||||
}
|
||||
Precedence::And => lhs && rhs,
|
||||
Precedence::Or => lhs || rhs,
|
||||
Precedence::BinOp => three(
|
||||
&[
|
||||
if lhs { b" " } else { b"" },
|
||||
op,
|
||||
if rhs { b" " } else { b"" },
|
||||
],
|
||||
error,
|
||||
),
|
||||
Precedence::Paren => unimplemented!(), // TODO: implement parentheses
|
||||
_ => unreachable!(),
|
||||
};
|
||||
if !args.is_empty() {
|
||||
prec = *hashmap.get(&args[0]).unwrap_or_else(|| {
|
||||
*error = true;
|
||||
&min_prec
|
||||
});
|
||||
}
|
||||
}
|
||||
lhs
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn setup_hashmap<'a>() -> HashMap<&'a [u8], Precedence> {
|
||||
let mut hashmap = HashMap::<&'a [u8], Precedence>::new();
|
||||
|
||||
hashmap.insert(b"-b", Precedence::UnOp);
|
||||
hashmap.insert(b"-c", Precedence::UnOp);
|
||||
hashmap.insert(b"-d", Precedence::UnOp);
|
||||
hashmap.insert(b"-e", Precedence::UnOp);
|
||||
hashmap.insert(b"-f", Precedence::UnOp);
|
||||
hashmap.insert(b"-g", Precedence::UnOp);
|
||||
hashmap.insert(b"-h", Precedence::UnOp);
|
||||
hashmap.insert(b"-L", Precedence::UnOp);
|
||||
hashmap.insert(b"-n", Precedence::UnOp);
|
||||
hashmap.insert(b"-p", Precedence::UnOp);
|
||||
hashmap.insert(b"-r", Precedence::UnOp);
|
||||
hashmap.insert(b"-S", Precedence::UnOp);
|
||||
hashmap.insert(b"-s", Precedence::UnOp);
|
||||
hashmap.insert(b"-t", Precedence::UnOp);
|
||||
hashmap.insert(b"-u", Precedence::UnOp);
|
||||
hashmap.insert(b"-w", Precedence::UnOp);
|
||||
hashmap.insert(b"-x", Precedence::UnOp);
|
||||
hashmap.insert(b"-z", Precedence::UnOp);
|
||||
|
||||
hashmap.insert(b"=", Precedence::BinOp);
|
||||
hashmap.insert(b"!=", Precedence::BinOp);
|
||||
hashmap.insert(b"-eq", Precedence::BinOp);
|
||||
hashmap.insert(b"-ne", Precedence::BinOp);
|
||||
hashmap.insert(b"-gt", Precedence::BinOp);
|
||||
hashmap.insert(b"-ge", Precedence::BinOp);
|
||||
hashmap.insert(b"-lt", Precedence::BinOp);
|
||||
hashmap.insert(b"-le", Precedence::BinOp);
|
||||
|
||||
hashmap.insert(b"!", Precedence::BUnOp);
|
||||
|
||||
hashmap.insert(b"-a", Precedence::And);
|
||||
hashmap.insert(b"-o", Precedence::Or);
|
||||
|
||||
hashmap.insert(b"(", Precedence::Paren);
|
||||
hashmap.insert(b")", Precedence::Paren);
|
||||
|
||||
hashmap
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq)]
|
||||
enum PathCondition {
|
||||
BlockSpecial,
|
||||
|
@ -334,14 +177,10 @@ enum PathCondition {
|
|||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn path(path: &[u8], cond: PathCondition) -> bool {
|
||||
use std::ffi::OsStr;
|
||||
fn path(path: &OsStr, cond: PathCondition) -> bool {
|
||||
use std::fs::{self, Metadata};
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::os::unix::fs::{FileTypeExt, MetadataExt};
|
||||
|
||||
let path = OsStr::from_bytes(path);
|
||||
|
||||
const S_ISUID: u32 = 0o4000;
|
||||
const S_ISGID: u32 = 0o2000;
|
||||
|
||||
|
@ -403,13 +242,14 @@ fn path(path: &[u8], cond: PathCondition) -> bool {
|
|||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn path(path: &[u8], cond: PathCondition) -> bool {
|
||||
fn path(path: &OsStr, cond: PathCondition) -> bool {
|
||||
use std::fs::metadata;
|
||||
let path = from_utf8(path).unwrap();
|
||||
|
||||
let stat = match metadata(path) {
|
||||
Ok(s) => s,
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
match cond {
|
||||
PathCondition::BlockSpecial => false,
|
||||
PathCondition::CharacterSpecial => false,
|
||||
|
|
|
@ -23,11 +23,9 @@ use std::io::{stdin, stdout, BufRead, BufWriter, Write};
|
|||
use crate::expand::ExpandSet;
|
||||
use uucore::InvalidEncodingHandling;
|
||||
|
||||
static NAME: &str = "tr";
|
||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
static ABOUT: &str = "translate or delete characters";
|
||||
static LONG_HELP: &str = "Translate, squeeze, and/or delete characters from standard input,
|
||||
writing to standard output.";
|
||||
|
||||
const BUFFER_LEN: usize = 1024;
|
||||
|
||||
mod options {
|
||||
|
@ -125,10 +123,17 @@ impl SymbolTranslator for DeleteAndSqueezeOperation {
|
|||
|
||||
struct TranslateOperation {
|
||||
translate_map: FnvHashMap<usize, char>,
|
||||
complement: bool,
|
||||
s2_last: char,
|
||||
}
|
||||
|
||||
impl TranslateOperation {
|
||||
fn new(set1: ExpandSet, set2: &mut ExpandSet, truncate: bool) -> TranslateOperation {
|
||||
fn new(
|
||||
set1: ExpandSet,
|
||||
set2: &mut ExpandSet,
|
||||
truncate: bool,
|
||||
complement: bool,
|
||||
) -> TranslateOperation {
|
||||
let mut map = FnvHashMap::default();
|
||||
let mut s2_prev = '_';
|
||||
for i in set1 {
|
||||
|
@ -141,13 +146,54 @@ impl TranslateOperation {
|
|||
map.insert(i as usize, s2_prev);
|
||||
}
|
||||
}
|
||||
TranslateOperation { translate_map: map }
|
||||
TranslateOperation {
|
||||
translate_map: map,
|
||||
complement,
|
||||
s2_last: set2.last().unwrap_or(s2_prev),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SymbolTranslator for TranslateOperation {
|
||||
fn translate(&self, c: char, _prev_c: char) -> Option<char> {
|
||||
Some(*self.translate_map.get(&(c as usize)).unwrap_or(&c))
|
||||
if self.complement {
|
||||
Some(if self.translate_map.contains_key(&(c as usize)) {
|
||||
c
|
||||
} else {
|
||||
self.s2_last
|
||||
})
|
||||
} else {
|
||||
Some(*self.translate_map.get(&(c as usize)).unwrap_or(&c))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TranslateAndSqueezeOperation {
|
||||
translate: TranslateOperation,
|
||||
squeeze: SqueezeOperation,
|
||||
}
|
||||
|
||||
impl TranslateAndSqueezeOperation {
|
||||
fn new(
|
||||
set1: ExpandSet,
|
||||
set2: &mut ExpandSet,
|
||||
set2_: ExpandSet,
|
||||
truncate: bool,
|
||||
complement: bool,
|
||||
) -> TranslateAndSqueezeOperation {
|
||||
TranslateAndSqueezeOperation {
|
||||
translate: TranslateOperation::new(set1, set2, truncate, complement),
|
||||
squeeze: SqueezeOperation::new(set2_, complement),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SymbolTranslator for TranslateAndSqueezeOperation {
|
||||
fn translate(&self, c: char, prev_c: char) -> Option<char> {
|
||||
// `unwrap()` will never panic because `Translate.translate()`
|
||||
// always returns `Some`.
|
||||
self.squeeze
|
||||
.translate(self.translate.translate(c, 0 as char).unwrap(), prev_c)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -168,8 +214,11 @@ fn translate_input<T: SymbolTranslator>(
|
|||
// isolation to make borrow checker happy
|
||||
let filtered = buf.chars().filter_map(|c| {
|
||||
let res = translator.translate(c, prev_c);
|
||||
if res.is_some() {
|
||||
prev_c = c;
|
||||
// Set `prev_c` to the post-translate character. This
|
||||
// allows the squeeze operation to correctly function
|
||||
// after the translate operation.
|
||||
if let Some(rc) = res {
|
||||
prev_c = rc;
|
||||
}
|
||||
res
|
||||
});
|
||||
|
@ -186,24 +235,38 @@ fn get_usage() -> String {
|
|||
format!("{} [OPTION]... SET1 [SET2]", executable!())
|
||||
}
|
||||
|
||||
fn get_long_usage() -> String {
|
||||
String::from(
|
||||
"Translate, squeeze, and/or delete characters from standard input,
|
||||
writing to standard output.",
|
||||
)
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
let usage = get_usage();
|
||||
let args = args
|
||||
.collect_str(InvalidEncodingHandling::ConvertLossy)
|
||||
.accept_any();
|
||||
|
||||
let usage = get_usage();
|
||||
let after_help = get_long_usage();
|
||||
|
||||
let matches = App::new(executable!())
|
||||
.version(VERSION)
|
||||
.about(ABOUT)
|
||||
.usage(&usage[..])
|
||||
.after_help(LONG_HELP)
|
||||
.after_help(&after_help[..])
|
||||
.arg(
|
||||
Arg::with_name(options::COMPLEMENT)
|
||||
.short("C")
|
||||
// .visible_short_alias('C') // TODO: requires clap "3.0.0-beta.2"
|
||||
.short("c")
|
||||
.long(options::COMPLEMENT)
|
||||
.help("use the complement of SET1"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("C") // work around for `Arg::visible_short_alias`
|
||||
.short("C")
|
||||
.help("same as -c"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::DELETE)
|
||||
.short("d")
|
||||
|
@ -216,8 +279,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.short("s")
|
||||
.help(
|
||||
"replace each sequence of a repeated character that is
|
||||
listed in the last specified SET, with a single occurrence
|
||||
of that character",
|
||||
listed in the last specified SET, with a single occurrence
|
||||
of that character",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
|
@ -230,7 +293,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.get_matches_from(args);
|
||||
|
||||
let delete_flag = matches.is_present(options::DELETE);
|
||||
let complement_flag = matches.is_present(options::COMPLEMENT);
|
||||
let complement_flag = matches.is_present(options::COMPLEMENT) || matches.is_present("C");
|
||||
let squeeze_flag = matches.is_present(options::SQUEEZE);
|
||||
let truncate_flag = matches.is_present(options::TRUNCATE);
|
||||
|
||||
|
@ -242,7 +305,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
if sets.is_empty() {
|
||||
show_error!(
|
||||
"missing operand\nTry `{} --help` for more information.",
|
||||
NAME
|
||||
executable!()
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
@ -251,16 +314,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
show_error!(
|
||||
"missing operand after ‘{}’\nTry `{} --help` for more information.",
|
||||
sets[0],
|
||||
NAME
|
||||
executable!()
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if complement_flag && !delete_flag && !squeeze_flag {
|
||||
show_error!("-c is only supported with -d or -s");
|
||||
return 1;
|
||||
}
|
||||
|
||||
let stdin = stdin();
|
||||
let mut locked_stdin = stdin.lock();
|
||||
let stdout = stdout();
|
||||
|
@ -278,12 +336,25 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
translate_input(&mut locked_stdin, &mut buffered_stdout, op);
|
||||
}
|
||||
} else if squeeze_flag {
|
||||
let op = SqueezeOperation::new(set1, complement_flag);
|
||||
translate_input(&mut locked_stdin, &mut buffered_stdout, op);
|
||||
if sets.len() < 2 {
|
||||
let op = SqueezeOperation::new(set1, complement_flag);
|
||||
translate_input(&mut locked_stdin, &mut buffered_stdout, op);
|
||||
} else {
|
||||
let mut set2 = ExpandSet::new(sets[1].as_ref());
|
||||
let set2_ = ExpandSet::new(sets[1].as_ref());
|
||||
let op = TranslateAndSqueezeOperation::new(
|
||||
set1,
|
||||
&mut set2,
|
||||
set2_,
|
||||
complement_flag,
|
||||
truncate_flag,
|
||||
);
|
||||
translate_input(&mut locked_stdin, &mut buffered_stdout, op);
|
||||
}
|
||||
} else {
|
||||
let mut set2 = ExpandSet::new(sets[1].as_ref());
|
||||
let op = TranslateOperation::new(set1, &mut set2, truncate_flag);
|
||||
translate_input(&mut locked_stdin, &mut buffered_stdout, op)
|
||||
let op = TranslateOperation::new(set1, &mut set2, truncate_flag, complement_flag);
|
||||
translate_input(&mut locked_stdin, &mut buffered_stdout, op);
|
||||
}
|
||||
|
||||
0
|
||||
|
|
|
@ -98,7 +98,7 @@ fn test_wrap_bad_arg() {
|
|||
.arg(wrap_param)
|
||||
.arg("b")
|
||||
.fails()
|
||||
.stderr_only("base32: error: invalid wrap size: ‘b’: invalid digit found in string\n");
|
||||
.stderr_only("base32: error: Invalid wrap size: ‘b’: invalid digit found in string\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,21 @@ fn test_encode() {
|
|||
.pipe_in(input)
|
||||
.succeeds()
|
||||
.stdout_only("aGVsbG8sIHdvcmxkIQ==\n");
|
||||
|
||||
// Using '-' as our file
|
||||
new_ucmd!()
|
||||
.arg("-")
|
||||
.pipe_in(input)
|
||||
.succeeds()
|
||||
.stdout_only("aGVsbG8sIHdvcmxkIQ==\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_base64_encode_file() {
|
||||
new_ucmd!()
|
||||
.arg("input-simple.txt")
|
||||
.succeeds()
|
||||
.stdout_only("SGVsbG8sIFdvcmxkIQo=\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -60,10 +75,9 @@ fn test_wrap() {
|
|||
#[test]
|
||||
fn test_wrap_no_arg() {
|
||||
for wrap_param in vec!["-w", "--wrap"] {
|
||||
new_ucmd!().arg(wrap_param).fails().stderr_only(format!(
|
||||
"base64: error: Argument to option '{}' missing\n",
|
||||
if wrap_param == "-w" { "w" } else { "wrap" }
|
||||
));
|
||||
new_ucmd!().arg(wrap_param).fails().stderr_contains(
|
||||
&"The argument '--wrap <wrap>' requires a value but none was supplied",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,6 +88,24 @@ fn test_wrap_bad_arg() {
|
|||
.arg(wrap_param)
|
||||
.arg("b")
|
||||
.fails()
|
||||
.stderr_only("base64: error: invalid wrap size: ‘b’: invalid digit found in string\n");
|
||||
.stderr_only("base64: error: Invalid wrap size: ‘b’: invalid digit found in string\n");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_base64_extra_operand() {
|
||||
// Expect a failure when multiple files are specified.
|
||||
new_ucmd!()
|
||||
.arg("a.txt")
|
||||
.arg("a.txt")
|
||||
.fails()
|
||||
.stderr_only("base64: error: extra operand ‘a.txt’");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_base64_file_not_found() {
|
||||
new_ucmd!()
|
||||
.arg("a.txt")
|
||||
.fails()
|
||||
.stderr_only("base64: error: a.txt: No such file or directory");
|
||||
}
|
||||
|
|
|
@ -16,6 +16,21 @@ fn test_path_without_trailing_slashes() {
|
|||
.stdout_is("/root/alpha/beta/gamma/delta/epsilon\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_without_trailing_slashes_and_zero() {
|
||||
new_ucmd!()
|
||||
.arg("-z")
|
||||
.arg("/root/alpha/beta/gamma/delta/epsilon/omega")
|
||||
.succeeds()
|
||||
.stdout_is("/root/alpha/beta/gamma/delta/epsilon\u{0}");
|
||||
|
||||
new_ucmd!()
|
||||
.arg("--zero")
|
||||
.arg("/root/alpha/beta/gamma/delta/epsilon/omega")
|
||||
.succeeds()
|
||||
.stdout_is("/root/alpha/beta/gamma/delta/epsilon\u{0}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_root() {
|
||||
new_ucmd!().arg("/").run().stdout_is("/\n");
|
||||
|
|
|
@ -39,3 +39,25 @@ fn test_link_nonexistent_file() {
|
|||
assert!(!at.file_exists(file));
|
||||
assert!(!at.file_exists(link));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_link_one_argument() {
|
||||
let (_, mut ucmd) = at_and_ucmd!();
|
||||
let file = "test_link_argument";
|
||||
ucmd.args(&[file]).fails().stderr_contains(
|
||||
"error: The argument '<FILES>...' requires at least 2 values, but only 1 was provide",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_link_three_arguments() {
|
||||
let (_, mut ucmd) = at_and_ucmd!();
|
||||
let arguments = vec![
|
||||
"test_link_argument1",
|
||||
"test_link_argument2",
|
||||
"test_link_argument3",
|
||||
];
|
||||
ucmd.args(&arguments[..]).fails().stderr_contains(
|
||||
format!("error: The value '{}' was provided to '<FILES>...', but it wasn't expecting any more values", arguments[2]),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -43,23 +43,74 @@ fn test_ls_a() {
|
|||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
at.touch(".test-1");
|
||||
at.mkdir("some-dir");
|
||||
at.touch(
|
||||
Path::new("some-dir")
|
||||
.join(".test-2")
|
||||
.as_os_str()
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let result = scene.ucmd().succeeds();
|
||||
let stdout = result.stdout_str();
|
||||
assert!(!stdout.contains(".test-1"));
|
||||
assert!(!stdout.contains(".."));
|
||||
let re_pwd = Regex::new(r"^\.\n").unwrap();
|
||||
|
||||
// Using the present working directory
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-1")
|
||||
.succeeds()
|
||||
.stdout_does_not_contain(".test-1")
|
||||
.stdout_does_not_contain("..")
|
||||
.stdout_does_not_match(&re_pwd);
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-a")
|
||||
.arg("-1")
|
||||
.succeeds()
|
||||
.stdout_contains(&".test-1")
|
||||
.stdout_contains(&"..");
|
||||
.stdout_contains(&"..")
|
||||
.stdout_matches(&re_pwd);
|
||||
|
||||
let result = scene.ucmd().arg("-A").succeeds();
|
||||
result.stdout_contains(".test-1");
|
||||
let stdout = result.stdout_str();
|
||||
assert!(!stdout.contains(".."));
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-A")
|
||||
.arg("-1")
|
||||
.succeeds()
|
||||
.stdout_contains(".test-1")
|
||||
.stdout_does_not_contain("..")
|
||||
.stdout_does_not_match(&re_pwd);
|
||||
|
||||
// Using a subdirectory
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-1")
|
||||
.arg("some-dir")
|
||||
.succeeds()
|
||||
.stdout_does_not_contain(".test-2")
|
||||
.stdout_does_not_contain("..")
|
||||
.stdout_does_not_match(&re_pwd);
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-a")
|
||||
.arg("-1")
|
||||
.arg("some-dir")
|
||||
.succeeds()
|
||||
.stdout_contains(&".test-2")
|
||||
.stdout_contains(&"..")
|
||||
.no_stderr()
|
||||
.stdout_matches(&re_pwd);
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-A")
|
||||
.arg("-1")
|
||||
.arg("some-dir")
|
||||
.succeeds()
|
||||
.stdout_contains(".test-2")
|
||||
.stdout_does_not_contain("..")
|
||||
.stdout_does_not_match(&re_pwd);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -482,7 +533,6 @@ fn test_ls_sort_name() {
|
|||
.succeeds()
|
||||
.stdout_is(["test-1", "test-2", "test-3\n"].join(sep));
|
||||
|
||||
// Order of a named sort ignores leading dots.
|
||||
let scene_dot = TestScenario::new(util_name!());
|
||||
let at = &scene_dot.fixtures;
|
||||
at.touch(".a");
|
||||
|
@ -495,7 +545,7 @@ fn test_ls_sort_name() {
|
|||
.arg("--sort=name")
|
||||
.arg("-A")
|
||||
.succeeds()
|
||||
.stdout_is([".a", "a", ".b", "b\n"].join(sep));
|
||||
.stdout_is([".a", ".b", "a", "b\n"].join(sep));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -584,7 +634,6 @@ fn test_ls_order_birthtime() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_ls_styles() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
|
@ -598,7 +647,7 @@ fn test_ls_styles() {
|
|||
Regex::new(r"[a-z-]* \d* \w* \w* \d* \d{4}-\d{2}-\d{2} \d{2}:\d{2} test\n").unwrap();
|
||||
let re_iso = Regex::new(r"[a-z-]* \d* \w* \w* \d* \d{2}-\d{2} \d{2}:\d{2} test\n").unwrap();
|
||||
let re_locale =
|
||||
Regex::new(r"[a-z-]* \d* \w* \w* \d* [A-Z][a-z]{2} \d{2} \d{2}:\d{2} test\n").unwrap();
|
||||
Regex::new(r"[a-z-]* \d* \w* \w* \d* [A-Z][a-z]{2} ( |\d)\d \d{2}:\d{2} test\n").unwrap();
|
||||
|
||||
//full-iso
|
||||
let result = scene
|
||||
|
|
|
@ -34,6 +34,36 @@ fn test_long_format() {
|
|||
));
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[test]
|
||||
fn test_long_format_multiple_users() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
|
||||
let expected = scene
|
||||
.cmd_keepenv(util_name!())
|
||||
.env("LANGUAGE", "C")
|
||||
.arg("-l")
|
||||
.arg("root")
|
||||
.arg("root")
|
||||
.arg("root")
|
||||
.succeeds();
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-l")
|
||||
.arg("root")
|
||||
.arg("root")
|
||||
.arg("root")
|
||||
.succeeds()
|
||||
.stdout_is(expected.stdout_str());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_long_format_wo_user() {
|
||||
// "no username specified; at least one must be specified when using -l"
|
||||
new_ucmd!().arg("-l").fails().code_is(1);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[test]
|
||||
fn test_short_format_i() {
|
||||
|
|
|
@ -103,7 +103,7 @@ fn test_relpath_with_from_with_d() {
|
|||
at.mkdir_all(from);
|
||||
|
||||
// d is part of subpath -> expect relative path
|
||||
let mut result_stdout = scene
|
||||
let mut _result_stdout = scene
|
||||
.ucmd()
|
||||
.arg(to)
|
||||
.arg(from)
|
||||
|
@ -112,17 +112,17 @@ fn test_relpath_with_from_with_d() {
|
|||
.stdout_move_str();
|
||||
// relax rules for windows test environment
|
||||
#[cfg(not(windows))]
|
||||
assert!(Path::new(&result_stdout).is_relative());
|
||||
assert!(Path::new(&_result_stdout).is_relative());
|
||||
|
||||
// d is not part of subpath -> expect absolut path
|
||||
result_stdout = scene
|
||||
_result_stdout = scene
|
||||
.ucmd()
|
||||
.arg(to)
|
||||
.arg(from)
|
||||
.arg("-dnon_existing")
|
||||
.succeeds()
|
||||
.stdout_move_str();
|
||||
assert!(Path::new(&result_stdout).is_absolute());
|
||||
assert!(Path::new(&_result_stdout).is_absolute());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,12 +135,12 @@ fn test_relpath_no_from_no_d() {
|
|||
let to: &str = &convert_path(test.to);
|
||||
at.mkdir_all(to);
|
||||
|
||||
let result_stdout = scene.ucmd().arg(to).succeeds().stdout_move_str();
|
||||
let _result_stdout = scene.ucmd().arg(to).succeeds().stdout_move_str();
|
||||
#[cfg(not(windows))]
|
||||
assert_eq!(result_stdout, format!("{}\n", to));
|
||||
assert_eq!(_result_stdout, format!("{}\n", to));
|
||||
// relax rules for windows test environment
|
||||
#[cfg(windows)]
|
||||
assert!(result_stdout.ends_with(&format!("{}\n", to)));
|
||||
assert!(_result_stdout.ends_with(&format!("{}\n", to)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// (c) mahkoh (ju.orth [at] gmail [dot] com)
|
||||
// (c) Daniel Rocco <drocco@gmail.com>
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
@ -9,11 +10,466 @@
|
|||
|
||||
use crate::common::util::*;
|
||||
|
||||
#[test]
|
||||
fn test_empty_test_equivalent_to_false() {
|
||||
new_ucmd!().run().status_code(1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_string_is_false() {
|
||||
new_ucmd!().arg("").run().status_code(1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_solo_not() {
|
||||
new_ucmd!().arg("!").succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_solo_and_or_or_is_a_literal() {
|
||||
// /bin/test '' -a '' => 1; so test(1) must interpret `-a` by itself as
|
||||
// a literal string
|
||||
new_ucmd!().arg("-a").succeeds();
|
||||
new_ucmd!().arg("-o").succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_double_not_is_false() {
|
||||
new_ucmd!().args(&["!", "!"]).run().status_code(1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_and_not_is_false() {
|
||||
new_ucmd!().args(&["-a", "!"]).run().status_code(1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_not_and_is_false() {
|
||||
// `-a` is a literal here & has nonzero length
|
||||
new_ucmd!().args(&["!", "-a"]).run().status_code(1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_not_and_not_succeeds() {
|
||||
new_ucmd!().args(&["!", "-a", "!"]).succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple_or() {
|
||||
new_ucmd!().args(&["foo", "-o", ""]).succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_negated_or() {
|
||||
new_ucmd!()
|
||||
.args(&["!", "foo", "-o", "bar"])
|
||||
.run()
|
||||
.status_code(1);
|
||||
new_ucmd!().args(&["foo", "-o", "!", "bar"]).succeeds();
|
||||
new_ucmd!()
|
||||
.args(&["!", "foo", "-o", "!", "bar"])
|
||||
.run()
|
||||
.status_code(1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_strlen_of_nothing() {
|
||||
// odd but matches GNU, which must interpret -n as a literal here
|
||||
new_ucmd!().arg("-n").succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_strlen_of_empty() {
|
||||
new_ucmd!().args(&["-n", ""]).run().status_code(1);
|
||||
|
||||
// STRING equivalent to -n STRING
|
||||
new_ucmd!().arg("").run().status_code(1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nothing_is_empty() {
|
||||
// -z is a literal here and has nonzero length
|
||||
new_ucmd!().arg("-z").succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zero_len_of_empty() {
|
||||
new_ucmd!().args(&["-z", ""]).succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_solo_paren_is_literal() {
|
||||
let scenario = TestScenario::new(util_name!());
|
||||
let tests = [["("], [")"]];
|
||||
|
||||
for test in &tests {
|
||||
scenario.ucmd().args(&test[..]).succeeds();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_solo_empty_parenthetical_is_error() {
|
||||
new_ucmd!().args(&["(", ")"]).run().status_code(2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zero_len_equals_zero_len() {
|
||||
new_ucmd!().args(&["", "=", ""]).succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zero_len_not_equals_zero_len_is_false() {
|
||||
new_ucmd!().args(&["", "!=", ""]).run().status_code(1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_comparison() {
|
||||
let scenario = TestScenario::new(util_name!());
|
||||
let tests = [
|
||||
["foo", "!=", "bar"],
|
||||
["contained\nnewline", "=", "contained\nnewline"],
|
||||
["(", "=", "("],
|
||||
["(", "!=", ")"],
|
||||
["!", "=", "!"],
|
||||
];
|
||||
|
||||
for test in &tests {
|
||||
scenario.ucmd().args(&test[..]).succeeds();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "fixme: error reporting"]
|
||||
fn test_dangling_string_comparison_is_error() {
|
||||
new_ucmd!()
|
||||
.args(&["missing_something", "="])
|
||||
.run()
|
||||
.status_code(2)
|
||||
.stderr_is("test: missing argument after ‘=’");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stringop_is_literal_after_bang() {
|
||||
let scenario = TestScenario::new(util_name!());
|
||||
let tests = [
|
||||
["!", "="],
|
||||
["!", "!="],
|
||||
["!", "-eq"],
|
||||
["!", "-ne"],
|
||||
["!", "-lt"],
|
||||
["!", "-le"],
|
||||
["!", "-gt"],
|
||||
["!", "-ge"],
|
||||
["!", "-ef"],
|
||||
["!", "-nt"],
|
||||
["!", "-ot"],
|
||||
];
|
||||
|
||||
for test in &tests {
|
||||
scenario.ucmd().args(&test[..]).run().status_code(1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_a_bunch_of_not() {
|
||||
new_ucmd!()
|
||||
.args(&["!", "", "!=", "", "-a", "!", "", "!=", ""])
|
||||
.succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pseudofloat_equal() {
|
||||
new_ucmd!().args(&["123.45", "=", "123.45"]).succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pseudofloat_not_equal() {
|
||||
new_ucmd!().args(&["123.45", "!=", "123.450"]).succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_negative_arg_is_a_string() {
|
||||
new_ucmd!().arg("-12345").succeeds();
|
||||
new_ucmd!().arg("--qwert").succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_some_int_compares() {
|
||||
let scenario = TestScenario::new(util_name!());
|
||||
|
||||
let tests = [
|
||||
["0", "-eq", "0"],
|
||||
["0", "-ne", "1"],
|
||||
["421", "-lt", "3720"],
|
||||
["0", "-le", "0"],
|
||||
["11", "-gt", "10"],
|
||||
["1024", "-ge", "512"],
|
||||
["9223372036854775806", "-le", "9223372036854775807"],
|
||||
];
|
||||
|
||||
for test in &tests {
|
||||
scenario.ucmd().args(&test[..]).succeeds();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "fixme: evaluation error (code 1); GNU returns 0"]
|
||||
fn test_values_greater_than_i64_allowed() {
|
||||
new_ucmd!()
|
||||
.args(&["9223372036854775808", "-gt", "0"])
|
||||
.succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_negative_int_compare() {
|
||||
let scenario = TestScenario::new(util_name!());
|
||||
|
||||
let tests = [
|
||||
["-1", "-eq", "-1"],
|
||||
["-1", "-ne", "-2"],
|
||||
["-3720", "-lt", "-421"],
|
||||
["-10", "-le", "-10"],
|
||||
["-21", "-gt", "-22"],
|
||||
["-128", "-ge", "-256"],
|
||||
["-9223372036854775808", "-le", "-9223372036854775807"],
|
||||
];
|
||||
|
||||
for test in &tests {
|
||||
scenario.ucmd().args(&test[..]).succeeds();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_float_inequality_is_error() {
|
||||
new_ucmd!()
|
||||
.args(&["123.45", "-ge", "6"])
|
||||
.run()
|
||||
.status_code(2)
|
||||
.stderr_is("test: invalid integer ‘123.45’");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(windows))]
|
||||
fn test_invalid_utf8_integer_compare() {
|
||||
use std::ffi::OsStr;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
let source = [0x66, 0x6f, 0x80, 0x6f];
|
||||
let arg = OsStr::from_bytes(&source[..]);
|
||||
|
||||
let mut cmd = new_ucmd!();
|
||||
cmd.arg("123").arg("-ne");
|
||||
cmd.raw.arg(arg);
|
||||
|
||||
cmd.run()
|
||||
.status_code(2)
|
||||
.stderr_is("test: invalid integer ‘fo<66>o’");
|
||||
|
||||
let mut cmd = new_ucmd!();
|
||||
cmd.raw.arg(arg);
|
||||
cmd.arg("-eq").arg("456");
|
||||
|
||||
cmd.run()
|
||||
.status_code(2)
|
||||
.stderr_is("test: invalid integer ‘fo<66>o’");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "fixme: parse/evaluation error (code 2); GNU returns 1"]
|
||||
fn test_file_is_itself() {
|
||||
new_ucmd!()
|
||||
.args(&["regular_file", "-ef", "regular_file"])
|
||||
.succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "fixme: parse/evaluation error (code 2); GNU returns 1"]
|
||||
fn test_file_is_newer_than_and_older_than_itself() {
|
||||
// odd but matches GNU
|
||||
new_ucmd!()
|
||||
.args(&["regular_file", "-nt", "regular_file"])
|
||||
.run()
|
||||
.status_code(1);
|
||||
new_ucmd!()
|
||||
.args(&["regular_file", "-ot", "regular_file"])
|
||||
.run()
|
||||
.status_code(1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "todo: implement these"]
|
||||
fn test_newer_file() {
|
||||
let scenario = TestScenario::new(util_name!());
|
||||
|
||||
scenario.cmd("touch").arg("newer_file").succeeds();
|
||||
scenario
|
||||
.cmd("touch")
|
||||
.args(&["-m", "-d", "last Thursday", "regular_file"])
|
||||
.succeeds();
|
||||
|
||||
scenario
|
||||
.ucmd()
|
||||
.args(&["newer_file", "-nt", "regular_file"])
|
||||
.succeeds();
|
||||
scenario
|
||||
.ucmd()
|
||||
.args(&["regular_file", "-ot", "newer_file"])
|
||||
.succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_exists() {
|
||||
new_ucmd!().args(&["-e", "regular_file"]).succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nonexistent_file_does_not_exist() {
|
||||
new_ucmd!()
|
||||
.args(&["-e", "nonexistent_file"])
|
||||
.run()
|
||||
.status_code(1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nonexistent_file_is_not_regular() {
|
||||
new_ucmd!()
|
||||
.args(&["-f", "nonexistent_file"])
|
||||
.run()
|
||||
.status_code(1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_exists_and_is_regular() {
|
||||
new_ucmd!().args(&["-f", "regular_file"]).succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(windows))] // FIXME: implement on Windows
|
||||
fn test_file_is_readable() {
|
||||
new_ucmd!().args(&["-r", "regular_file"]).succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(windows))] // FIXME: implement on Windows
|
||||
fn test_file_is_not_readable() {
|
||||
let scenario = TestScenario::new(util_name!());
|
||||
let mut ucmd = scenario.ucmd();
|
||||
let mut chmod = scenario.cmd("chmod");
|
||||
|
||||
scenario.fixtures.touch("crypto_file");
|
||||
chmod.args(&["u-r", "crypto_file"]).succeeds();
|
||||
|
||||
ucmd.args(&["!", "-r", "crypto_file"]).succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(windows))] // FIXME: implement on Windows
|
||||
fn test_file_is_writable() {
|
||||
new_ucmd!().args(&["-w", "regular_file"]).succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(windows))] // FIXME: implement on Windows
|
||||
fn test_file_is_not_writable() {
|
||||
let scenario = TestScenario::new(util_name!());
|
||||
let mut ucmd = scenario.ucmd();
|
||||
let mut chmod = scenario.cmd("chmod");
|
||||
|
||||
scenario.fixtures.touch("immutable_file");
|
||||
chmod.args(&["u-w", "immutable_file"]).succeeds();
|
||||
|
||||
ucmd.args(&["!", "-w", "immutable_file"]).succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_is_not_executable() {
|
||||
new_ucmd!().args(&["!", "-x", "regular_file"]).succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(windows))] // FIXME: implement on Windows
|
||||
fn test_file_is_executable() {
|
||||
let scenario = TestScenario::new(util_name!());
|
||||
let mut chmod = scenario.cmd("chmod");
|
||||
|
||||
chmod.args(&["u+x", "regular_file"]).succeeds();
|
||||
|
||||
scenario.ucmd().args(&["-x", "regular_file"]).succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_not_empty() {
|
||||
new_ucmd!().args(&["-s", "non_empty_file"]).succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nonexistent_file_size_test_is_false() {
|
||||
new_ucmd!()
|
||||
.args(&["-s", "nonexistent_file"])
|
||||
.run()
|
||||
.status_code(1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_not_is_not_empty() {
|
||||
new_ucmd!().args(&["!", "-s", "regular_file"]).succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(windows))]
|
||||
fn test_symlink_is_symlink() {
|
||||
let scenario = TestScenario::new(util_name!());
|
||||
let mut ln = scenario.cmd("ln");
|
||||
|
||||
// creating symlinks requires admin on Windows
|
||||
ln.args(&["-s", "regular_file", "symlink"]).succeeds();
|
||||
|
||||
// FIXME: implement on Windows
|
||||
scenario.ucmd().args(&["-h", "symlink"]).succeeds();
|
||||
scenario.ucmd().args(&["-L", "symlink"]).succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_is_not_symlink() {
|
||||
let scenario = TestScenario::new(util_name!());
|
||||
|
||||
scenario
|
||||
.ucmd()
|
||||
.args(&["!", "-h", "regular_file"])
|
||||
.succeeds();
|
||||
scenario
|
||||
.ucmd()
|
||||
.args(&["!", "-L", "regular_file"])
|
||||
.succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nonexistent_file_is_not_symlink() {
|
||||
let scenario = TestScenario::new(util_name!());
|
||||
|
||||
scenario
|
||||
.ucmd()
|
||||
.args(&["!", "-h", "nonexistent_file"])
|
||||
.succeeds();
|
||||
scenario
|
||||
.ucmd()
|
||||
.args(&["!", "-L", "nonexistent_file"])
|
||||
.succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_op_prec_and_or_1() {
|
||||
new_ucmd!().args(&[" ", "-o", "", "-a", ""]).succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_op_prec_and_or_1_overridden_by_parentheses() {
|
||||
new_ucmd!()
|
||||
.args(&["(", " ", "-o", "", ")", "-a", ""])
|
||||
.run()
|
||||
.status_code(1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_op_prec_and_or_2() {
|
||||
new_ucmd!()
|
||||
|
@ -22,6 +478,54 @@ fn test_op_prec_and_or_2() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_or_as_filename() {
|
||||
new_ucmd!().args(&["x", "-a", "-z", "-o"]).fails();
|
||||
fn test_op_prec_and_or_2_overridden_by_parentheses() {
|
||||
new_ucmd!()
|
||||
.args(&["", "-a", "(", "", "-o", " ", ")", "-a", " "])
|
||||
.run()
|
||||
.status_code(1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "fixme: error reporting"]
|
||||
fn test_dangling_parenthesis() {
|
||||
new_ucmd!()
|
||||
.args(&["(", "(", "a", "!=", "b", ")", "-o", "-n", "c"])
|
||||
.run()
|
||||
.status_code(2);
|
||||
new_ucmd!()
|
||||
.args(&["(", "(", "a", "!=", "b", ")", "-o", "-n", "c", ")"])
|
||||
.succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complicated_parenthesized_expression() {
|
||||
new_ucmd!()
|
||||
.args(&[
|
||||
"(", "(", "!", "(", "a", "=", "b", ")", "-o", "c", "=", "d", ")", "-a", "(", "q", "!=",
|
||||
"r", ")", ")",
|
||||
])
|
||||
.succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_erroneous_parenthesized_expression() {
|
||||
new_ucmd!()
|
||||
.args(&["a", "!=", "(", "b", "-a", "b", ")", "!=", "c"])
|
||||
.run()
|
||||
.status_code(2)
|
||||
.stderr_is("test: extra argument ‘b’");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_or_as_filename() {
|
||||
new_ucmd!()
|
||||
.args(&["x", "-a", "-z", "-o"])
|
||||
.run()
|
||||
.status_code(1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "GNU considers this an error"]
|
||||
fn test_strlen_and_nothing() {
|
||||
new_ucmd!().args(&["-n", "a", "-a"]).run().status_code(2);
|
||||
}
|
||||
|
|
|
@ -45,6 +45,67 @@ fn test_delete_complement() {
|
|||
.stdout_is("ac");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_complement_2() {
|
||||
new_ucmd!()
|
||||
.args(&["-d", "-C", "0-9"])
|
||||
.pipe_in("Phone: 01234 567890")
|
||||
.succeeds()
|
||||
.stdout_is("01234567890");
|
||||
new_ucmd!()
|
||||
.args(&["-d", "--complement", "0-9"])
|
||||
.pipe_in("Phone: 01234 567890")
|
||||
.succeeds()
|
||||
.stdout_is("01234567890");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complement1() {
|
||||
new_ucmd!()
|
||||
.args(&["-c", "a", "X"])
|
||||
.pipe_in("ab")
|
||||
.run()
|
||||
.stdout_is("aX");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complement2() {
|
||||
new_ucmd!()
|
||||
.args(&["-c", "0-9", "x"])
|
||||
.pipe_in("Phone: 01234 567890")
|
||||
.run()
|
||||
.stdout_is("xxxxxxx01234x567890");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complement3() {
|
||||
new_ucmd!()
|
||||
.args(&["-c", "abcdefgh", "123"])
|
||||
.pipe_in("the cat and the bat")
|
||||
.run()
|
||||
.stdout_is("3he3ca33a3d33he3ba3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complement4() {
|
||||
// $ echo -n '0x1y2z3' | tr -c '0-@' '*-~'
|
||||
// 0~1~2~3
|
||||
new_ucmd!()
|
||||
.args(&["-c", "0-@", "*-~"])
|
||||
.pipe_in("0x1y2z3")
|
||||
.run()
|
||||
.stdout_is("0~1~2~3");
|
||||
|
||||
// TODO: fix this
|
||||
// $ echo '0x1y2z3' | tr -c '\0-@' '*-~'
|
||||
// 0a1b2c3
|
||||
// new_ucmd!()
|
||||
// .args(&["-c", "\\0-@", "*-~"])
|
||||
// .pipe_in("0x1y2z3")
|
||||
// .run()
|
||||
// .stdout_is("0a1b2c3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_squeeze() {
|
||||
new_ucmd!()
|
||||
|
@ -63,6 +124,24 @@ fn test_squeeze_complement() {
|
|||
.stdout_is("aaBcDcc");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_translate_and_squeeze() {
|
||||
new_ucmd!()
|
||||
.args(&["-s", "x", "y"])
|
||||
.pipe_in("xx")
|
||||
.run()
|
||||
.stdout_is("y");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_translate_and_squeeze_multiple_lines() {
|
||||
new_ucmd!()
|
||||
.args(&["-s", "x", "y"])
|
||||
.pipe_in("xxaax\nxaaxx")
|
||||
.run()
|
||||
.stdout_is("yaay\nyaay");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_and_squeeze() {
|
||||
new_ucmd!()
|
||||
|
|
|
@ -36,7 +36,12 @@ fn test_uname_kernel_version() {
|
|||
fn test_uname_kernel() {
|
||||
let (_, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
let result = ucmd.arg("-o").succeeds();
|
||||
#[cfg(target_os = "linux")]
|
||||
assert!(result.stdout_str().to_lowercase().contains("linux"));
|
||||
{
|
||||
let result = ucmd.arg("-o").succeeds();
|
||||
assert!(result.stdout_str().to_lowercase().contains("linux"));
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let result = ucmd.arg("-o").succeeds();
|
||||
}
|
||||
|
|
1
tests/fixtures/base64/input-simple.txt
vendored
Normal file
1
tests/fixtures/base64/input-simple.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Hello, World!
|
1
tests/fixtures/test/non_empty_file
vendored
Normal file
1
tests/fixtures/test/non_empty_file
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Not empty!
|
0
tests/fixtures/test/regular_file
vendored
Normal file
0
tests/fixtures/test/regular_file
vendored
Normal file
Loading…
Reference in a new issue