mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-29 20:17:45 +00:00
Merge branch 'master' of https://github.com/uutils/coreutils into timeout/cmd-args
This commit is contained in:
commit
a69c2d52f9
71 changed files with 1882 additions and 1513 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -8,16 +8,6 @@ version = "0.11.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
|
||||
|
||||
[[package]]
|
||||
name = "advapi32-sys"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e06588080cb19d0acb6739808aafa5f26bfb2ca015b2b6370028b44cf7cb8a9a"
|
||||
dependencies = [
|
||||
"winapi 0.2.8",
|
||||
"winapi-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.18"
|
||||
|
@ -1787,6 +1777,7 @@ dependencies = [
|
|||
name = "uu_chgrp"
|
||||
version = "0.0.6"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"uucore",
|
||||
"uucore_procs",
|
||||
"walkdir",
|
||||
|
@ -2757,7 +2748,6 @@ dependencies = [
|
|||
name = "uu_whoami"
|
||||
version = "0.0.6"
|
||||
dependencies = [
|
||||
"advapi32-sys",
|
||||
"clap",
|
||||
"uucore",
|
||||
"uucore_procs",
|
||||
|
|
|
@ -54,15 +54,13 @@ impl Config {
|
|||
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,
|
||||
};
|
||||
let cols = options
|
||||
.value_of(options::WRAP)
|
||||
.map(|num| {
|
||||
num.parse::<usize>()
|
||||
.map_err(|e| format!("Invalid wrap size: ‘{}’: {}", num, e))
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
Ok(Config {
|
||||
decode: options.is_present(options::DECODE),
|
||||
|
|
|
@ -118,14 +118,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
|
||||
fn basename(fullname: &str, suffix: &str) -> String {
|
||||
// Remove all platform-specific path separators from the end
|
||||
let mut path: String = fullname
|
||||
.chars()
|
||||
.rev()
|
||||
.skip_while(|&ch| is_separator(ch))
|
||||
.collect();
|
||||
|
||||
// Undo reverse
|
||||
path = path.chars().rev().collect();
|
||||
let path = fullname.trim_end_matches(is_separator);
|
||||
|
||||
// Convert to path buffer and get last path component
|
||||
let pb = PathBuf::from(path);
|
||||
|
|
|
@ -15,6 +15,7 @@ edition = "2018"
|
|||
path = "src/chgrp.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33"
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
walkdir = "2.2"
|
||||
|
|
|
@ -14,6 +14,8 @@ use uucore::fs::resolve_relative_path;
|
|||
use uucore::libc::gid_t;
|
||||
use uucore::perms::{wrap_chgrp, Verbosity};
|
||||
|
||||
use clap::{App, Arg};
|
||||
|
||||
extern crate walkdir;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
|
@ -24,76 +26,194 @@ use std::os::unix::fs::MetadataExt;
|
|||
use std::path::Path;
|
||||
use uucore::InvalidEncodingHandling;
|
||||
|
||||
static SYNTAX: &str =
|
||||
"chgrp [OPTION]... GROUP FILE...\n or : chgrp [OPTION]... --reference=RFILE FILE...";
|
||||
static SUMMARY: &str = "Change the group of each FILE to GROUP.";
|
||||
static ABOUT: &str = "Change the group of each FILE to GROUP.";
|
||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
pub mod options {
|
||||
pub mod verbosity {
|
||||
pub static CHANGES: &str = "changes";
|
||||
pub static QUIET: &str = "quiet";
|
||||
pub static SILENT: &str = "silent";
|
||||
pub static VERBOSE: &str = "verbose";
|
||||
}
|
||||
pub mod preserve_root {
|
||||
pub static PRESERVE: &str = "preserve-root";
|
||||
pub static NO_PRESERVE: &str = "no-preserve-root";
|
||||
}
|
||||
pub mod dereference {
|
||||
pub static DEREFERENCE: &str = "dereference";
|
||||
pub static NO_DEREFERENCE: &str = "no-dereference";
|
||||
}
|
||||
pub static RECURSIVE: &str = "recursive";
|
||||
pub mod traverse {
|
||||
pub static TRAVERSE: &str = "H";
|
||||
pub static NO_TRAVERSE: &str = "P";
|
||||
pub static EVERY: &str = "L";
|
||||
}
|
||||
pub static REFERENCE: &str = "reference";
|
||||
pub static ARG_GROUP: &str = "GROUP";
|
||||
pub static ARG_FILES: &str = "FILE";
|
||||
}
|
||||
|
||||
const FTS_COMFOLLOW: u8 = 1;
|
||||
const FTS_PHYSICAL: u8 = 1 << 1;
|
||||
const FTS_LOGICAL: u8 = 1 << 2;
|
||||
|
||||
fn get_usage() -> String {
|
||||
format!(
|
||||
"{0} [OPTION]... GROUP FILE...\n {0} [OPTION]... --reference=RFILE FILE...",
|
||||
executable!()
|
||||
)
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
let args = args
|
||||
.collect_str(InvalidEncodingHandling::ConvertLossy)
|
||||
.accept_any();
|
||||
|
||||
let mut opts = app!(SYNTAX, SUMMARY, "");
|
||||
opts.optflag("c",
|
||||
"changes",
|
||||
"like verbose but report only when a change is made")
|
||||
.optflag("f", "silent", "")
|
||||
.optflag("", "quiet", "suppress most error messages")
|
||||
.optflag("v",
|
||||
"verbose",
|
||||
"output a diagnostic for every file processed")
|
||||
.optflag("", "dereference", "affect the referent of each symbolic link (this is the default), rather than the symbolic link itself")
|
||||
.optflag("h", "no-dereference", "affect symbolic links instead of any referenced file (useful only on systems that can change the ownership of a symlink)")
|
||||
.optflag("",
|
||||
"no-preserve-root",
|
||||
"do not treat '/' specially (the default)")
|
||||
.optflag("", "preserve-root", "fail to operate recursively on '/'")
|
||||
.optopt("",
|
||||
"reference",
|
||||
"use RFILE's owner and group rather than specifying OWNER:GROUP values",
|
||||
"RFILE")
|
||||
.optflag("R",
|
||||
"recursive",
|
||||
"operate on files and directories recursively")
|
||||
.optflag("H",
|
||||
"",
|
||||
"if a command line argument is a symbolic link to a directory, traverse it")
|
||||
.optflag("L",
|
||||
"",
|
||||
"traverse every symbolic link to a directory encountered")
|
||||
.optflag("P", "", "do not traverse any symbolic links (default)");
|
||||
let usage = get_usage();
|
||||
|
||||
let mut bit_flag = FTS_PHYSICAL;
|
||||
let mut preserve_root = false;
|
||||
let mut derefer = -1;
|
||||
let flags: &[char] = &['H', 'L', 'P'];
|
||||
for opt in &args {
|
||||
match opt.as_str() {
|
||||
// If more than one is specified, only the final one takes effect.
|
||||
s if s.contains(flags) => {
|
||||
if let Some(idx) = s.rfind(flags) {
|
||||
match s.chars().nth(idx).unwrap() {
|
||||
'H' => bit_flag = FTS_COMFOLLOW | FTS_PHYSICAL,
|
||||
'L' => bit_flag = FTS_LOGICAL,
|
||||
'P' => bit_flag = FTS_PHYSICAL,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
"--no-preserve-root" => preserve_root = false,
|
||||
"--preserve-root" => preserve_root = true,
|
||||
"--dereference" => derefer = 1,
|
||||
"--no-dereference" => derefer = 0,
|
||||
_ => (),
|
||||
let mut app = App::new(executable!())
|
||||
.version(VERSION)
|
||||
.about(ABOUT)
|
||||
.usage(&usage[..])
|
||||
.arg(
|
||||
Arg::with_name(options::verbosity::CHANGES)
|
||||
.short("c")
|
||||
.long(options::verbosity::CHANGES)
|
||||
.help("like verbose but report only when a change is made"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::verbosity::SILENT)
|
||||
.short("f")
|
||||
.long(options::verbosity::SILENT),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::verbosity::QUIET)
|
||||
.long(options::verbosity::QUIET)
|
||||
.help("suppress most error messages"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::verbosity::VERBOSE)
|
||||
.short("v")
|
||||
.long(options::verbosity::VERBOSE)
|
||||
.help("output a diagnostic for every file processed"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::dereference::DEREFERENCE)
|
||||
.long(options::dereference::DEREFERENCE),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::dereference::NO_DEREFERENCE)
|
||||
.short("h")
|
||||
.long(options::dereference::NO_DEREFERENCE)
|
||||
.help(
|
||||
"affect symbolic links instead of any referenced file (useful only on systems that can change the ownership of a symlink)",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::preserve_root::PRESERVE)
|
||||
.long(options::preserve_root::PRESERVE)
|
||||
.help("fail to operate recursively on '/'"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::preserve_root::NO_PRESERVE)
|
||||
.long(options::preserve_root::NO_PRESERVE)
|
||||
.help("do not treat '/' specially (the default)"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::REFERENCE)
|
||||
.long(options::REFERENCE)
|
||||
.value_name("RFILE")
|
||||
.help("use RFILE's group rather than specifying GROUP values")
|
||||
.takes_value(true)
|
||||
.multiple(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::RECURSIVE)
|
||||
.short("R")
|
||||
.long(options::RECURSIVE)
|
||||
.help("operate on files and directories recursively"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::traverse::TRAVERSE)
|
||||
.short(options::traverse::TRAVERSE)
|
||||
.help("if a command line argument is a symbolic link to a directory, traverse it"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::traverse::NO_TRAVERSE)
|
||||
.short(options::traverse::NO_TRAVERSE)
|
||||
.help("do not traverse any symbolic links (default)")
|
||||
.overrides_with_all(&[options::traverse::TRAVERSE, options::traverse::EVERY]),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::traverse::EVERY)
|
||||
.short(options::traverse::EVERY)
|
||||
.help("traverse every symbolic link to a directory encountered"),
|
||||
);
|
||||
|
||||
// we change the positional args based on whether
|
||||
// --reference was used.
|
||||
let mut reference = false;
|
||||
let mut help = false;
|
||||
// stop processing options on --
|
||||
for arg in args.iter().take_while(|s| *s != "--") {
|
||||
if arg.starts_with("--reference=") || arg == "--reference" {
|
||||
reference = true;
|
||||
} else if arg == "--help" {
|
||||
// we stop processing once we see --help,
|
||||
// as it doesn't matter if we've seen reference or not
|
||||
help = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let matches = opts.parse(args);
|
||||
let recursive = matches.opt_present("recursive");
|
||||
if help || !reference {
|
||||
// add both positional arguments
|
||||
app = app.arg(
|
||||
Arg::with_name(options::ARG_GROUP)
|
||||
.value_name(options::ARG_GROUP)
|
||||
.required(true)
|
||||
.takes_value(true)
|
||||
.multiple(false),
|
||||
)
|
||||
}
|
||||
app = app.arg(
|
||||
Arg::with_name(options::ARG_FILES)
|
||||
.value_name(options::ARG_FILES)
|
||||
.multiple(true)
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.min_values(1),
|
||||
);
|
||||
|
||||
let matches = app.get_matches_from(args);
|
||||
|
||||
/* Get the list of files */
|
||||
let files: Vec<String> = matches
|
||||
.values_of(options::ARG_FILES)
|
||||
.map(|v| v.map(ToString::to_string).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
let preserve_root = matches.is_present(options::preserve_root::PRESERVE);
|
||||
|
||||
let mut derefer = if matches.is_present(options::dereference::DEREFERENCE) {
|
||||
1
|
||||
} else if matches.is_present(options::dereference::NO_DEREFERENCE) {
|
||||
0
|
||||
} else {
|
||||
-1
|
||||
};
|
||||
|
||||
let mut bit_flag = if matches.is_present(options::traverse::TRAVERSE) {
|
||||
FTS_COMFOLLOW | FTS_PHYSICAL
|
||||
} else if matches.is_present(options::traverse::EVERY) {
|
||||
FTS_LOGICAL
|
||||
} else {
|
||||
FTS_PHYSICAL
|
||||
};
|
||||
|
||||
let recursive = matches.is_present(options::RECURSIVE);
|
||||
if recursive {
|
||||
if bit_flag == FTS_PHYSICAL {
|
||||
if derefer == 1 {
|
||||
|
@ -106,27 +226,20 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
bit_flag = FTS_PHYSICAL;
|
||||
}
|
||||
|
||||
let verbosity = if matches.opt_present("changes") {
|
||||
let verbosity = if matches.is_present(options::verbosity::CHANGES) {
|
||||
Verbosity::Changes
|
||||
} else if matches.opt_present("silent") || matches.opt_present("quiet") {
|
||||
} else if matches.is_present(options::verbosity::SILENT)
|
||||
|| matches.is_present(options::verbosity::QUIET)
|
||||
{
|
||||
Verbosity::Silent
|
||||
} else if matches.opt_present("verbose") {
|
||||
} else if matches.is_present(options::verbosity::VERBOSE) {
|
||||
Verbosity::Verbose
|
||||
} else {
|
||||
Verbosity::Normal
|
||||
};
|
||||
|
||||
if matches.free.is_empty() {
|
||||
show_usage_error!("missing operand");
|
||||
return 1;
|
||||
} else if matches.free.len() < 2 && !matches.opt_present("reference") {
|
||||
show_usage_error!("missing operand after ‘{}’", matches.free[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
let dest_gid: gid_t;
|
||||
let mut files;
|
||||
if let Some(file) = matches.opt_str("reference") {
|
||||
let dest_gid: u32;
|
||||
if let Some(file) = matches.value_of(options::REFERENCE) {
|
||||
match fs::metadata(&file) {
|
||||
Ok(meta) => {
|
||||
dest_gid = meta.gid();
|
||||
|
@ -136,19 +249,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
return 1;
|
||||
}
|
||||
}
|
||||
files = matches.free;
|
||||
} else {
|
||||
match entries::grp2gid(&matches.free[0]) {
|
||||
let group = matches.value_of(options::ARG_GROUP).unwrap_or_default();
|
||||
match entries::grp2gid(group) {
|
||||
Ok(g) => {
|
||||
dest_gid = g;
|
||||
}
|
||||
_ => {
|
||||
show_error!("invalid group: {}", matches.free[0].as_str());
|
||||
show_error!("invalid group: {}", group);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
files = matches.free;
|
||||
files.remove(0);
|
||||
}
|
||||
|
||||
let executor = Chgrper {
|
||||
|
|
|
@ -278,37 +278,25 @@ fn parse_spec(spec: &str) -> Result<(Option<u32>, Option<u32>), String> {
|
|||
let usr_only = args.len() == 1 && !args[0].is_empty();
|
||||
let grp_only = args.len() == 2 && args[0].is_empty();
|
||||
let usr_grp = args.len() == 2 && !args[0].is_empty() && !args[1].is_empty();
|
||||
|
||||
if usr_only {
|
||||
Ok((
|
||||
Some(match Passwd::locate(args[0]) {
|
||||
Ok(v) => v.uid(),
|
||||
_ => return Err(format!("invalid user: ‘{}’", spec)),
|
||||
}),
|
||||
None,
|
||||
))
|
||||
} else if grp_only {
|
||||
Ok((
|
||||
None,
|
||||
Some(match Group::locate(args[1]) {
|
||||
Ok(v) => v.gid(),
|
||||
_ => return Err(format!("invalid group: ‘{}’", spec)),
|
||||
}),
|
||||
))
|
||||
} else if usr_grp {
|
||||
Ok((
|
||||
Some(match Passwd::locate(args[0]) {
|
||||
Ok(v) => v.uid(),
|
||||
_ => return Err(format!("invalid user: ‘{}’", spec)),
|
||||
}),
|
||||
Some(match Group::locate(args[1]) {
|
||||
Ok(v) => v.gid(),
|
||||
_ => return Err(format!("invalid group: ‘{}’", spec)),
|
||||
}),
|
||||
))
|
||||
let uid = if usr_only || usr_grp {
|
||||
Some(
|
||||
Passwd::locate(args[0])
|
||||
.map_err(|_| format!("invalid user: ‘{}’", spec))?
|
||||
.uid(),
|
||||
)
|
||||
} else {
|
||||
Ok((None, None))
|
||||
}
|
||||
None
|
||||
};
|
||||
let gid = if grp_only || usr_grp {
|
||||
Some(
|
||||
Group::locate(args[1])
|
||||
.map_err(|_| format!("invalid group: ‘{}’", spec))?
|
||||
.gid(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok((uid, gid))
|
||||
}
|
||||
|
||||
enum IfFrom {
|
||||
|
@ -497,3 +485,17 @@ impl Chowner {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_spec() {
|
||||
assert_eq!(parse_spec(":"), Ok((None, None)));
|
||||
assert!(parse_spec("::")
|
||||
.err()
|
||||
.unwrap()
|
||||
.starts_with("invalid group: "));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ mod options {
|
|||
pub const GROUP: &str = "group";
|
||||
pub const GROUPS: &str = "groups";
|
||||
pub const USERSPEC: &str = "userspec";
|
||||
pub const COMMAND: &str = "command";
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
|
@ -39,7 +40,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.usage(SYNTAX)
|
||||
.arg(Arg::with_name(options::NEWROOT).hidden(true).required(true))
|
||||
.arg(
|
||||
Arg::with_name(options::NEWROOT)
|
||||
.hidden(true)
|
||||
.required(true)
|
||||
.index(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::USER)
|
||||
.short("u")
|
||||
|
@ -71,6 +77,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
)
|
||||
.value_name("USER:GROUP"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::COMMAND)
|
||||
.hidden(true)
|
||||
.multiple(true)
|
||||
.index(2),
|
||||
)
|
||||
.get_matches_from(args);
|
||||
|
||||
let default_shell: &'static str = "/bin/sh";
|
||||
|
@ -94,7 +106,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
);
|
||||
}
|
||||
|
||||
let command: Vec<&str> = match matches.args.len() {
|
||||
let commands = match matches.values_of(options::COMMAND) {
|
||||
Some(v) => v.collect(),
|
||||
None => vec![],
|
||||
};
|
||||
|
||||
// TODO: refactor the args and command matching
|
||||
// See: https://github.com/uutils/coreutils/pull/2365#discussion_r647849967
|
||||
let command: Vec<&str> = match commands.len() {
|
||||
1 => {
|
||||
let shell: &str = match user_shell {
|
||||
Err(_) => default_shell,
|
||||
|
@ -102,14 +121,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
};
|
||||
vec![shell, default_option]
|
||||
}
|
||||
_ => {
|
||||
let mut vector: Vec<&str> = Vec::new();
|
||||
for (&k, v) in matches.args.iter() {
|
||||
vector.push(k);
|
||||
vector.push(v.vals[0].to_str().unwrap());
|
||||
}
|
||||
vector
|
||||
}
|
||||
_ => commands,
|
||||
};
|
||||
|
||||
set_context(newroot, &matches);
|
||||
|
|
|
@ -160,18 +160,14 @@ fn cksum(fname: &str) -> io::Result<(u32, usize)> {
|
|||
|
||||
let mut bytes = init_byte_array();
|
||||
loop {
|
||||
match rd.read(&mut bytes) {
|
||||
Ok(num_bytes) => {
|
||||
if num_bytes == 0 {
|
||||
return Ok((crc_final(crc, size), size));
|
||||
}
|
||||
for &b in bytes[..num_bytes].iter() {
|
||||
crc = crc_update(crc, b);
|
||||
}
|
||||
size += num_bytes;
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
let num_bytes = rd.read(&mut bytes)?;
|
||||
if num_bytes == 0 {
|
||||
return Ok((crc_final(crc, size), size));
|
||||
}
|
||||
for &b in bytes[..num_bytes].iter() {
|
||||
crc = crc_update(crc, b);
|
||||
}
|
||||
size += num_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -50,9 +50,8 @@ fn mkdelim(col: usize, opts: &ArgMatches) -> String {
|
|||
}
|
||||
|
||||
fn ensure_nl(line: &mut String) {
|
||||
match line.chars().last() {
|
||||
Some('\n') => (),
|
||||
_ => line.push('\n'),
|
||||
if !line.ends_with('\n') {
|
||||
line.push('\n');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -709,27 +709,26 @@ fn parse_path_args(path_args: &[String], options: &Options) -> CopyResult<(Vec<S
|
|||
return Err(format!("extra operand {:?}", paths[2]).into());
|
||||
}
|
||||
|
||||
let (mut sources, target) = match options.target_dir {
|
||||
let target = match options.target_dir {
|
||||
Some(ref target) => {
|
||||
// All path args are sources, and the target dir was
|
||||
// specified separately
|
||||
(paths, PathBuf::from(target))
|
||||
PathBuf::from(target)
|
||||
}
|
||||
None => {
|
||||
// If there was no explicit target-dir, then use the last
|
||||
// path_arg
|
||||
let target = paths.pop().unwrap();
|
||||
(paths, target)
|
||||
paths.pop().unwrap()
|
||||
}
|
||||
};
|
||||
|
||||
if options.strip_trailing_slashes {
|
||||
for source in sources.iter_mut() {
|
||||
for source in paths.iter_mut() {
|
||||
*source = source.components().as_path().to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
Ok((sources, target))
|
||||
Ok((paths, target))
|
||||
}
|
||||
|
||||
fn preserve_hardlinks(
|
||||
|
@ -1271,15 +1270,15 @@ fn copy_on_write_linux(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyRes
|
|||
ReflinkMode::Always => unsafe {
|
||||
let result = ficlone(dst_file.as_raw_fd(), src_file.as_raw_fd() as *const i32);
|
||||
if result != 0 {
|
||||
return Err(format!(
|
||||
Err(format!(
|
||||
"failed to clone {:?} from {:?}: {}",
|
||||
source,
|
||||
dest,
|
||||
std::io::Error::last_os_error()
|
||||
)
|
||||
.into());
|
||||
.into())
|
||||
} else {
|
||||
return Ok(());
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
ReflinkMode::Auto => unsafe {
|
||||
|
@ -1287,11 +1286,10 @@ fn copy_on_write_linux(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyRes
|
|||
if result != 0 {
|
||||
fs::copy(source, dest).context(&*context_for(source, dest))?;
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
ReflinkMode::Never => unreachable!(),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Copies `source` to `dest` using copy-on-write if possible.
|
||||
|
|
|
@ -133,20 +133,12 @@ fn extract_patterns(args: &[String]) -> Result<Vec<Pattern>, CsplitError> {
|
|||
Some(m) => m.as_str().parse().unwrap(),
|
||||
};
|
||||
if let Some(up_to_match) = captures.name("UPTO") {
|
||||
let pattern = match Regex::new(up_to_match.as_str()) {
|
||||
Err(_) => {
|
||||
return Err(CsplitError::InvalidPattern(arg.to_string()));
|
||||
}
|
||||
Ok(reg) => reg,
|
||||
};
|
||||
let pattern = Regex::new(up_to_match.as_str())
|
||||
.map_err(|_| CsplitError::InvalidPattern(arg.to_string()))?;
|
||||
patterns.push(Pattern::UpToMatch(pattern, offset, execute_ntimes));
|
||||
} else if let Some(skip_to_match) = captures.name("SKIPTO") {
|
||||
let pattern = match Regex::new(skip_to_match.as_str()) {
|
||||
Err(_) => {
|
||||
return Err(CsplitError::InvalidPattern(arg.to_string()));
|
||||
}
|
||||
Ok(reg) => reg,
|
||||
};
|
||||
let pattern = Regex::new(skip_to_match.as_str())
|
||||
.map_err(|_| CsplitError::InvalidPattern(arg.to_string()))?;
|
||||
patterns.push(Pattern::SkipToMatch(pattern, offset, execute_ntimes));
|
||||
}
|
||||
} else if let Ok(line_number) = arg.parse::<usize>() {
|
||||
|
|
|
@ -33,13 +33,13 @@ impl SplitName {
|
|||
// get the prefix
|
||||
let prefix = prefix_opt.unwrap_or_else(|| "xx".to_string());
|
||||
// the width for the split offset
|
||||
let n_digits = match n_digits_opt {
|
||||
None => 2,
|
||||
Some(opt) => match opt.parse::<usize>() {
|
||||
Ok(digits) => digits,
|
||||
Err(_) => return Err(CsplitError::InvalidNumber(opt)),
|
||||
},
|
||||
};
|
||||
let n_digits = n_digits_opt
|
||||
.map(|opt| {
|
||||
opt.parse::<usize>()
|
||||
.map_err(|_| CsplitError::InvalidNumber(opt))
|
||||
})
|
||||
.transpose()?
|
||||
.unwrap_or(2);
|
||||
// translate the custom format into a function
|
||||
let fn_split_name: Box<dyn Fn(usize) -> String> = match format_opt {
|
||||
None => Box::new(move |n: usize| -> String {
|
||||
|
|
|
@ -19,6 +19,8 @@ clap = "2.33"
|
|||
chrono = "0.4"
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = { version="0.3", features=[] }
|
||||
|
||||
[[bin]]
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// (c) Derek Chiang <derekchiang93@gmail.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) Derek Chiang <derekchiang93@gmail.com>
|
||||
// *
|
||||
// * For the full copyright and license information, please view the LICENSE
|
||||
// * file that was distributed with this source code.
|
||||
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
@ -12,6 +12,7 @@ use chrono::prelude::DateTime;
|
|||
use chrono::Local;
|
||||
use clap::{crate_version, App, Arg};
|
||||
use std::collections::HashSet;
|
||||
use std::convert::TryFrom;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
#[cfg(not(windows))]
|
||||
|
@ -28,6 +29,7 @@ use std::os::windows::io::AsRawHandle;
|
|||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::time::{Duration, UNIX_EPOCH};
|
||||
use uucore::parse_size::{parse_size, ParseSizeError};
|
||||
use uucore::InvalidEncodingHandling;
|
||||
#[cfg(windows)]
|
||||
use winapi::shared::minwindef::{DWORD, LPVOID};
|
||||
|
@ -44,7 +46,7 @@ mod options {
|
|||
pub const NULL: &str = "0";
|
||||
pub const ALL: &str = "all";
|
||||
pub const APPARENT_SIZE: &str = "apparent-size";
|
||||
pub const BLOCK_SIZE: &str = "B";
|
||||
pub const BLOCK_SIZE: &str = "block-size";
|
||||
pub const BYTES: &str = "b";
|
||||
pub const TOTAL: &str = "c";
|
||||
pub const MAX_DEPTH: &str = "d";
|
||||
|
@ -57,6 +59,7 @@ mod options {
|
|||
pub const SI: &str = "si";
|
||||
pub const TIME: &str = "time";
|
||||
pub const TIME_STYLE: &str = "time-style";
|
||||
pub const ONE_FILE_SYSTEM: &str = "one-file-system";
|
||||
pub const FILE: &str = "FILE";
|
||||
}
|
||||
|
||||
|
@ -81,6 +84,7 @@ struct Options {
|
|||
max_depth: Option<usize>,
|
||||
total: bool,
|
||||
separate_dirs: bool,
|
||||
one_file_system: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
|
||||
|
@ -225,64 +229,22 @@ fn get_file_info(path: &Path) -> Option<FileInfo> {
|
|||
result
|
||||
}
|
||||
|
||||
fn unit_string_to_number(s: &str) -> Option<u64> {
|
||||
let mut offset = 0;
|
||||
let mut s_chars = s.chars().rev();
|
||||
|
||||
let (mut ch, multiple) = match s_chars.next() {
|
||||
Some('B') | Some('b') => ('B', 1000u64),
|
||||
Some(ch) => (ch, 1024u64),
|
||||
None => return None,
|
||||
};
|
||||
if ch == 'B' {
|
||||
ch = s_chars.next()?;
|
||||
offset += 1;
|
||||
}
|
||||
ch = ch.to_ascii_uppercase();
|
||||
|
||||
let unit = UNITS
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|&&(unit_ch, _)| unit_ch == ch)
|
||||
.map(|&(_, val)| {
|
||||
// we found a match, so increment offset
|
||||
offset += 1;
|
||||
val
|
||||
})
|
||||
.or_else(|| if multiple == 1024 { Some(0) } else { None })?;
|
||||
|
||||
let number = s[..s.len() - offset].parse::<u64>().ok()?;
|
||||
|
||||
Some(number * multiple.pow(unit))
|
||||
}
|
||||
|
||||
fn translate_to_pure_number(s: &Option<&str>) -> Option<u64> {
|
||||
match *s {
|
||||
Some(s) => unit_string_to_number(s),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn read_block_size(s: Option<&str>) -> u64 {
|
||||
match translate_to_pure_number(&s) {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
if let Some(value) = s {
|
||||
show_error!("invalid --block-size argument '{}'", value);
|
||||
};
|
||||
|
||||
for env_var in &["DU_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] {
|
||||
let env_size = env::var(env_var).ok();
|
||||
if let Some(quantity) = translate_to_pure_number(&env_size.as_deref()) {
|
||||
return quantity;
|
||||
fn read_block_size(s: Option<&str>) -> usize {
|
||||
if let Some(s) = s {
|
||||
parse_size(s)
|
||||
.unwrap_or_else(|e| crash!(1, "{}", format_error_message(e, s, options::BLOCK_SIZE)))
|
||||
} else {
|
||||
for env_var in &["DU_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] {
|
||||
if let Ok(env_size) = env::var(env_var) {
|
||||
if let Ok(v) = parse_size(&env_size) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
if env::var("POSIXLY_CORRECT").is_ok() {
|
||||
512
|
||||
} else {
|
||||
1024
|
||||
}
|
||||
}
|
||||
if env::var("POSIXLY_CORRECT").is_ok() {
|
||||
512
|
||||
} else {
|
||||
1024
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -318,6 +280,15 @@ fn du(
|
|||
Ok(entry) => match Stat::new(entry.path()) {
|
||||
Ok(this_stat) => {
|
||||
if this_stat.is_dir {
|
||||
if options.one_file_system {
|
||||
if let (Some(this_inode), Some(my_inode)) =
|
||||
(this_stat.inode, my_stat.inode)
|
||||
{
|
||||
if this_inode.dev_id != my_inode.dev_id {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
futures.push(du(this_stat, options, depth + 1, inodes));
|
||||
} else {
|
||||
if let Some(inode) = this_stat.inode {
|
||||
|
@ -439,7 +410,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.arg(
|
||||
Arg::with_name(options::BLOCK_SIZE)
|
||||
.short("B")
|
||||
.long("block-size")
|
||||
.long(options::BLOCK_SIZE)
|
||||
.value_name("SIZE")
|
||||
.help(
|
||||
"scale sizes by SIZE before printing them. \
|
||||
|
@ -533,12 +504,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.long(options::SI)
|
||||
.help("like -h, but use powers of 1000 not 1024")
|
||||
)
|
||||
// .arg(
|
||||
// Arg::with_name("one-file-system")
|
||||
// .short("x")
|
||||
// .long("one-file-system")
|
||||
// .help("skip directories on different file systems")
|
||||
// )
|
||||
.arg(
|
||||
Arg::with_name(options::ONE_FILE_SYSTEM)
|
||||
.short("x")
|
||||
.long(options::ONE_FILE_SYSTEM)
|
||||
.help("skip directories on different file systems")
|
||||
)
|
||||
// .arg(
|
||||
// Arg::with_name("")
|
||||
// .short("x")
|
||||
|
@ -603,6 +574,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
max_depth,
|
||||
total: matches.is_present(options::TOTAL),
|
||||
separate_dirs: matches.is_present(options::SEPARATE_DIRS),
|
||||
one_file_system: matches.is_present(options::ONE_FILE_SYSTEM),
|
||||
};
|
||||
|
||||
let files = match matches.value_of(options::FILE) {
|
||||
|
@ -612,7 +584,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
}
|
||||
};
|
||||
|
||||
let block_size = read_block_size(matches.value_of(options::BLOCK_SIZE));
|
||||
let block_size = u64::try_from(read_block_size(matches.value_of(options::BLOCK_SIZE))).unwrap();
|
||||
|
||||
let multiplier: u64 = if matches.is_present(options::SI) {
|
||||
1000
|
||||
|
@ -748,31 +720,27 @@ Try '{} --help' for more information.",
|
|||
0
|
||||
}
|
||||
|
||||
fn format_error_message(error: ParseSizeError, s: &str, option: &str) -> String {
|
||||
// NOTE:
|
||||
// GNU's du echos affected flag, -B or --block-size (-t or --threshold), depending user's selection
|
||||
// GNU's du does distinguish between "invalid (suffix in) argument"
|
||||
match error {
|
||||
ParseSizeError::ParseFailure(_) => format!("invalid --{} argument '{}'", option, s),
|
||||
ParseSizeError::SizeTooBig(_) => format!("--{} argument '{}' too large", option, s),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_du {
|
||||
#[allow(unused_imports)]
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_translate_to_pure_number() {
|
||||
let test_data = [
|
||||
(Some("10".to_string()), Some(10)),
|
||||
(Some("10K".to_string()), Some(10 * 1024)),
|
||||
(Some("5M".to_string()), Some(5 * 1024 * 1024)),
|
||||
(Some("900KB".to_string()), Some(900 * 1000)),
|
||||
(Some("BAD_STRING".to_string()), None),
|
||||
];
|
||||
for it in test_data.iter() {
|
||||
assert_eq!(translate_to_pure_number(&it.0.as_deref()), it.1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_block_size() {
|
||||
let test_data = [
|
||||
(Some("10".to_string()), 10),
|
||||
(Some("1024".to_string()), 1024),
|
||||
(Some("K".to_string()), 1024),
|
||||
(None, 1024),
|
||||
(Some("BAD_STRING".to_string()), 1024),
|
||||
];
|
||||
for it in test_data.iter() {
|
||||
assert_eq!(read_block_size(it.0.as_deref()), it.1);
|
||||
|
|
18
src/uu/env/src/env.rs
vendored
18
src/uu/env/src/env.rs
vendored
|
@ -82,13 +82,10 @@ fn load_config_file(opts: &mut Options) -> Result<(), i32> {
|
|||
Ini::load_from_file(file)
|
||||
};
|
||||
|
||||
let conf = match conf {
|
||||
Ok(config) => config,
|
||||
Err(error) => {
|
||||
eprintln!("env: error: \"{}\": {}", file, error);
|
||||
return Err(1);
|
||||
}
|
||||
};
|
||||
let conf = conf.map_err(|error| {
|
||||
eprintln!("env: error: \"{}\": {}", file, error);
|
||||
1
|
||||
})?;
|
||||
|
||||
for (_, prop) in &conf {
|
||||
// ignore all INI section lines (treat them as comments)
|
||||
|
@ -256,13 +253,10 @@ fn run_env(args: impl uucore::Args) -> Result<(), i32> {
|
|||
|
||||
// FIXME: this should just use execvp() (no fork()) on Unix-like systems
|
||||
match Command::new(&*prog).args(args).status() {
|
||||
Ok(exit) => {
|
||||
if !exit.success() {
|
||||
return Err(exit.code().unwrap());
|
||||
}
|
||||
}
|
||||
Ok(exit) if !exit.success() => return Err(exit.code().unwrap()),
|
||||
Err(ref err) if err.kind() == io::ErrorKind::NotFound => return Err(127),
|
||||
Err(_) => return Err(126),
|
||||
Ok(_) => (),
|
||||
}
|
||||
} else {
|
||||
// no program provided, so just dump all env vars to stdout
|
||||
|
|
|
@ -160,10 +160,8 @@ impl AstNode {
|
|||
if let AstNode::Node { operands, .. } = self {
|
||||
let mut out = Vec::with_capacity(operands.len());
|
||||
for operand in operands {
|
||||
match operand.evaluate() {
|
||||
Ok(value) => out.push(value),
|
||||
Err(reason) => return Err(reason),
|
||||
}
|
||||
let value = operand.evaluate()?;
|
||||
out.push(value);
|
||||
}
|
||||
Ok(out)
|
||||
} else {
|
||||
|
@ -252,10 +250,8 @@ fn maybe_ast_node(
|
|||
) -> Result<Box<AstNode>, String> {
|
||||
let mut operands = Vec::with_capacity(arity);
|
||||
for _ in 0..arity {
|
||||
match ast_from_rpn(rpn) {
|
||||
Err(reason) => return Err(reason),
|
||||
Ok(operand) => operands.push(operand),
|
||||
}
|
||||
let operand = ast_from_rpn(rpn)?;
|
||||
operands.push(operand);
|
||||
}
|
||||
operands.reverse();
|
||||
Ok(AstNode::new_node(token_idx, op_type, operands))
|
||||
|
@ -399,10 +395,12 @@ fn move_till_match_paren(
|
|||
op_stack: &mut TokenStack,
|
||||
) -> Result<(), String> {
|
||||
loop {
|
||||
match op_stack.pop() {
|
||||
None => return Err("syntax error (Mismatched close-parenthesis)".to_string()),
|
||||
Some((_, Token::ParOpen)) => return Ok(()),
|
||||
Some(other) => out_stack.push(other),
|
||||
let op = op_stack
|
||||
.pop()
|
||||
.ok_or_else(|| "syntax error (Mismatched close-parenthesis)".to_string())?;
|
||||
match op {
|
||||
(_, Token::ParOpen) => return Ok(()),
|
||||
other => out_stack.push(other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -462,22 +460,17 @@ fn infix_operator_and(values: &[String]) -> String {
|
|||
|
||||
fn operator_match(values: &[String]) -> Result<String, String> {
|
||||
assert!(values.len() == 2);
|
||||
let re = match Regex::with_options(&values[1], RegexOptions::REGEX_OPTION_NONE, Syntax::grep())
|
||||
{
|
||||
Ok(m) => m,
|
||||
Err(err) => return Err(err.description().to_string()),
|
||||
};
|
||||
if re.captures_len() > 0 {
|
||||
Ok(match re.captures(&values[0]) {
|
||||
Some(captures) => captures.at(1).unwrap().to_string(),
|
||||
None => "".to_string(),
|
||||
})
|
||||
let re = Regex::with_options(&values[1], RegexOptions::REGEX_OPTION_NONE, Syntax::grep())
|
||||
.map_err(|err| err.description().to_string())?;
|
||||
Ok(if re.captures_len() > 0 {
|
||||
re.captures(&values[0])
|
||||
.map(|captures| captures.at(1).unwrap())
|
||||
.unwrap_or("")
|
||||
.to_string()
|
||||
} else {
|
||||
Ok(match re.find(&values[0]) {
|
||||
Some((start, end)) => (end - start).to_string(),
|
||||
None => "0".to_string(),
|
||||
})
|
||||
}
|
||||
re.find(&values[0])
|
||||
.map_or("0".to_string(), |(start, end)| (end - start).to_string())
|
||||
})
|
||||
}
|
||||
|
||||
fn prefix_operator_length(values: &[String]) -> String {
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
// * This file is part of the uutils coreutils package.
|
||||
// *
|
||||
// * For the full copyright and license information, please view the LICENSE
|
||||
// * file that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore (vars) zlines
|
||||
|
||||
use clap::{crate_version, App, Arg};
|
||||
|
@ -75,7 +80,7 @@ fn app<'a>() -> App<'a, 'a> {
|
|||
.arg(
|
||||
Arg::with_name(options::QUIET_NAME)
|
||||
.short("q")
|
||||
.long("--quiet")
|
||||
.long("quiet")
|
||||
.visible_alias("silent")
|
||||
.help("never print headers giving file names")
|
||||
.overrides_with_all(&[options::VERBOSE_NAME, options::QUIET_NAME]),
|
||||
|
@ -108,12 +113,7 @@ where
|
|||
{
|
||||
match parse::parse_num(src) {
|
||||
Ok((n, last)) => Ok((closure(n), last)),
|
||||
Err(reason) => match reason {
|
||||
parse::ParseError::Syntax => Err(format!("'{}'", src)),
|
||||
parse::ParseError::Overflow => {
|
||||
Err(format!("'{}': Value too large for defined datatype", src))
|
||||
}
|
||||
},
|
||||
Err(e) => Err(e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -176,19 +176,11 @@ impl HeadOptions {
|
|||
options.zeroed = matches.is_present(options::ZERO_NAME);
|
||||
|
||||
let mode_and_from_end = if let Some(v) = matches.value_of(options::BYTES_NAME) {
|
||||
match parse_mode(v, Modes::Bytes) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
return Err(format!("invalid number of bytes: {}", err));
|
||||
}
|
||||
}
|
||||
parse_mode(v, Modes::Bytes)
|
||||
.map_err(|err| format!("invalid number of bytes: {}", err))?
|
||||
} else if let Some(v) = matches.value_of(options::LINES_NAME) {
|
||||
match parse_mode(v, Modes::Lines) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
return Err(format!("invalid number of lines: {}", err));
|
||||
}
|
||||
}
|
||||
parse_mode(v, Modes::Lines)
|
||||
.map_err(|err| format!("invalid number of lines: {}", err))?
|
||||
} else {
|
||||
(Modes::Lines(10), false)
|
||||
};
|
||||
|
@ -474,7 +466,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
let args = match HeadOptions::get_from(args) {
|
||||
Ok(o) => o,
|
||||
Err(s) => {
|
||||
crash!(EXIT_FAILURE, "head: {}", s);
|
||||
crash!(EXIT_FAILURE, "{}", s);
|
||||
}
|
||||
};
|
||||
match uu_head(&args) {
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
use std::convert::TryFrom;
|
||||
// * This file is part of the uutils coreutils package.
|
||||
// *
|
||||
// * For the full copyright and license information, please view the LICENSE
|
||||
// * file that was distributed with this source code.
|
||||
|
||||
use std::ffi::OsString;
|
||||
use uucore::parse_size::{parse_size, ParseSizeError};
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum ParseError {
|
||||
|
@ -92,92 +97,25 @@ pub fn parse_obsolete(src: &str) -> Option<Result<impl Iterator<Item = OsString>
|
|||
}
|
||||
/// Parses an -c or -n argument,
|
||||
/// the bool specifies whether to read from the end
|
||||
pub fn parse_num(src: &str) -> Result<(usize, bool), ParseError> {
|
||||
let mut num_start = 0;
|
||||
let mut chars = src.char_indices();
|
||||
let (mut chars, all_but_last) = match chars.next() {
|
||||
Some((_, c)) => {
|
||||
pub fn parse_num(src: &str) -> Result<(usize, bool), ParseSizeError> {
|
||||
let mut size_string = src.trim();
|
||||
let mut all_but_last = false;
|
||||
|
||||
if let Some(c) = size_string.chars().next() {
|
||||
if c == '+' || c == '-' {
|
||||
// head: '+' is not documented (8.32 man pages)
|
||||
size_string = &size_string[1..];
|
||||
if c == '-' {
|
||||
num_start += 1;
|
||||
(chars, true)
|
||||
} else {
|
||||
(src.char_indices(), false)
|
||||
all_but_last = true;
|
||||
}
|
||||
}
|
||||
None => return Err(ParseError::Syntax),
|
||||
};
|
||||
let mut num_end = 0usize;
|
||||
let mut last_char = 0 as char;
|
||||
let mut num_count = 0usize;
|
||||
for (n, c) in &mut chars {
|
||||
if c.is_numeric() {
|
||||
num_end = n;
|
||||
num_count += 1;
|
||||
} else {
|
||||
last_char = c;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
return Err(ParseSizeError::ParseFailure(src.to_string()));
|
||||
}
|
||||
|
||||
let num = if num_count > 0 {
|
||||
match src[num_start..=num_end].parse::<usize>() {
|
||||
Ok(n) => Some(n),
|
||||
Err(_) => return Err(ParseError::Overflow),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if last_char == 0 as char {
|
||||
if let Some(n) = num {
|
||||
Ok((n, all_but_last))
|
||||
} else {
|
||||
Err(ParseError::Syntax)
|
||||
}
|
||||
} else {
|
||||
let base: u128 = match chars.next() {
|
||||
Some((_, c)) => {
|
||||
let b = match c {
|
||||
'B' if last_char != 'b' => 1000,
|
||||
'i' if last_char != 'b' => {
|
||||
if let Some((_, 'B')) = chars.next() {
|
||||
1024
|
||||
} else {
|
||||
return Err(ParseError::Syntax);
|
||||
}
|
||||
}
|
||||
_ => return Err(ParseError::Syntax),
|
||||
};
|
||||
if chars.next().is_some() {
|
||||
return Err(ParseError::Syntax);
|
||||
} else {
|
||||
b
|
||||
}
|
||||
}
|
||||
None => 1024,
|
||||
};
|
||||
let mul = match last_char.to_lowercase().next().unwrap() {
|
||||
'b' => 512,
|
||||
'k' => base.pow(1),
|
||||
'm' => base.pow(2),
|
||||
'g' => base.pow(3),
|
||||
't' => base.pow(4),
|
||||
'p' => base.pow(5),
|
||||
'e' => base.pow(6),
|
||||
'z' => base.pow(7),
|
||||
'y' => base.pow(8),
|
||||
_ => return Err(ParseError::Syntax),
|
||||
};
|
||||
let mul = match usize::try_from(mul) {
|
||||
Ok(n) => n,
|
||||
Err(_) => return Err(ParseError::Overflow),
|
||||
};
|
||||
match num.unwrap_or(1).checked_mul(mul) {
|
||||
Some(n) => Ok((n, all_but_last)),
|
||||
None => Err(ParseError::Overflow),
|
||||
}
|
||||
}
|
||||
parse_size(size_string).map(|n| (n, all_but_last))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -195,44 +133,6 @@ mod tests {
|
|||
Some(Ok(src.iter().map(|s| s.to_string()).collect()))
|
||||
}
|
||||
#[test]
|
||||
#[cfg(not(target_pointer_width = "128"))]
|
||||
fn test_parse_overflow_x64() {
|
||||
assert_eq!(parse_num("1Y"), Err(ParseError::Overflow));
|
||||
assert_eq!(parse_num("1Z"), Err(ParseError::Overflow));
|
||||
assert_eq!(parse_num("100E"), Err(ParseError::Overflow));
|
||||
assert_eq!(parse_num("100000P"), Err(ParseError::Overflow));
|
||||
assert_eq!(parse_num("1000000000T"), Err(ParseError::Overflow));
|
||||
assert_eq!(
|
||||
parse_num("10000000000000000000000"),
|
||||
Err(ParseError::Overflow)
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
fn test_parse_overflow_x32() {
|
||||
assert_eq!(parse_num("1T"), Err(ParseError::Overflow));
|
||||
assert_eq!(parse_num("1000G"), Err(ParseError::Overflow));
|
||||
}
|
||||
#[test]
|
||||
fn test_parse_bad_syntax() {
|
||||
assert_eq!(parse_num("5MiB nonsense"), Err(ParseError::Syntax));
|
||||
assert_eq!(parse_num("Nonsense string"), Err(ParseError::Syntax));
|
||||
assert_eq!(parse_num("5mib"), Err(ParseError::Syntax));
|
||||
assert_eq!(parse_num("biB"), Err(ParseError::Syntax));
|
||||
assert_eq!(parse_num("-"), Err(ParseError::Syntax));
|
||||
assert_eq!(parse_num(""), Err(ParseError::Syntax));
|
||||
}
|
||||
#[test]
|
||||
fn test_parse_numbers() {
|
||||
assert_eq!(parse_num("k"), Ok((1024, false)));
|
||||
assert_eq!(parse_num("MiB"), Ok((1024 * 1024, false)));
|
||||
assert_eq!(parse_num("-5"), Ok((5, true)));
|
||||
assert_eq!(parse_num("b"), Ok((512, false)));
|
||||
assert_eq!(parse_num("-2GiB"), Ok((2 * 1024 * 1024 * 1024, true)));
|
||||
assert_eq!(parse_num("5M"), Ok((5 * 1024 * 1024, false)));
|
||||
assert_eq!(parse_num("5MB"), Ok((5 * 1000 * 1000, false)));
|
||||
}
|
||||
#[test]
|
||||
fn test_parse_numbers_obsolete() {
|
||||
assert_eq!(obsolete("-5"), obsolete_result(&["-n", "5"]));
|
||||
assert_eq!(obsolete("-100"), obsolete_result(&["-n", "100"]));
|
||||
|
|
|
@ -299,29 +299,17 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
|
|||
let considering_dir: bool = MainFunction::Directory == main_function;
|
||||
|
||||
let specified_mode: Option<u32> = if matches.is_present(OPT_MODE) {
|
||||
match matches.value_of(OPT_MODE) {
|
||||
Some(x) => match mode::parse(x, considering_dir) {
|
||||
Ok(y) => Some(y),
|
||||
Err(err) => {
|
||||
show_error!("Invalid mode string: {}", err);
|
||||
return Err(1);
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err(1);
|
||||
}
|
||||
}
|
||||
let x = matches.value_of(OPT_MODE).ok_or(1)?;
|
||||
Some(mode::parse(x, considering_dir).map_err(|err| {
|
||||
show_error!("Invalid mode string: {}", err);
|
||||
1
|
||||
})?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let backup_suffix = if matches.is_present(OPT_SUFFIX) {
|
||||
match matches.value_of(OPT_SUFFIX) {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
return Err(1);
|
||||
}
|
||||
}
|
||||
matches.value_of(OPT_SUFFIX).ok_or(1)?
|
||||
} else {
|
||||
"~"
|
||||
};
|
||||
|
|
|
@ -27,7 +27,6 @@ use uucore::fs::{canonicalize, CanonicalizeMode};
|
|||
pub struct Settings {
|
||||
overwrite: OverwriteMode,
|
||||
backup: BackupMode,
|
||||
force: bool,
|
||||
suffix: String,
|
||||
symbolic: bool,
|
||||
relative: bool,
|
||||
|
@ -54,7 +53,7 @@ pub enum BackupMode {
|
|||
|
||||
fn get_usage() -> String {
|
||||
format!(
|
||||
"{0} [OPTION]... [-T] TARGET LINK_executable!() (1st form)
|
||||
"{0} [OPTION]... [-T] TARGET LINK_NAME (1st form)
|
||||
{0} [OPTION]... TARGET (2nd form)
|
||||
{0} [OPTION]... TARGET... DIRECTORY (3rd form)
|
||||
{0} [OPTION]... -t DIRECTORY TARGET... (4th form)",
|
||||
|
@ -64,7 +63,7 @@ fn get_usage() -> String {
|
|||
|
||||
fn get_long_usage() -> String {
|
||||
String::from(
|
||||
" In the 1st form, create a link to TARGET with the name LINK_executable!().
|
||||
" In the 1st form, create a link to TARGET with the name LINK_NAME.
|
||||
In the 2nd form, create a link to TARGET in the current directory.
|
||||
In the 3rd and 4th forms, create links to each TARGET in DIRECTORY.
|
||||
Create hard links by default, symbolic links with --symbolic.
|
||||
|
@ -78,17 +77,19 @@ fn get_long_usage() -> String {
|
|||
|
||||
static ABOUT: &str = "change file owner and group";
|
||||
|
||||
static OPT_B: &str = "b";
|
||||
static OPT_BACKUP: &str = "backup";
|
||||
static OPT_FORCE: &str = "force";
|
||||
static OPT_INTERACTIVE: &str = "interactive";
|
||||
static OPT_NO_DEREFERENCE: &str = "no-dereference";
|
||||
static OPT_SYMBOLIC: &str = "symbolic";
|
||||
static OPT_SUFFIX: &str = "suffix";
|
||||
static OPT_TARGET_DIRECTORY: &str = "target-directory";
|
||||
static OPT_NO_TARGET_DIRECTORY: &str = "no-target-directory";
|
||||
static OPT_RELATIVE: &str = "relative";
|
||||
static OPT_VERBOSE: &str = "verbose";
|
||||
mod options {
|
||||
pub const B: &str = "b";
|
||||
pub const BACKUP: &str = "backup";
|
||||
pub const FORCE: &str = "force";
|
||||
pub const INTERACTIVE: &str = "interactive";
|
||||
pub const NO_DEREFERENCE: &str = "no-dereference";
|
||||
pub const SYMBOLIC: &str = "symbolic";
|
||||
pub const SUFFIX: &str = "suffix";
|
||||
pub const TARGET_DIRECTORY: &str = "target-directory";
|
||||
pub const NO_TARGET_DIRECTORY: &str = "no-target-directory";
|
||||
pub const RELATIVE: &str = "relative";
|
||||
pub const VERBOSE: &str = "verbose";
|
||||
}
|
||||
|
||||
static ARG_FILES: &str = "files";
|
||||
|
||||
|
@ -101,49 +102,44 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.about(ABOUT)
|
||||
.usage(&usage[..])
|
||||
.after_help(&long_usage[..])
|
||||
.arg(Arg::with_name(OPT_B).short(OPT_B).help(
|
||||
.arg(Arg::with_name(options::B).short(options::B).help(
|
||||
"make a backup of each file that would otherwise be overwritten or \
|
||||
removed",
|
||||
))
|
||||
.arg(
|
||||
Arg::with_name(OPT_BACKUP)
|
||||
.long(OPT_BACKUP)
|
||||
Arg::with_name(options::BACKUP)
|
||||
.long(options::BACKUP)
|
||||
.help(
|
||||
"make a backup of each file that would otherwise be overwritten \
|
||||
or removed",
|
||||
)
|
||||
.takes_value(true)
|
||||
.possible_value("simple")
|
||||
.possible_value("never")
|
||||
.possible_value("numbered")
|
||||
.possible_value("t")
|
||||
.possible_value("existing")
|
||||
.possible_value("nil")
|
||||
.possible_value("none")
|
||||
.possible_value("off")
|
||||
.possible_values(&[
|
||||
"simple", "never", "numbered", "t", "existing", "nil", "none", "off",
|
||||
])
|
||||
.value_name("METHOD"),
|
||||
)
|
||||
// TODO: opts.arg(
|
||||
// Arg::with_name(("d", "directory", "allow users with appropriate privileges to attempt \
|
||||
// to make hard links to directories");
|
||||
.arg(
|
||||
Arg::with_name(OPT_FORCE)
|
||||
Arg::with_name(options::FORCE)
|
||||
.short("f")
|
||||
.long(OPT_FORCE)
|
||||
.long(options::FORCE)
|
||||
.help("remove existing destination files"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_INTERACTIVE)
|
||||
Arg::with_name(options::INTERACTIVE)
|
||||
.short("i")
|
||||
.long(OPT_INTERACTIVE)
|
||||
.long(options::INTERACTIVE)
|
||||
.help("prompt whether to remove existing destination files"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_NO_DEREFERENCE)
|
||||
Arg::with_name(options::NO_DEREFERENCE)
|
||||
.short("n")
|
||||
.long(OPT_NO_DEREFERENCE)
|
||||
.long(options::NO_DEREFERENCE)
|
||||
.help(
|
||||
"treat LINK_executable!() as a normal file if it is a \
|
||||
"treat LINK_NAME as a normal file if it is a \
|
||||
symbolic link to a directory",
|
||||
),
|
||||
)
|
||||
|
@ -153,43 +149,46 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
// TODO: opts.arg(
|
||||
// Arg::with_name(("P", "physical", "make hard links directly to symbolic links");
|
||||
.arg(
|
||||
Arg::with_name(OPT_SYMBOLIC)
|
||||
Arg::with_name(options::SYMBOLIC)
|
||||
.short("s")
|
||||
.long("symbolic")
|
||||
.help("make symbolic links instead of hard links"),
|
||||
.help("make symbolic links instead of hard links")
|
||||
// override added for https://github.com/uutils/coreutils/issues/2359
|
||||
.overrides_with(options::SYMBOLIC),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_SUFFIX)
|
||||
Arg::with_name(options::SUFFIX)
|
||||
.short("S")
|
||||
.long(OPT_SUFFIX)
|
||||
.long(options::SUFFIX)
|
||||
.help("override the usual backup suffix")
|
||||
.value_name("SUFFIX")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_TARGET_DIRECTORY)
|
||||
Arg::with_name(options::TARGET_DIRECTORY)
|
||||
.short("t")
|
||||
.long(OPT_TARGET_DIRECTORY)
|
||||
.long(options::TARGET_DIRECTORY)
|
||||
.help("specify the DIRECTORY in which to create the links")
|
||||
.value_name("DIRECTORY")
|
||||
.conflicts_with(OPT_NO_TARGET_DIRECTORY),
|
||||
.conflicts_with(options::NO_TARGET_DIRECTORY),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_NO_TARGET_DIRECTORY)
|
||||
Arg::with_name(options::NO_TARGET_DIRECTORY)
|
||||
.short("T")
|
||||
.long(OPT_NO_TARGET_DIRECTORY)
|
||||
.help("treat LINK_executable!() as a normal file always"),
|
||||
.long(options::NO_TARGET_DIRECTORY)
|
||||
.help("treat LINK_NAME as a normal file always"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_RELATIVE)
|
||||
Arg::with_name(options::RELATIVE)
|
||||
.short("r")
|
||||
.long(OPT_RELATIVE)
|
||||
.help("create symbolic links relative to link location"),
|
||||
.long(options::RELATIVE)
|
||||
.help("create symbolic links relative to link location")
|
||||
.requires(options::SYMBOLIC),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_VERBOSE)
|
||||
Arg::with_name(options::VERBOSE)
|
||||
.short("v")
|
||||
.long(OPT_VERBOSE)
|
||||
.long(options::VERBOSE)
|
||||
.help("print name of each linked file"),
|
||||
)
|
||||
.arg(
|
||||
|
@ -209,18 +208,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.map(PathBuf::from)
|
||||
.collect();
|
||||
|
||||
let overwrite_mode = if matches.is_present(OPT_FORCE) {
|
||||
let overwrite_mode = if matches.is_present(options::FORCE) {
|
||||
OverwriteMode::Force
|
||||
} else if matches.is_present(OPT_INTERACTIVE) {
|
||||
} else if matches.is_present(options::INTERACTIVE) {
|
||||
OverwriteMode::Interactive
|
||||
} else {
|
||||
OverwriteMode::NoClobber
|
||||
};
|
||||
|
||||
let backup_mode = if matches.is_present(OPT_B) {
|
||||
let backup_mode = if matches.is_present(options::B) {
|
||||
BackupMode::ExistingBackup
|
||||
} else if matches.is_present(OPT_BACKUP) {
|
||||
match matches.value_of(OPT_BACKUP) {
|
||||
} else if matches.is_present(options::BACKUP) {
|
||||
match matches.value_of(options::BACKUP) {
|
||||
None => BackupMode::ExistingBackup,
|
||||
Some(mode) => match mode {
|
||||
"simple" | "never" => BackupMode::SimpleBackup,
|
||||
|
@ -234,8 +233,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
BackupMode::NoBackup
|
||||
};
|
||||
|
||||
let backup_suffix = if matches.is_present(OPT_SUFFIX) {
|
||||
matches.value_of(OPT_SUFFIX).unwrap()
|
||||
let backup_suffix = if matches.is_present(options::SUFFIX) {
|
||||
matches.value_of(options::SUFFIX).unwrap()
|
||||
} else {
|
||||
"~"
|
||||
};
|
||||
|
@ -243,14 +242,15 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
let settings = Settings {
|
||||
overwrite: overwrite_mode,
|
||||
backup: backup_mode,
|
||||
force: matches.is_present(OPT_FORCE),
|
||||
suffix: backup_suffix.to_string(),
|
||||
symbolic: matches.is_present(OPT_SYMBOLIC),
|
||||
relative: matches.is_present(OPT_RELATIVE),
|
||||
target_dir: matches.value_of(OPT_TARGET_DIRECTORY).map(String::from),
|
||||
no_target_dir: matches.is_present(OPT_NO_TARGET_DIRECTORY),
|
||||
no_dereference: matches.is_present(OPT_NO_DEREFERENCE),
|
||||
verbose: matches.is_present(OPT_VERBOSE),
|
||||
symbolic: matches.is_present(options::SYMBOLIC),
|
||||
relative: matches.is_present(options::RELATIVE),
|
||||
target_dir: matches
|
||||
.value_of(options::TARGET_DIRECTORY)
|
||||
.map(String::from),
|
||||
no_target_dir: matches.is_present(options::NO_TARGET_DIRECTORY),
|
||||
no_dereference: matches.is_present(options::NO_DEREFERENCE),
|
||||
verbose: matches.is_present(options::VERBOSE),
|
||||
};
|
||||
|
||||
exec(&paths[..], &settings)
|
||||
|
@ -310,47 +310,48 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings)
|
|||
|
||||
let mut all_successful = true;
|
||||
for srcpath in files.iter() {
|
||||
let targetpath = if settings.no_dereference && settings.force {
|
||||
// In that case, we don't want to do link resolution
|
||||
// We need to clean the target
|
||||
if is_symlink(target_dir) {
|
||||
if target_dir.is_file() {
|
||||
if let Err(e) = fs::remove_file(target_dir) {
|
||||
show_error!("Could not update {}: {}", target_dir.display(), e)
|
||||
};
|
||||
}
|
||||
if target_dir.is_dir() {
|
||||
// Not sure why but on Windows, the symlink can be
|
||||
// considered as a dir
|
||||
// See test_ln::test_symlink_no_deref_dir
|
||||
if let Err(e) = fs::remove_dir(target_dir) {
|
||||
show_error!("Could not update {}: {}", target_dir.display(), e)
|
||||
};
|
||||
}
|
||||
}
|
||||
target_dir.to_path_buf()
|
||||
} else {
|
||||
match srcpath.as_os_str().to_str() {
|
||||
Some(name) => {
|
||||
match Path::new(name).file_name() {
|
||||
Some(basename) => target_dir.join(basename),
|
||||
// This can be None only for "." or "..". Trying
|
||||
// to create a link with such name will fail with
|
||||
// EEXIST, which agrees with the behavior of GNU
|
||||
// coreutils.
|
||||
None => target_dir.join(name),
|
||||
let targetpath =
|
||||
if settings.no_dereference && matches!(settings.overwrite, OverwriteMode::Force) {
|
||||
// In that case, we don't want to do link resolution
|
||||
// We need to clean the target
|
||||
if is_symlink(target_dir) {
|
||||
if target_dir.is_file() {
|
||||
if let Err(e) = fs::remove_file(target_dir) {
|
||||
show_error!("Could not update {}: {}", target_dir.display(), e)
|
||||
};
|
||||
}
|
||||
if target_dir.is_dir() {
|
||||
// Not sure why but on Windows, the symlink can be
|
||||
// considered as a dir
|
||||
// See test_ln::test_symlink_no_deref_dir
|
||||
if let Err(e) = fs::remove_dir(target_dir) {
|
||||
show_error!("Could not update {}: {}", target_dir.display(), e)
|
||||
};
|
||||
}
|
||||
}
|
||||
None => {
|
||||
show_error!(
|
||||
"cannot stat '{}': No such file or directory",
|
||||
srcpath.display()
|
||||
);
|
||||
all_successful = false;
|
||||
continue;
|
||||
target_dir.to_path_buf()
|
||||
} else {
|
||||
match srcpath.as_os_str().to_str() {
|
||||
Some(name) => {
|
||||
match Path::new(name).file_name() {
|
||||
Some(basename) => target_dir.join(basename),
|
||||
// This can be None only for "." or "..". Trying
|
||||
// to create a link with such name will fail with
|
||||
// EEXIST, which agrees with the behavior of GNU
|
||||
// coreutils.
|
||||
None => target_dir.join(name),
|
||||
}
|
||||
}
|
||||
None => {
|
||||
show_error!(
|
||||
"cannot stat '{}': No such file or directory",
|
||||
srcpath.display()
|
||||
);
|
||||
all_successful = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
if let Err(e) = link(srcpath, &targetpath, settings) {
|
||||
show_error!(
|
||||
|
@ -371,7 +372,8 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings)
|
|||
|
||||
fn relative_path<'a>(src: &Path, dst: &Path) -> Result<Cow<'a, Path>> {
|
||||
let src_abs = canonicalize(src, CanonicalizeMode::Normal)?;
|
||||
let dst_abs = canonicalize(dst, CanonicalizeMode::Normal)?;
|
||||
let mut dst_abs = canonicalize(dst.parent().unwrap(), CanonicalizeMode::Normal)?;
|
||||
dst_abs.push(dst.components().last().unwrap());
|
||||
let suffix_pos = src_abs
|
||||
.components()
|
||||
.zip(dst_abs.components())
|
||||
|
@ -421,10 +423,6 @@ fn link(src: &Path, dst: &Path, settings: &Settings) -> Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
if settings.no_dereference && settings.force && dst.exists() {
|
||||
fs::remove_file(dst)?;
|
||||
}
|
||||
|
||||
if settings.symbolic {
|
||||
symlink(&source, dst)?;
|
||||
} else {
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
extern crate uucore;
|
||||
|
||||
use std::{
|
||||
convert::TryInto,
|
||||
fs::File,
|
||||
io::{stdin, stdout, BufReader, Read, Stdout, Write},
|
||||
path::Path,
|
||||
|
@ -32,6 +31,8 @@ use crossterm::{
|
|||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
const BELL: &str = "\x07";
|
||||
|
||||
pub mod options {
|
||||
pub const SILENT: &str = "silent";
|
||||
pub const LOGICAL: &str = "logical";
|
||||
|
@ -53,14 +54,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
let matches = App::new(executable!())
|
||||
.about("A file perusal filter for CRT viewing.")
|
||||
.version(crate_version!())
|
||||
// The commented arguments below are unimplemented:
|
||||
/*
|
||||
.arg(
|
||||
Arg::with_name(options::SILENT)
|
||||
.short("d")
|
||||
.long(options::SILENT)
|
||||
.help("Display help instead of ringing bell"),
|
||||
)
|
||||
// The commented arguments below are unimplemented:
|
||||
/*
|
||||
.arg(
|
||||
Arg::with_name(options::LOGICAL)
|
||||
.short("f")
|
||||
|
@ -140,6 +141,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.get_matches_from(args);
|
||||
|
||||
let mut buff = String::new();
|
||||
let silent = matches.is_present(options::SILENT);
|
||||
if let Some(files) = matches.values_of(options::FILES) {
|
||||
let mut stdout = setup_term();
|
||||
let length = files.len();
|
||||
|
@ -162,14 +164,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
}
|
||||
let mut reader = BufReader::new(File::open(file).unwrap());
|
||||
reader.read_to_string(&mut buff).unwrap();
|
||||
more(&buff, &mut stdout, next_file.copied());
|
||||
more(&buff, &mut stdout, next_file.copied(), silent);
|
||||
buff.clear();
|
||||
}
|
||||
reset_term(&mut stdout);
|
||||
} else if atty::isnt(atty::Stream::Stdin) {
|
||||
stdin().read_to_string(&mut buff).unwrap();
|
||||
let mut stdout = setup_term();
|
||||
more(&buff, &mut stdout, None);
|
||||
more(&buff, &mut stdout, None, silent);
|
||||
reset_term(&mut stdout);
|
||||
} else {
|
||||
show_usage_error!("bad usage");
|
||||
|
@ -204,38 +206,18 @@ fn reset_term(stdout: &mut std::io::Stdout) {
|
|||
#[inline(always)]
|
||||
fn reset_term(_: &mut usize) {}
|
||||
|
||||
fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>) {
|
||||
fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>, silent: bool) {
|
||||
let (cols, rows) = terminal::size().unwrap();
|
||||
let lines = break_buff(buff, usize::from(cols));
|
||||
let line_count: u16 = lines.len().try_into().unwrap();
|
||||
|
||||
let mut upper_mark = 0;
|
||||
let mut lines_left = line_count.saturating_sub(upper_mark + rows);
|
||||
|
||||
draw(
|
||||
&mut upper_mark,
|
||||
rows,
|
||||
&mut stdout,
|
||||
lines.clone(),
|
||||
line_count,
|
||||
next_file,
|
||||
);
|
||||
|
||||
let is_last = next_file.is_none();
|
||||
|
||||
// Specifies whether we have reached the end of the file and should
|
||||
// return on the next key press. However, we immediately return when
|
||||
// this is the last file.
|
||||
let mut to_be_done = false;
|
||||
if lines_left == 0 && is_last {
|
||||
if is_last {
|
||||
return;
|
||||
} else {
|
||||
to_be_done = true;
|
||||
}
|
||||
let mut pager = Pager::new(rows as usize, lines, next_file, silent);
|
||||
pager.draw(stdout, false);
|
||||
if pager.should_close() {
|
||||
return;
|
||||
}
|
||||
|
||||
loop {
|
||||
let mut wrong_key = false;
|
||||
if event::poll(Duration::from_millis(10)).unwrap() {
|
||||
match event::read().unwrap() {
|
||||
Event::Key(KeyEvent {
|
||||
|
@ -257,59 +239,127 @@ fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>) {
|
|||
code: KeyCode::Char(' '),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => {
|
||||
upper_mark = upper_mark.saturating_add(rows.saturating_sub(1));
|
||||
pager.page_down();
|
||||
}
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Up,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => {
|
||||
upper_mark = upper_mark.saturating_sub(rows.saturating_sub(1));
|
||||
pager.page_up();
|
||||
}
|
||||
_ => {
|
||||
wrong_key = true;
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
lines_left = line_count.saturating_sub(upper_mark + rows);
|
||||
draw(
|
||||
&mut upper_mark,
|
||||
rows,
|
||||
&mut stdout,
|
||||
lines.clone(),
|
||||
line_count,
|
||||
next_file,
|
||||
);
|
||||
|
||||
if lines_left == 0 {
|
||||
if to_be_done || is_last {
|
||||
return;
|
||||
}
|
||||
to_be_done = true;
|
||||
pager.draw(stdout, wrong_key);
|
||||
if pager.should_close() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(
|
||||
upper_mark: &mut u16,
|
||||
rows: u16,
|
||||
mut stdout: &mut std::io::Stdout,
|
||||
struct Pager<'a> {
|
||||
// The current line at the top of the screen
|
||||
upper_mark: usize,
|
||||
// The number of rows that fit on the screen
|
||||
content_rows: usize,
|
||||
lines: Vec<String>,
|
||||
lc: u16,
|
||||
next_file: Option<&str>,
|
||||
) {
|
||||
execute!(stdout, terminal::Clear(terminal::ClearType::CurrentLine)).unwrap();
|
||||
let (up_mark, lower_mark) = calc_range(*upper_mark, rows, lc);
|
||||
// Reduce the row by 1 for the prompt
|
||||
let displayed_lines = lines
|
||||
.iter()
|
||||
.skip(up_mark.into())
|
||||
.take(usize::from(rows.saturating_sub(1)));
|
||||
next_file: Option<&'a str>,
|
||||
line_count: usize,
|
||||
close_on_down: bool,
|
||||
silent: bool,
|
||||
}
|
||||
|
||||
for line in displayed_lines {
|
||||
stdout
|
||||
.write_all(format!("\r{}\n", line).as_bytes())
|
||||
.unwrap();
|
||||
impl<'a> Pager<'a> {
|
||||
fn new(rows: usize, lines: Vec<String>, next_file: Option<&'a str>, silent: bool) -> Self {
|
||||
let line_count = lines.len();
|
||||
Self {
|
||||
upper_mark: 0,
|
||||
content_rows: rows - 1,
|
||||
lines,
|
||||
next_file,
|
||||
line_count,
|
||||
close_on_down: false,
|
||||
silent,
|
||||
}
|
||||
}
|
||||
|
||||
fn should_close(&mut self) -> bool {
|
||||
if self.upper_mark + self.content_rows >= self.line_count {
|
||||
if self.close_on_down {
|
||||
return true;
|
||||
}
|
||||
if self.next_file.is_none() {
|
||||
return true;
|
||||
} else {
|
||||
self.close_on_down = true;
|
||||
}
|
||||
} else {
|
||||
self.close_on_down = false;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn page_down(&mut self) {
|
||||
self.upper_mark += self.content_rows;
|
||||
}
|
||||
|
||||
fn page_up(&mut self) {
|
||||
self.upper_mark = self.upper_mark.saturating_sub(self.content_rows);
|
||||
}
|
||||
|
||||
fn draw(&self, stdout: &mut std::io::Stdout, wrong_key: bool) {
|
||||
let lower_mark = self.line_count.min(self.upper_mark + self.content_rows);
|
||||
self.draw_lines(stdout);
|
||||
self.draw_prompt(stdout, lower_mark, wrong_key);
|
||||
stdout.flush().unwrap();
|
||||
}
|
||||
|
||||
fn draw_lines(&self, stdout: &mut std::io::Stdout) {
|
||||
execute!(stdout, terminal::Clear(terminal::ClearType::CurrentLine)).unwrap();
|
||||
let displayed_lines = self
|
||||
.lines
|
||||
.iter()
|
||||
.skip(self.upper_mark)
|
||||
.take(self.content_rows);
|
||||
|
||||
for line in displayed_lines {
|
||||
stdout
|
||||
.write_all(format!("\r{}\n", line).as_bytes())
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_prompt(&self, stdout: &mut Stdout, lower_mark: usize, wrong_key: bool) {
|
||||
let status_inner = if lower_mark == self.line_count {
|
||||
format!("Next file: {}", self.next_file.unwrap_or_default())
|
||||
} else {
|
||||
format!(
|
||||
"{}%",
|
||||
(lower_mark as f64 / self.line_count as f64 * 100.0).round() as u16
|
||||
)
|
||||
};
|
||||
|
||||
let status = format!("--More--({})", status_inner);
|
||||
|
||||
let banner = match (self.silent, wrong_key) {
|
||||
(true, true) => "[Press 'h' for instructions. (unimplemented)]".to_string(),
|
||||
(true, false) => format!("{}[Press space to continue, 'q' to quit.]", status),
|
||||
(false, true) => format!("{}{}", status, BELL),
|
||||
(false, false) => status,
|
||||
};
|
||||
|
||||
write!(
|
||||
stdout,
|
||||
"\r{}{}{}",
|
||||
Attribute::Reverse,
|
||||
banner,
|
||||
Attribute::Reset
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
make_prompt_and_flush(&mut stdout, lower_mark, lc, next_file);
|
||||
*upper_mark = up_mark;
|
||||
}
|
||||
|
||||
// Break the lines on the cols of the terminal
|
||||
|
@ -350,52 +400,11 @@ fn break_line(line: &str, cols: usize) -> Vec<String> {
|
|||
lines
|
||||
}
|
||||
|
||||
// Calculate upper_mark based on certain parameters
|
||||
fn calc_range(mut upper_mark: u16, rows: u16, line_count: u16) -> (u16, u16) {
|
||||
let mut lower_mark = upper_mark.saturating_add(rows);
|
||||
|
||||
if lower_mark >= line_count {
|
||||
upper_mark = line_count.saturating_sub(rows).saturating_add(1);
|
||||
lower_mark = line_count;
|
||||
} else {
|
||||
lower_mark = lower_mark.saturating_sub(1)
|
||||
}
|
||||
(upper_mark, lower_mark)
|
||||
}
|
||||
|
||||
// Make a prompt similar to original more
|
||||
fn make_prompt_and_flush(stdout: &mut Stdout, lower_mark: u16, lc: u16, next_file: Option<&str>) {
|
||||
let status = if lower_mark == lc {
|
||||
format!("Next file: {}", next_file.unwrap_or_default())
|
||||
} else {
|
||||
format!(
|
||||
"{}%",
|
||||
(lower_mark as f64 / lc as f64 * 100.0).round() as u16
|
||||
)
|
||||
};
|
||||
write!(
|
||||
stdout,
|
||||
"\r{}--More--({}){}",
|
||||
Attribute::Reverse,
|
||||
status,
|
||||
Attribute::Reset
|
||||
)
|
||||
.unwrap();
|
||||
stdout.flush().unwrap();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{break_line, calc_range};
|
||||
use super::break_line;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
// It is good to test the above functions
|
||||
#[test]
|
||||
fn test_calc_range() {
|
||||
assert_eq!((0, 24), calc_range(0, 25, 100));
|
||||
assert_eq!((50, 74), calc_range(50, 25, 100));
|
||||
assert_eq!((76, 100), calc_range(85, 25, 100));
|
||||
}
|
||||
#[test]
|
||||
fn test_break_lines_long() {
|
||||
let mut test_string = String::with_capacity(100);
|
||||
|
|
|
@ -247,7 +247,7 @@ fn nl<T: Read>(reader: &mut BufReader<T>, settings: &Settings) {
|
|||
let mut line_filter: fn(&str, ®ex::Regex) -> bool = pass_regex;
|
||||
for mut l in reader.lines().map(|r| r.unwrap()) {
|
||||
// Sanitize the string. We want to print the newline ourselves.
|
||||
if !l.is_empty() && l.chars().rev().next().unwrap() == '\n' {
|
||||
if l.ends_with('\n') {
|
||||
l.pop();
|
||||
}
|
||||
// Next we iterate through the individual chars to see if this
|
||||
|
|
|
@ -43,6 +43,7 @@ use crate::partialreader::*;
|
|||
use crate::peekreader::*;
|
||||
use crate::prn_char::format_ascii_dump;
|
||||
use clap::{self, crate_version, AppSettings, Arg, ArgMatches};
|
||||
use uucore::parse_size::ParseSizeError;
|
||||
use uucore::InvalidEncodingHandling;
|
||||
|
||||
const PEEK_BUFFER_SIZE: usize = 4; // utf-8 can be 4 bytes
|
||||
|
@ -128,42 +129,34 @@ impl OdOptions {
|
|||
}
|
||||
};
|
||||
|
||||
let mut skip_bytes = match matches.value_of(options::SKIP_BYTES) {
|
||||
None => 0,
|
||||
Some(s) => match parse_number_of_bytes(s) {
|
||||
Ok(i) => i,
|
||||
Err(_) => {
|
||||
return Err(format!("Invalid argument --skip-bytes={}", s));
|
||||
}
|
||||
},
|
||||
};
|
||||
let mut skip_bytes = matches.value_of(options::SKIP_BYTES).map_or(0, |s| {
|
||||
parse_number_of_bytes(s).unwrap_or_else(|e| {
|
||||
crash!(1, "{}", format_error_message(e, s, options::SKIP_BYTES))
|
||||
})
|
||||
});
|
||||
|
||||
let mut label: Option<usize> = None;
|
||||
|
||||
let input_strings = match parse_inputs(&matches) {
|
||||
Ok(CommandLineInputs::FileNames(v)) => v,
|
||||
Ok(CommandLineInputs::FileAndOffset((f, s, l))) => {
|
||||
let parsed_input = parse_inputs(&matches).map_err(|e| format!("Invalid inputs: {}", e))?;
|
||||
let input_strings = match parsed_input {
|
||||
CommandLineInputs::FileNames(v) => v,
|
||||
CommandLineInputs::FileAndOffset((f, s, l)) => {
|
||||
skip_bytes = s;
|
||||
label = l;
|
||||
vec![f]
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(format!("Invalid inputs: {}", e));
|
||||
}
|
||||
};
|
||||
|
||||
let formats = match parse_format_flags(&args) {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
let formats = parse_format_flags(&args)?;
|
||||
|
||||
let mut line_bytes = matches.value_of(options::WIDTH).map_or(16, |s| {
|
||||
if matches.occurrences_of(options::WIDTH) == 0 {
|
||||
return 16;
|
||||
};
|
||||
parse_number_of_bytes(s)
|
||||
.unwrap_or_else(|e| crash!(1, "{}", format_error_message(e, s, options::WIDTH)))
|
||||
});
|
||||
|
||||
let mut line_bytes = match matches.value_of(options::WIDTH) {
|
||||
None => 16,
|
||||
Some(_) if matches.occurrences_of(options::WIDTH) == 0 => 16,
|
||||
Some(s) => s.parse::<usize>().unwrap_or(0),
|
||||
};
|
||||
let min_bytes = formats.iter().fold(1, |max, next| {
|
||||
cmp::max(max, next.formatter_item_info.byte_size)
|
||||
});
|
||||
|
@ -174,15 +167,11 @@ impl OdOptions {
|
|||
|
||||
let output_duplicates = matches.is_present(options::OUTPUT_DUPLICATES);
|
||||
|
||||
let read_bytes = match matches.value_of(options::READ_BYTES) {
|
||||
None => None,
|
||||
Some(s) => match parse_number_of_bytes(s) {
|
||||
Ok(i) => Some(i),
|
||||
Err(_) => {
|
||||
return Err(format!("Invalid argument --read-bytes={}", s));
|
||||
}
|
||||
},
|
||||
};
|
||||
let read_bytes = matches.value_of(options::READ_BYTES).map(|s| {
|
||||
parse_number_of_bytes(s).unwrap_or_else(|e| {
|
||||
crash!(1, "{}", format_error_message(e, s, options::READ_BYTES))
|
||||
})
|
||||
});
|
||||
|
||||
let radix = match matches.value_of(options::ADDRESS_RADIX) {
|
||||
None => Radix::Octal,
|
||||
|
@ -263,7 +252,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.short("S")
|
||||
.long(options::STRINGS)
|
||||
.help(
|
||||
"output strings of at least BYTES graphic chars. 3 is assumed when \
|
||||
"NotImplemented: output strings of at least BYTES graphic chars. 3 is assumed when \
|
||||
BYTES is not specified.",
|
||||
)
|
||||
.default_value("3")
|
||||
|
@ -453,8 +442,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
|
||||
let od_options = match OdOptions::new(clap_matches, args) {
|
||||
Err(s) => {
|
||||
show_usage_error!("{}", s);
|
||||
return 1;
|
||||
crash!(1, "{}", s);
|
||||
}
|
||||
Ok(o) => o,
|
||||
};
|
||||
|
@ -636,3 +624,13 @@ fn open_input_peek_reader(
|
|||
let pr = PartialReader::new(mf, skip_bytes, read_bytes);
|
||||
PeekReader::new(pr)
|
||||
}
|
||||
|
||||
fn format_error_message(error: ParseSizeError, s: &str, option: &str) -> String {
|
||||
// NOTE:
|
||||
// GNU's od echos affected flag, -N or --read-bytes (-j or --skip-bytes, etc.), depending user's selection
|
||||
// GNU's od does distinguish between "invalid (suffix in) argument"
|
||||
match error {
|
||||
ParseSizeError::ParseFailure(_) => format!("invalid --{} argument '{}'", option, s),
|
||||
ParseSizeError::SizeTooBig(_) => format!("--{} argument '{}' too large", option, s),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,10 +108,8 @@ pub fn parse_format_flags(args: &[String]) -> Result<Vec<ParsedFormatterItemInfo
|
|||
|
||||
for arg in arg_iter {
|
||||
if expect_type_string {
|
||||
match parse_type_string(arg) {
|
||||
Ok(v) => formats.extend(v.into_iter()),
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
let v = parse_type_string(arg)?;
|
||||
formats.extend(v.into_iter());
|
||||
expect_type_string = false;
|
||||
} else if arg.starts_with("--") {
|
||||
if arg.len() == 2 {
|
||||
|
@ -119,10 +117,8 @@ pub fn parse_format_flags(args: &[String]) -> Result<Vec<ParsedFormatterItemInfo
|
|||
}
|
||||
if arg.starts_with("--format=") {
|
||||
let params: String = arg.chars().skip_while(|c| *c != '=').skip(1).collect();
|
||||
match parse_type_string(¶ms) {
|
||||
Ok(v) => formats.extend(v.into_iter()),
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
let v = parse_type_string(¶ms)?;
|
||||
formats.extend(v.into_iter());
|
||||
}
|
||||
if arg == "--format" {
|
||||
expect_type_string = true;
|
||||
|
@ -145,10 +141,8 @@ pub fn parse_format_flags(args: &[String]) -> Result<Vec<ParsedFormatterItemInfo
|
|||
}
|
||||
}
|
||||
if !format_spec.is_empty() {
|
||||
match parse_type_string(&format_spec) {
|
||||
Ok(v) => formats.extend(v.into_iter()),
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
let v = parse_type_string(&format_spec)?;
|
||||
formats.extend(v.into_iter());
|
||||
expect_type_string = false;
|
||||
}
|
||||
}
|
||||
|
@ -297,30 +291,25 @@ fn parse_type_string(params: &str) -> Result<Vec<ParsedFormatterItemInfo>, Strin
|
|||
ch = chars.next();
|
||||
}
|
||||
if !decimal_size.is_empty() {
|
||||
byte_size = match decimal_size.parse() {
|
||||
Err(_) => {
|
||||
return Err(format!(
|
||||
"invalid number '{}' in format specification '{}'",
|
||||
decimal_size, params
|
||||
))
|
||||
}
|
||||
Ok(n) => n,
|
||||
}
|
||||
byte_size = decimal_size.parse().map_err(|_| {
|
||||
format!(
|
||||
"invalid number '{}' in format specification '{}'",
|
||||
decimal_size, params
|
||||
)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
if is_format_dump_char(ch, &mut show_ascii_dump) {
|
||||
ch = chars.next();
|
||||
}
|
||||
|
||||
match od_format_type(type_char, byte_size) {
|
||||
Some(ft) => formats.push(ParsedFormatterItemInfo::new(ft, show_ascii_dump)),
|
||||
None => {
|
||||
return Err(format!(
|
||||
"invalid size '{}' in format specification '{}'",
|
||||
byte_size, params
|
||||
))
|
||||
}
|
||||
}
|
||||
let ft = od_format_type(type_char, byte_size).ok_or_else(|| {
|
||||
format!(
|
||||
"invalid size '{}' in format specification '{}'",
|
||||
byte_size, params
|
||||
)
|
||||
})?;
|
||||
formats.push(ParsedFormatterItemInfo::new(ft, show_ascii_dump));
|
||||
}
|
||||
|
||||
Ok(formats)
|
||||
|
@ -331,16 +320,13 @@ pub fn parse_format_flags_str(
|
|||
args_str: &Vec<&'static str>,
|
||||
) -> Result<Vec<FormatterItemInfo>, String> {
|
||||
let args: Vec<String> = args_str.iter().map(|s| s.to_string()).collect();
|
||||
match parse_format_flags(&args) {
|
||||
Err(e) => Err(e),
|
||||
Ok(v) => {
|
||||
// tests using this function assume add_ascii_dump is not set
|
||||
Ok(v.into_iter()
|
||||
.inspect(|f| assert!(!f.add_ascii_dump))
|
||||
.map(|f| f.formatter_item_info)
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
parse_format_flags(&args).map(|v| {
|
||||
// tests using this function assume add_ascii_dump is not set
|
||||
v.into_iter()
|
||||
.inspect(|f| assert!(!f.add_ascii_dump))
|
||||
.map(|f| f.formatter_item_info)
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
pub fn parse_number_of_bytes(s: &str) -> Result<usize, &'static str> {
|
||||
use uucore::parse_size::{parse_size, ParseSizeError};
|
||||
|
||||
pub fn parse_number_of_bytes(s: &str) -> Result<usize, ParseSizeError> {
|
||||
let mut start = 0;
|
||||
let mut len = s.len();
|
||||
let mut radix = 10;
|
||||
let mut radix = 16;
|
||||
let mut multiply = 1;
|
||||
|
||||
if s.starts_with("0x") || s.starts_with("0X") {
|
||||
start = 2;
|
||||
radix = 16;
|
||||
} else if s.starts_with('0') {
|
||||
radix = 8;
|
||||
} else {
|
||||
return parse_size(&s[start..]);
|
||||
}
|
||||
|
||||
let mut ends_with = s.chars().rev();
|
||||
|
@ -56,78 +59,33 @@ pub fn parse_number_of_bytes(s: &str) -> Result<usize, &'static str> {
|
|||
Some('P') => 1000 * 1000 * 1000 * 1000 * 1000,
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
Some('E') => 1000 * 1000 * 1000 * 1000 * 1000 * 1000,
|
||||
_ => return Err("parse failed"),
|
||||
_ => return Err(ParseSizeError::ParseFailure(s.to_string())),
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match usize::from_str_radix(&s[start..len], radix) {
|
||||
Ok(i) => Ok(i * multiply),
|
||||
Err(_) => Err("parse failed"),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn parse_number_of_bytes_str(s: &str) -> Result<usize, &'static str> {
|
||||
parse_number_of_bytes(&String::from(s))
|
||||
let factor = match usize::from_str_radix(&s[start..len], radix) {
|
||||
Ok(f) => f,
|
||||
Err(e) => return Err(ParseSizeError::ParseFailure(e.to_string())),
|
||||
};
|
||||
factor
|
||||
.checked_mul(multiply)
|
||||
.ok_or_else(|| ParseSizeError::SizeTooBig(s.to_string()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_number_of_bytes() {
|
||||
// normal decimal numbers
|
||||
assert_eq!(0, parse_number_of_bytes_str("0").unwrap());
|
||||
assert_eq!(5, parse_number_of_bytes_str("5").unwrap());
|
||||
assert_eq!(999, parse_number_of_bytes_str("999").unwrap());
|
||||
assert_eq!(2 * 512, parse_number_of_bytes_str("2b").unwrap());
|
||||
assert_eq!(2 * 1024, parse_number_of_bytes_str("2k").unwrap());
|
||||
assert_eq!(4 * 1024, parse_number_of_bytes_str("4K").unwrap());
|
||||
assert_eq!(2 * 1048576, parse_number_of_bytes_str("2m").unwrap());
|
||||
assert_eq!(4 * 1048576, parse_number_of_bytes_str("4M").unwrap());
|
||||
assert_eq!(1073741824, parse_number_of_bytes_str("1G").unwrap());
|
||||
assert_eq!(2000, parse_number_of_bytes_str("2kB").unwrap());
|
||||
assert_eq!(4000, parse_number_of_bytes_str("4KB").unwrap());
|
||||
assert_eq!(2000000, parse_number_of_bytes_str("2mB").unwrap());
|
||||
assert_eq!(4000000, parse_number_of_bytes_str("4MB").unwrap());
|
||||
assert_eq!(2000000000, parse_number_of_bytes_str("2GB").unwrap());
|
||||
|
||||
// octal input
|
||||
assert_eq!(8, parse_number_of_bytes_str("010").unwrap());
|
||||
assert_eq!(8 * 512, parse_number_of_bytes_str("010b").unwrap());
|
||||
assert_eq!(8 * 1024, parse_number_of_bytes_str("010k").unwrap());
|
||||
assert_eq!(8 * 1048576, parse_number_of_bytes_str("010m").unwrap());
|
||||
assert_eq!(8, parse_number_of_bytes("010").unwrap());
|
||||
assert_eq!(8 * 512, parse_number_of_bytes("010b").unwrap());
|
||||
assert_eq!(8 * 1024, parse_number_of_bytes("010k").unwrap());
|
||||
assert_eq!(8 * 1_048_576, parse_number_of_bytes("010m").unwrap());
|
||||
|
||||
// hex input
|
||||
assert_eq!(15, parse_number_of_bytes_str("0xf").unwrap());
|
||||
assert_eq!(15, parse_number_of_bytes_str("0XF").unwrap());
|
||||
assert_eq!(27, parse_number_of_bytes_str("0x1b").unwrap());
|
||||
assert_eq!(16 * 1024, parse_number_of_bytes_str("0x10k").unwrap());
|
||||
assert_eq!(16 * 1048576, parse_number_of_bytes_str("0x10m").unwrap());
|
||||
|
||||
// invalid input
|
||||
parse_number_of_bytes_str("").unwrap_err();
|
||||
parse_number_of_bytes_str("-1").unwrap_err();
|
||||
parse_number_of_bytes_str("1e2").unwrap_err();
|
||||
parse_number_of_bytes_str("xyz").unwrap_err();
|
||||
parse_number_of_bytes_str("b").unwrap_err();
|
||||
parse_number_of_bytes_str("1Y").unwrap_err();
|
||||
parse_number_of_bytes_str("∞").unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
fn test_parse_number_of_bytes_64bits() {
|
||||
assert_eq!(1099511627776, parse_number_of_bytes_str("1T").unwrap());
|
||||
assert_eq!(1125899906842624, parse_number_of_bytes_str("1P").unwrap());
|
||||
assert_eq!(
|
||||
1152921504606846976,
|
||||
parse_number_of_bytes_str("1E").unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(2000000000000, parse_number_of_bytes_str("2TB").unwrap());
|
||||
assert_eq!(2000000000000000, parse_number_of_bytes_str("2PB").unwrap());
|
||||
assert_eq!(
|
||||
2000000000000000000,
|
||||
parse_number_of_bytes_str("2EB").unwrap()
|
||||
);
|
||||
assert_eq!(15, parse_number_of_bytes("0xf").unwrap());
|
||||
assert_eq!(15, parse_number_of_bytes("0XF").unwrap());
|
||||
assert_eq!(27, parse_number_of_bytes("0x1b").unwrap());
|
||||
assert_eq!(16 * 1024, parse_number_of_bytes("0x10k").unwrap());
|
||||
assert_eq!(16 * 1_048_576, parse_number_of_bytes("0x10m").unwrap());
|
||||
}
|
||||
|
|
|
@ -36,16 +36,15 @@ impl<R: Read> Read for PartialReader<R> {
|
|||
while self.skip > 0 {
|
||||
let skip_count = cmp::min(self.skip, MAX_SKIP_BUFFER);
|
||||
|
||||
match self.inner.read(&mut bytes[..skip_count]) {
|
||||
Ok(0) => {
|
||||
match self.inner.read(&mut bytes[..skip_count])? {
|
||||
0 => {
|
||||
// this is an error as we still have more to skip
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::UnexpectedEof,
|
||||
"tried to skip past end of input",
|
||||
));
|
||||
}
|
||||
Ok(n) => self.skip -= n,
|
||||
Err(e) => return Err(e),
|
||||
n => self.skip -= n,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -241,13 +241,14 @@ fn no_leading_hyphen(path_segment: &str) -> bool {
|
|||
|
||||
// check whether a path segment contains only valid (read: portable) characters
|
||||
fn check_portable_chars(path_segment: &str) -> bool {
|
||||
let valid_str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-".to_string();
|
||||
for ch in path_segment.chars() {
|
||||
if !valid_str.contains(ch) {
|
||||
const VALID_CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-";
|
||||
for (i, ch) in path_segment.as_bytes().iter().enumerate() {
|
||||
if !VALID_CHARS.contains(ch) {
|
||||
let invalid = path_segment[i..].chars().next().unwrap();
|
||||
writeln!(
|
||||
&mut std::io::stderr(),
|
||||
"nonportable character '{}' in file name component '{}'",
|
||||
ch,
|
||||
invalid,
|
||||
path_segment
|
||||
);
|
||||
return false;
|
||||
|
|
|
@ -401,18 +401,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
|
||||
for file_group in file_groups {
|
||||
let result_options = build_options(&matches, &file_group, args.join(" "));
|
||||
let options = match result_options {
|
||||
Ok(options) => options,
|
||||
Err(err) => {
|
||||
print_error(&matches, err);
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
if result_options.is_err() {
|
||||
print_error(&matches, result_options.err().unwrap());
|
||||
return 1;
|
||||
}
|
||||
|
||||
let options = &result_options.unwrap();
|
||||
|
||||
let cmd_result = if file_group.len() == 1 {
|
||||
pr(file_group.get(0).unwrap(), options)
|
||||
let cmd_result = if let Ok(group) = file_group.iter().exactly_one() {
|
||||
pr(group, &options)
|
||||
} else {
|
||||
mpr(&file_group, options)
|
||||
mpr(&file_group, &options)
|
||||
};
|
||||
|
||||
let status = match cmd_result {
|
||||
|
@ -442,11 +442,12 @@ fn recreate_arguments(args: &[String]) -> Vec<String> {
|
|||
let mut arguments = args.to_owned();
|
||||
let num_option = args.iter().find_position(|x| n_regex.is_match(x.trim()));
|
||||
if let Some((pos, _value)) = num_option {
|
||||
let num_val_opt = args.get(pos + 1);
|
||||
if num_val_opt.is_some() && !num_regex.is_match(num_val_opt.unwrap()) {
|
||||
let could_be_file = arguments.remove(pos + 1);
|
||||
arguments.insert(pos + 1, format!("{}", NumberingMode::default().width));
|
||||
arguments.insert(pos + 2, could_be_file);
|
||||
if let Some(num_val_opt) = args.get(pos + 1) {
|
||||
if !num_regex.is_match(num_val_opt) {
|
||||
let could_be_file = arguments.remove(pos + 1);
|
||||
arguments.insert(pos + 1, format!("{}", NumberingMode::default().width));
|
||||
arguments.insert(pos + 2, could_be_file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -666,12 +667,13 @@ fn build_options(
|
|||
None => end_page_in_plus_option,
|
||||
};
|
||||
|
||||
if end_page.is_some() && start_page > end_page.unwrap() {
|
||||
return Err(PrError::EncounteredErrors(format!(
|
||||
"invalid --pages argument '{}:{}'",
|
||||
start_page,
|
||||
end_page.unwrap()
|
||||
)));
|
||||
if let Some(end_page) = end_page {
|
||||
if start_page > end_page {
|
||||
return Err(PrError::EncounteredErrors(format!(
|
||||
"invalid --pages argument '{}:{}'",
|
||||
start_page, end_page
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
let default_lines_per_page = if form_feed_used {
|
||||
|
@ -947,7 +949,7 @@ fn read_stream_and_create_pages(
|
|||
let current_page = x + 1;
|
||||
|
||||
current_page >= start_page
|
||||
&& (last_page.is_none() || current_page <= last_page.unwrap())
|
||||
&& last_page.map_or(true, |last_page| current_page <= last_page)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
@ -996,8 +998,8 @@ fn mpr(paths: &[String], options: &OutputOptions) -> Result<i32, PrError> {
|
|||
|
||||
for (_key, file_line_group) in file_line_groups.into_iter() {
|
||||
for file_line in file_line_group {
|
||||
if file_line.line_content.is_err() {
|
||||
return Err(file_line.line_content.unwrap_err().into());
|
||||
if let Err(e) = file_line.line_content {
|
||||
return Err(e.into());
|
||||
}
|
||||
let new_page_number = file_line.page_number;
|
||||
if page_counter != new_page_number {
|
||||
|
@ -1030,8 +1032,7 @@ fn print_page(lines: &[FileLine], options: &OutputOptions, page: usize) -> Resul
|
|||
|
||||
let lines_written = write_columns(lines, options, out)?;
|
||||
|
||||
for index in 0..trailer_content.len() {
|
||||
let x = trailer_content.get(index).unwrap();
|
||||
for (index, x) in trailer_content.iter().enumerate() {
|
||||
out.write_all(x.as_bytes())?;
|
||||
if index + 1 != trailer_content.len() {
|
||||
out.write_all(line_separator)?;
|
||||
|
@ -1074,8 +1075,7 @@ fn write_columns(
|
|||
let mut offset = 0;
|
||||
for col in 0..columns {
|
||||
let mut inserted = 0;
|
||||
for i in offset..lines.len() {
|
||||
let line = lines.get(i).unwrap();
|
||||
for line in &lines[offset..] {
|
||||
if line.file_id != col {
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -55,18 +55,9 @@ impl Formatter for Decf {
|
|||
);
|
||||
// strip trailing zeroes
|
||||
if let Some(ref post_dec) = f_sci.post_decimal {
|
||||
let mut i = post_dec.len();
|
||||
{
|
||||
let mut it = post_dec.chars();
|
||||
while let Some(c) = it.next_back() {
|
||||
if c != '0' {
|
||||
break;
|
||||
}
|
||||
i -= 1;
|
||||
}
|
||||
}
|
||||
if i != post_dec.len() {
|
||||
f_sci.post_decimal = Some(String::from(&post_dec[0..i]));
|
||||
let trimmed = post_dec.trim_end_matches('0');
|
||||
if trimmed.len() != post_dec.len() {
|
||||
f_sci.post_decimal = Some(trimmed.to_owned());
|
||||
}
|
||||
}
|
||||
let f_fl = get_primitive_dec(
|
||||
|
|
|
@ -247,8 +247,12 @@ pub fn get_primitive_dec(
|
|||
first_segment.len() as isize - 1,
|
||||
)
|
||||
} else {
|
||||
match first_segment.chars().next() {
|
||||
Some('0') => {
|
||||
match first_segment
|
||||
.chars()
|
||||
.next()
|
||||
.expect("float_common: no chars in first segment.")
|
||||
{
|
||||
'0' => {
|
||||
let it = second_segment.chars().enumerate();
|
||||
let mut m: isize = 0;
|
||||
let mut pre = String::from("0");
|
||||
|
@ -266,10 +270,7 @@ pub fn get_primitive_dec(
|
|||
}
|
||||
(pre, post, m)
|
||||
}
|
||||
Some(_) => (first_segment, second_segment, 0),
|
||||
None => {
|
||||
panic!("float_common: no chars in first segment.");
|
||||
}
|
||||
_ => (first_segment, second_segment, 0),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -109,17 +109,14 @@ fn remove(dirs: Vec<String>, ignore: bool, parents: bool, verbose: bool) -> Resu
|
|||
}
|
||||
|
||||
fn remove_dir(path: &Path, ignore: bool, verbose: bool) -> Result<(), i32> {
|
||||
let mut read_dir = match fs::read_dir(path) {
|
||||
Ok(m) => m,
|
||||
Err(e) if e.raw_os_error() == Some(ENOTDIR) => {
|
||||
let mut read_dir = fs::read_dir(path).map_err(|e| {
|
||||
if e.raw_os_error() == Some(ENOTDIR) {
|
||||
show_error!("failed to remove '{}': Not a directory", path.display());
|
||||
return Err(1);
|
||||
}
|
||||
Err(e) => {
|
||||
} else {
|
||||
show_error!("reading directory '{}': {}", path.display(), e);
|
||||
return Err(1);
|
||||
}
|
||||
};
|
||||
1
|
||||
})?;
|
||||
|
||||
let mut r = Ok(());
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ extern crate uucore;
|
|||
|
||||
static NAME: &str = "shred";
|
||||
const BLOCK_SIZE: usize = 512;
|
||||
const NAME_CHARSET: &str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_.";
|
||||
const NAME_CHARSET: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_.";
|
||||
|
||||
// Patterns as shown in the GNU coreutils shred implementation
|
||||
const PATTERNS: [&[u8]; 22] = [
|
||||
|
@ -89,7 +89,7 @@ impl Iterator for FilenameGenerator {
|
|||
// Make the return value, then increment
|
||||
let mut ret = String::new();
|
||||
for i in name_charset_indices.iter() {
|
||||
let c: char = NAME_CHARSET.chars().nth(*i).unwrap();
|
||||
let c = char::from(NAME_CHARSET[*i]);
|
||||
ret.push(c);
|
||||
}
|
||||
|
||||
|
@ -163,16 +163,14 @@ impl<'a> BytesGenerator<'a> {
|
|||
return None;
|
||||
}
|
||||
|
||||
let this_block_size = {
|
||||
if !self.exact {
|
||||
let this_block_size = if !self.exact {
|
||||
self.block_size
|
||||
} else {
|
||||
let bytes_left = self.total_bytes - self.bytes_generated.get();
|
||||
if bytes_left >= self.block_size as u64 {
|
||||
self.block_size
|
||||
} else {
|
||||
let bytes_left = self.total_bytes - self.bytes_generated.get();
|
||||
if bytes_left >= self.block_size as u64 {
|
||||
self.block_size
|
||||
} else {
|
||||
(bytes_left % self.block_size as u64) as usize
|
||||
}
|
||||
(bytes_left % self.block_size as u64) as usize
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -184,12 +182,10 @@ impl<'a> BytesGenerator<'a> {
|
|||
rng.fill(bytes);
|
||||
}
|
||||
PassType::Pattern(pattern) => {
|
||||
let skip = {
|
||||
if self.bytes_generated.get() == 0 {
|
||||
0
|
||||
} else {
|
||||
(pattern.len() as u64 % self.bytes_generated.get()) as usize
|
||||
}
|
||||
let skip = if self.bytes_generated.get() == 0 {
|
||||
0
|
||||
} else {
|
||||
(pattern.len() as u64 % self.bytes_generated.get()) as usize
|
||||
};
|
||||
|
||||
// Copy the pattern in chunks rather than simply one byte at a time
|
||||
|
|
|
@ -285,14 +285,12 @@ fn parse_range(input_range: &str) -> Result<(usize, usize), String> {
|
|||
if split.len() != 2 {
|
||||
Err(format!("invalid input range: '{}'", input_range))
|
||||
} else {
|
||||
let begin = match split[0].parse::<usize>() {
|
||||
Ok(m) => m,
|
||||
Err(_) => return Err(format!("invalid input range: '{}'", split[0])),
|
||||
};
|
||||
let end = match split[1].parse::<usize>() {
|
||||
Ok(m) => m,
|
||||
Err(_) => return Err(format!("invalid input range: '{}'", split[1])),
|
||||
};
|
||||
let begin = split[0]
|
||||
.parse::<usize>()
|
||||
.map_err(|_| format!("invalid input range: '{}'", split[0]))?;
|
||||
let end = split[1]
|
||||
.parse::<usize>()
|
||||
.map_err(|_| format!("invalid input range: '{}'", split[1]))?;
|
||||
Ok((begin, end + 1))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ path = "src/sleep.rs"
|
|||
|
||||
[dependencies]
|
||||
clap = "2.33"
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["parse_time"] }
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[[bin]]
|
||||
|
|
|
@ -43,6 +43,7 @@ use std::ops::Range;
|
|||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use uucore::parse_size::{parse_size, ParseSizeError};
|
||||
use uucore::InvalidEncodingHandling;
|
||||
|
||||
static NAME: &str = "sort";
|
||||
|
@ -162,32 +163,29 @@ pub struct GlobalSettings {
|
|||
}
|
||||
|
||||
impl GlobalSettings {
|
||||
/// Interpret this `&str` as a number with an optional trailing si unit.
|
||||
///
|
||||
/// If there is no trailing si unit, the implicit unit is K.
|
||||
/// The suffix B causes the number to be interpreted as a byte count.
|
||||
fn parse_byte_count(input: &str) -> usize {
|
||||
const SI_UNITS: &[char] = &['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
|
||||
/// Parse a SIZE string into a number of bytes.
|
||||
/// A size string comprises an integer and an optional unit.
|
||||
/// The unit may be k, K, m, M, g, G, t, T, P, E, Z, Y (powers of 1024), or b which is 1.
|
||||
/// Default is K.
|
||||
fn parse_byte_count(input: &str) -> Result<usize, ParseSizeError> {
|
||||
// GNU sort (8.32) valid: 1b, k, K, m, M, g, G, t, T, P, E, Z, Y
|
||||
// GNU sort (8.32) invalid: b, B, 1B, p, e, z, y
|
||||
const ALLOW_LIST: &[char] = &[
|
||||
'b', 'k', 'K', 'm', 'M', 'g', 'G', 't', 'T', 'P', 'E', 'Z', 'Y',
|
||||
];
|
||||
let mut size_string = input.trim().to_string();
|
||||
|
||||
let input = input.trim();
|
||||
|
||||
let (num_str, si_unit) =
|
||||
if input.ends_with(|c: char| SI_UNITS.contains(&c.to_ascii_uppercase())) {
|
||||
let mut chars = input.chars();
|
||||
let si_suffix = chars.next_back().unwrap().to_ascii_uppercase();
|
||||
let si_unit = SI_UNITS.iter().position(|&c| c == si_suffix).unwrap();
|
||||
let num_str = chars.as_str();
|
||||
(num_str, si_unit)
|
||||
} else {
|
||||
(input, 1)
|
||||
};
|
||||
|
||||
let num_usize: usize = num_str
|
||||
.trim()
|
||||
.parse()
|
||||
.unwrap_or_else(|e| crash!(1, "failed to parse buffer size `{}`: {}", num_str, e));
|
||||
|
||||
num_usize.saturating_mul(1000usize.saturating_pow(si_unit as u32))
|
||||
if size_string.ends_with(|c: char| ALLOW_LIST.contains(&c) || c.is_digit(10)) {
|
||||
// b 1, K 1024 (default)
|
||||
if size_string.ends_with(|c: char| c.is_digit(10)) {
|
||||
size_string.push('K');
|
||||
} else if size_string.ends_with('b') {
|
||||
size_string.pop();
|
||||
}
|
||||
parse_size(&size_string)
|
||||
} else {
|
||||
Err(ParseSizeError::ParseFailure("invalid suffix".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
fn out_writer(&self) -> BufWriter<Box<dyn Write>> {
|
||||
|
@ -1176,8 +1174,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
|
||||
settings.buffer_size = matches
|
||||
.value_of(OPT_BUF_SIZE)
|
||||
.map(GlobalSettings::parse_byte_count)
|
||||
.unwrap_or(DEFAULT_BUF_SIZE);
|
||||
.map_or(DEFAULT_BUF_SIZE, |s| {
|
||||
GlobalSettings::parse_byte_count(s)
|
||||
.unwrap_or_else(|e| crash!(2, "{}", format_error_message(e, s, OPT_BUF_SIZE)))
|
||||
});
|
||||
|
||||
settings.tmp_dir = matches
|
||||
.value_of(OPT_TMP_DIR)
|
||||
|
@ -1587,6 +1587,16 @@ fn open(path: impl AsRef<OsStr>) -> Box<dyn Read + Send> {
|
|||
}
|
||||
}
|
||||
|
||||
fn format_error_message(error: ParseSizeError, s: &str, option: &str) -> String {
|
||||
// NOTE:
|
||||
// GNU's sort echos affected flag, -S or --buffer-size, depending user's selection
|
||||
// GNU's sort does distinguish between "invalid (suffix in) argument"
|
||||
match error {
|
||||
ParseSizeError::ParseFailure(_) => format!("invalid --{} argument '{}'", option, s),
|
||||
ParseSizeError::SizeTooBig(_) => format!("--{} argument '{}' too large", option, s),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
@ -1676,4 +1686,48 @@ mod tests {
|
|||
// How big is a selection? Constant cost all lines pay when we need selections.
|
||||
assert_eq!(std::mem::size_of::<Selection>(), 24);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_byte_count() {
|
||||
let valid_input = [
|
||||
("0", 0),
|
||||
("50K", 50 * 1024),
|
||||
("50k", 50 * 1024),
|
||||
("1M", 1024 * 1024),
|
||||
("100M", 100 * 1024 * 1024),
|
||||
#[cfg(not(target_pointer_width = "32"))]
|
||||
("1000G", 1000 * 1024 * 1024 * 1024),
|
||||
#[cfg(not(target_pointer_width = "32"))]
|
||||
("10T", 10 * 1024 * 1024 * 1024 * 1024),
|
||||
("1b", 1),
|
||||
("1024b", 1024),
|
||||
("1024Mb", 1024 * 1024 * 1024), // NOTE: This might not be how GNU `sort` behaves for 'Mb'
|
||||
("1", 1024), // K is default
|
||||
("50", 50 * 1024),
|
||||
("K", 1024),
|
||||
("k", 1024),
|
||||
("m", 1024 * 1024),
|
||||
#[cfg(not(target_pointer_width = "32"))]
|
||||
("E", 1024 * 1024 * 1024 * 1024 * 1024 * 1024),
|
||||
];
|
||||
for (input, expected_output) in &valid_input {
|
||||
assert_eq!(
|
||||
GlobalSettings::parse_byte_count(input),
|
||||
Ok(*expected_output)
|
||||
);
|
||||
}
|
||||
|
||||
// SizeTooBig
|
||||
let invalid_input = ["500E", "1Y"];
|
||||
for input in &invalid_input {
|
||||
#[cfg(not(target_pointer_width = "128"))]
|
||||
assert!(GlobalSettings::parse_byte_count(input).is_err());
|
||||
}
|
||||
|
||||
// ParseFailure
|
||||
let invalid_input = ["nonsense", "1B", "B", "b", "p", "e", "z", "y"];
|
||||
for input in &invalid_input {
|
||||
assert!(GlobalSettings::parse_byte_count(input).is_err());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,11 +13,13 @@ extern crate uucore;
|
|||
mod platform;
|
||||
|
||||
use clap::{crate_version, App, Arg};
|
||||
use std::convert::TryFrom;
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::{stdin, BufRead, BufReader, BufWriter, Read, Write};
|
||||
use std::path::Path;
|
||||
use std::{char, fs::remove_file};
|
||||
use uucore::parse_size::parse_size;
|
||||
|
||||
static NAME: &str = "split";
|
||||
|
||||
|
@ -231,10 +233,9 @@ struct LineSplitter {
|
|||
impl LineSplitter {
|
||||
fn new(settings: &Settings) -> LineSplitter {
|
||||
LineSplitter {
|
||||
lines_per_split: settings
|
||||
.strategy_param
|
||||
.parse()
|
||||
.unwrap_or_else(|e| crash!(1, "invalid number of lines: {}", e)),
|
||||
lines_per_split: settings.strategy_param.parse().unwrap_or_else(|_| {
|
||||
crash!(1, "invalid number of lines: ‘{}’", settings.strategy_param)
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -276,40 +277,14 @@ struct ByteSplitter {
|
|||
|
||||
impl ByteSplitter {
|
||||
fn new(settings: &Settings) -> ByteSplitter {
|
||||
// These multipliers are the same as supported by GNU coreutils.
|
||||
let modifiers: Vec<(&str, u128)> = vec![
|
||||
("K", 1024u128),
|
||||
("M", 1024 * 1024),
|
||||
("G", 1024 * 1024 * 1024),
|
||||
("T", 1024 * 1024 * 1024 * 1024),
|
||||
("P", 1024 * 1024 * 1024 * 1024 * 1024),
|
||||
("E", 1024 * 1024 * 1024 * 1024 * 1024 * 1024),
|
||||
("Z", 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024),
|
||||
("Y", 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024),
|
||||
("KB", 1000),
|
||||
("MB", 1000 * 1000),
|
||||
("GB", 1000 * 1000 * 1000),
|
||||
("TB", 1000 * 1000 * 1000 * 1000),
|
||||
("PB", 1000 * 1000 * 1000 * 1000 * 1000),
|
||||
("EB", 1000 * 1000 * 1000 * 1000 * 1000 * 1000),
|
||||
("ZB", 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000),
|
||||
("YB", 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000),
|
||||
];
|
||||
|
||||
// This sequential find is acceptable since none of the modifiers are
|
||||
// suffixes of any other modifiers, a la Huffman codes.
|
||||
let (suffix, multiplier) = modifiers
|
||||
.iter()
|
||||
.find(|(suffix, _)| settings.strategy_param.ends_with(suffix))
|
||||
.unwrap_or(&("", 1));
|
||||
|
||||
// Try to parse the actual numeral.
|
||||
let n = &settings.strategy_param[0..(settings.strategy_param.len() - suffix.len())]
|
||||
.parse::<u128>()
|
||||
.unwrap_or_else(|e| crash!(1, "invalid number of bytes: {}", e));
|
||||
let size_string = &settings.strategy_param;
|
||||
let size_num = match parse_size(size_string) {
|
||||
Ok(n) => n,
|
||||
Err(e) => crash!(1, "invalid number of bytes: {}", e.to_string()),
|
||||
};
|
||||
|
||||
ByteSplitter {
|
||||
bytes_per_split: n * multiplier,
|
||||
bytes_per_split: u128::try_from(size_num).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ use std::path::PathBuf;
|
|||
use std::process::Command;
|
||||
use tempfile::tempdir;
|
||||
use tempfile::TempDir;
|
||||
use uucore::parse_size::parse_size;
|
||||
use uucore::InvalidEncodingHandling;
|
||||
|
||||
static ABOUT: &str =
|
||||
|
@ -55,7 +56,7 @@ const STDBUF_INJECT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/libstdbuf
|
|||
enum BufferType {
|
||||
Default,
|
||||
Line,
|
||||
Size(u64),
|
||||
Size(usize),
|
||||
}
|
||||
|
||||
struct ProgramOptions {
|
||||
|
@ -104,41 +105,6 @@ fn preload_strings() -> (&'static str, &'static str) {
|
|||
crash!(1, "Command not supported for this operating system!")
|
||||
}
|
||||
|
||||
fn parse_size(size: &str) -> Option<u64> {
|
||||
let ext = size.trim_start_matches(|c: char| c.is_digit(10));
|
||||
let num = size.trim_end_matches(char::is_alphabetic);
|
||||
let mut recovered = num.to_owned();
|
||||
recovered.push_str(ext);
|
||||
if recovered != size {
|
||||
return None;
|
||||
}
|
||||
let buf_size: u64 = match num.parse().ok() {
|
||||
Some(m) => m,
|
||||
None => return None,
|
||||
};
|
||||
let (power, base): (u32, u64) = match ext {
|
||||
"" => (0, 0),
|
||||
"KB" => (1, 1024),
|
||||
"K" => (1, 1000),
|
||||
"MB" => (2, 1024),
|
||||
"M" => (2, 1000),
|
||||
"GB" => (3, 1024),
|
||||
"G" => (3, 1000),
|
||||
"TB" => (4, 1024),
|
||||
"T" => (4, 1000),
|
||||
"PB" => (5, 1024),
|
||||
"P" => (5, 1000),
|
||||
"EB" => (6, 1024),
|
||||
"E" => (6, 1000),
|
||||
"ZB" => (7, 1024),
|
||||
"Z" => (7, 1000),
|
||||
"YB" => (8, 1024),
|
||||
"Y" => (8, 1000),
|
||||
_ => return None,
|
||||
};
|
||||
Some(buf_size * base.pow(power))
|
||||
}
|
||||
|
||||
fn check_option(matches: &ArgMatches, name: &str) -> Result<BufferType, ProgramOptionsError> {
|
||||
match matches.value_of(name) {
|
||||
Some(value) => match value {
|
||||
|
@ -151,13 +117,10 @@ fn check_option(matches: &ArgMatches, name: &str) -> Result<BufferType, ProgramO
|
|||
Ok(BufferType::Line)
|
||||
}
|
||||
}
|
||||
x => {
|
||||
let size = match parse_size(x) {
|
||||
Some(m) => m,
|
||||
None => return Err(ProgramOptionsError(format!("invalid mode {}", x))),
|
||||
};
|
||||
Ok(BufferType::Size(size))
|
||||
}
|
||||
x => parse_size(x).map_or_else(
|
||||
|e| crash!(125, "invalid mode {}", e),
|
||||
|m| Ok(BufferType::Size(m)),
|
||||
),
|
||||
},
|
||||
None => Ok(BufferType::Default),
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
// *
|
||||
// * For the full copyright and license information, please view the LICENSE
|
||||
// * file that was distributed with this source code.
|
||||
// *
|
||||
|
||||
// spell-checker:ignore (ToDO) seekable seek'd tail'ing ringbuffer ringbuf
|
||||
|
||||
|
@ -21,19 +20,18 @@ use chunks::ReverseChunks;
|
|||
|
||||
use clap::{App, Arg};
|
||||
use std::collections::VecDeque;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::fs::File;
|
||||
use std::io::{stdin, stdout, BufRead, BufReader, Read, Seek, SeekFrom, Write};
|
||||
use std::path::Path;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
use uucore::parse_size::{parse_size, ParseSizeError};
|
||||
use uucore::ringbuffer::RingBuffer;
|
||||
|
||||
pub mod options {
|
||||
pub mod verbosity {
|
||||
pub static QUIET: &str = "quiet";
|
||||
pub static SILENT: &str = "silent";
|
||||
pub static VERBOSE: &str = "verbose";
|
||||
}
|
||||
pub static BYTES: &str = "bytes";
|
||||
|
@ -42,13 +40,12 @@ pub mod options {
|
|||
pub static PID: &str = "pid";
|
||||
pub static SLEEP_INT: &str = "sleep-interval";
|
||||
pub static ZERO_TERM: &str = "zero-terminated";
|
||||
pub static ARG_FILES: &str = "files";
|
||||
}
|
||||
|
||||
static ARG_FILES: &str = "files";
|
||||
|
||||
enum FilterMode {
|
||||
Bytes(u64),
|
||||
Lines(u64, u8), // (number of lines, delimiter)
|
||||
Bytes(usize),
|
||||
Lines(usize, u8), // (number of lines, delimiter)
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
|
@ -78,12 +75,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
let app = App::new(executable!())
|
||||
.version(crate_version!())
|
||||
.about("output the last part of files")
|
||||
// TODO: add usage
|
||||
.arg(
|
||||
Arg::with_name(options::BYTES)
|
||||
.short("c")
|
||||
.long(options::BYTES)
|
||||
.takes_value(true)
|
||||
.allow_hyphen_values(true)
|
||||
.overrides_with_all(&[options::BYTES, options::LINES])
|
||||
.help("Number of bytes to print"),
|
||||
)
|
||||
.arg(
|
||||
|
@ -98,6 +97,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.long(options::LINES)
|
||||
.takes_value(true)
|
||||
.allow_hyphen_values(true)
|
||||
.overrides_with_all(&[options::BYTES, options::LINES])
|
||||
.help("Number of lines to print"),
|
||||
)
|
||||
.arg(
|
||||
|
@ -110,13 +110,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
Arg::with_name(options::verbosity::QUIET)
|
||||
.short("q")
|
||||
.long(options::verbosity::QUIET)
|
||||
.visible_alias("silent")
|
||||
.overrides_with_all(&[options::verbosity::QUIET, options::verbosity::VERBOSE])
|
||||
.help("never output headers giving file names"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::verbosity::SILENT)
|
||||
.long(options::verbosity::SILENT)
|
||||
.help("synonym of --quiet"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::SLEEP_INT)
|
||||
.short("s")
|
||||
|
@ -128,6 +125,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
Arg::with_name(options::verbosity::VERBOSE)
|
||||
.short("v")
|
||||
.long(options::verbosity::VERBOSE)
|
||||
.overrides_with_all(&[options::verbosity::QUIET, options::verbosity::VERBOSE])
|
||||
.help("always output headers giving file names"),
|
||||
)
|
||||
.arg(
|
||||
|
@ -137,7 +135,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.help("Line delimiter is NUL, not newline"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(ARG_FILES)
|
||||
Arg::with_name(options::ARG_FILES)
|
||||
.multiple(true)
|
||||
.takes_value(true)
|
||||
.min_values(1),
|
||||
|
@ -171,38 +169,21 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
}
|
||||
}
|
||||
|
||||
match matches.value_of(options::LINES) {
|
||||
Some(n) => {
|
||||
let mut slice: &str = n;
|
||||
if slice.chars().next().unwrap_or('_') == '+' {
|
||||
settings.beginning = true;
|
||||
slice = &slice[1..];
|
||||
}
|
||||
match parse_size(slice) {
|
||||
Ok(m) => settings.mode = FilterMode::Lines(m, b'\n'),
|
||||
Err(e) => {
|
||||
show_error!("{}", e.to_string());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
let mode_and_beginning = if let Some(arg) = matches.value_of(options::BYTES) {
|
||||
match parse_num(arg) {
|
||||
Ok((n, beginning)) => (FilterMode::Bytes(n), beginning),
|
||||
Err(e) => crash!(1, "invalid number of bytes: {}", e.to_string()),
|
||||
}
|
||||
None => {
|
||||
if let Some(n) = matches.value_of(options::BYTES) {
|
||||
let mut slice: &str = n;
|
||||
if slice.chars().next().unwrap_or('_') == '+' {
|
||||
settings.beginning = true;
|
||||
slice = &slice[1..];
|
||||
}
|
||||
match parse_size(slice) {
|
||||
Ok(m) => settings.mode = FilterMode::Bytes(m),
|
||||
Err(e) => {
|
||||
show_error!("{}", e.to_string());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some(arg) = matches.value_of(options::LINES) {
|
||||
match parse_num(arg) {
|
||||
Ok((n, beginning)) => (FilterMode::Lines(n, b'\n'), beginning),
|
||||
Err(e) => crash!(1, "invalid number of lines: {}", e.to_string()),
|
||||
}
|
||||
} else {
|
||||
(FilterMode::Lines(10, b'\n'), false)
|
||||
};
|
||||
settings.mode = mode_and_beginning.0;
|
||||
settings.beginning = mode_and_beginning.1;
|
||||
|
||||
if matches.is_present(options::ZERO_TERM) {
|
||||
if let FilterMode::Lines(count, _) = settings.mode {
|
||||
|
@ -211,11 +192,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
}
|
||||
|
||||
let verbose = matches.is_present(options::verbosity::VERBOSE);
|
||||
let quiet = matches.is_present(options::verbosity::QUIET)
|
||||
|| matches.is_present(options::verbosity::SILENT);
|
||||
let quiet = matches.is_present(options::verbosity::QUIET);
|
||||
|
||||
let files: Vec<String> = matches
|
||||
.values_of(ARG_FILES)
|
||||
.values_of(options::ARG_FILES)
|
||||
.map(|v| v.map(ToString::to_string).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
|
@ -264,98 +244,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
0
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ParseSizeErr {
|
||||
ParseFailure(String),
|
||||
SizeTooBig(String),
|
||||
}
|
||||
|
||||
impl Error for ParseSizeErr {
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
ParseSizeErr::ParseFailure(ref s) => &*s,
|
||||
ParseSizeErr::SizeTooBig(ref s) => &*s,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ParseSizeErr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
let s = match self {
|
||||
ParseSizeErr::ParseFailure(s) => s,
|
||||
ParseSizeErr::SizeTooBig(s) => s,
|
||||
};
|
||||
write!(f, "{}", s)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseSizeErr {
|
||||
fn parse_failure(s: &str) -> ParseSizeErr {
|
||||
ParseSizeErr::ParseFailure(format!("invalid size: '{}'", s))
|
||||
}
|
||||
|
||||
fn size_too_big(s: &str) -> ParseSizeErr {
|
||||
ParseSizeErr::SizeTooBig(format!(
|
||||
"invalid size: '{}': Value too large to be stored in data type",
|
||||
s
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub type ParseSizeResult = Result<u64, ParseSizeErr>;
|
||||
|
||||
pub fn parse_size(mut size_slice: &str) -> Result<u64, ParseSizeErr> {
|
||||
let mut base = if size_slice.chars().last().unwrap_or('_') == 'B' {
|
||||
size_slice = &size_slice[..size_slice.len() - 1];
|
||||
1000u64
|
||||
} else {
|
||||
1024u64
|
||||
};
|
||||
|
||||
let exponent = if !size_slice.is_empty() {
|
||||
let mut has_suffix = true;
|
||||
let exp = match size_slice.chars().last().unwrap_or('_') {
|
||||
'K' | 'k' => 1u64,
|
||||
'M' => 2u64,
|
||||
'G' => 3u64,
|
||||
'T' => 4u64,
|
||||
'P' => 5u64,
|
||||
'E' => 6u64,
|
||||
'Z' | 'Y' => {
|
||||
return Err(ParseSizeErr::size_too_big(size_slice));
|
||||
}
|
||||
'b' => {
|
||||
base = 512u64;
|
||||
1u64
|
||||
}
|
||||
_ => {
|
||||
has_suffix = false;
|
||||
0u64
|
||||
}
|
||||
};
|
||||
if has_suffix {
|
||||
size_slice = &size_slice[..size_slice.len() - 1];
|
||||
}
|
||||
exp
|
||||
} else {
|
||||
0u64
|
||||
};
|
||||
|
||||
let mut multiplier = 1u64;
|
||||
for _ in 0u64..exponent {
|
||||
multiplier *= base;
|
||||
}
|
||||
if base == 1000u64 && exponent == 0u64 {
|
||||
// sole B is not a valid suffix
|
||||
Err(ParseSizeErr::parse_failure(size_slice))
|
||||
} else {
|
||||
let value: Option<i64> = size_slice.parse().ok();
|
||||
value
|
||||
.map(|v| Ok((multiplier as i64 * v.abs()) as u64))
|
||||
.unwrap_or_else(|| Err(ParseSizeErr::parse_failure(size_slice)))
|
||||
}
|
||||
}
|
||||
|
||||
fn follow<T: Read>(readers: &mut [BufReader<T>], filenames: &[String], settings: &Settings) {
|
||||
assert!(settings.follow);
|
||||
let mut last = readers.len() - 1;
|
||||
|
@ -469,7 +357,7 @@ fn bounded_tail(file: &mut File, settings: &Settings) {
|
|||
/// If any element of `iter` is an [`Err`], then this function panics.
|
||||
fn unbounded_tail_collect<T, E>(
|
||||
iter: impl Iterator<Item = Result<T, E>>,
|
||||
count: u64,
|
||||
count: usize,
|
||||
beginning: bool,
|
||||
) -> VecDeque<T>
|
||||
where
|
||||
|
@ -514,3 +402,22 @@ fn print_byte<T: Write>(stdout: &mut T, ch: u8) {
|
|||
crash!(1, "{}", err);
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_num(src: &str) -> Result<(usize, bool), ParseSizeError> {
|
||||
let mut size_string = src.trim();
|
||||
let mut starting_with = false;
|
||||
|
||||
if let Some(c) = size_string.chars().next() {
|
||||
if c == '+' || c == '-' {
|
||||
// tail: '-' is not documented (8.32 man pages)
|
||||
size_string = &size_string[1..];
|
||||
if c == '+' {
|
||||
starting_with = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(ParseSizeError::ParseFailure(src.to_string()));
|
||||
}
|
||||
|
||||
parse_size(size_string).map(|n| (n, starting_with))
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ path = "src/timeout.rs"
|
|||
clap = "2.33"
|
||||
libc = "0.2.42"
|
||||
nix = "0.20.0"
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["parse_time", "process", "signals"] }
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["process", "signals"] }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
|
||||
|
|
|
@ -22,14 +22,15 @@ use std::ops::RangeInclusive;
|
|||
/// character; octal escape sequences consume 1 to 3 octal digits.
|
||||
#[inline]
|
||||
fn parse_sequence(s: &str) -> (char, usize) {
|
||||
let c = s.chars().next().expect("invalid escape: empty string");
|
||||
let mut s = s.chars();
|
||||
let c = s.next().expect("invalid escape: empty string");
|
||||
|
||||
if ('0'..='7').contains(&c) {
|
||||
let mut v = c.to_digit(8).unwrap();
|
||||
let mut consumed = 1;
|
||||
let bits_per_digit = 3;
|
||||
|
||||
for c in s.chars().skip(1).take(2) {
|
||||
for c in s.take(2) {
|
||||
match c.to_digit(8) {
|
||||
Some(c) => {
|
||||
v = (v << bits_per_digit) | c;
|
||||
|
|
|
@ -11,19 +11,21 @@
|
|||
extern crate uucore;
|
||||
|
||||
use clap::{crate_version, App, Arg};
|
||||
use std::convert::TryFrom;
|
||||
use std::fs::{metadata, OpenOptions};
|
||||
use std::io::ErrorKind;
|
||||
use std::path::Path;
|
||||
use uucore::parse_size::{parse_size, ParseSizeError};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
enum TruncateMode {
|
||||
Absolute(u64),
|
||||
Extend(u64),
|
||||
Reduce(u64),
|
||||
AtMost(u64),
|
||||
AtLeast(u64),
|
||||
RoundDown(u64),
|
||||
RoundUp(u64),
|
||||
Absolute(usize),
|
||||
Extend(usize),
|
||||
Reduce(usize),
|
||||
AtMost(usize),
|
||||
AtLeast(usize),
|
||||
RoundDown(usize),
|
||||
RoundUp(usize),
|
||||
}
|
||||
|
||||
impl TruncateMode {
|
||||
|
@ -38,7 +40,7 @@ impl TruncateMode {
|
|||
/// let fsize = 10;
|
||||
/// assert_eq!(mode.to_size(fsize), 15);
|
||||
/// ```
|
||||
fn to_size(&self, fsize: u64) -> u64 {
|
||||
fn to_size(&self, fsize: usize) -> usize {
|
||||
match self {
|
||||
TruncateMode::Absolute(size) => *size,
|
||||
TruncateMode::Extend(size) => fsize + size,
|
||||
|
@ -58,10 +60,9 @@ pub mod options {
|
|||
pub static NO_CREATE: &str = "no-create";
|
||||
pub static REFERENCE: &str = "reference";
|
||||
pub static SIZE: &str = "size";
|
||||
pub static ARG_FILES: &str = "files";
|
||||
}
|
||||
|
||||
static ARG_FILES: &str = "files";
|
||||
|
||||
fn get_usage() -> String {
|
||||
format!("{0} [OPTION]... [FILE]...", executable!())
|
||||
}
|
||||
|
@ -113,21 +114,28 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
Arg::with_name(options::REFERENCE)
|
||||
.short("r")
|
||||
.long(options::REFERENCE)
|
||||
.required_unless(options::SIZE)
|
||||
.help("base the size of each file on the size of RFILE")
|
||||
.value_name("RFILE")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::SIZE)
|
||||
.short("s")
|
||||
.long("size")
|
||||
.long(options::SIZE)
|
||||
.required_unless(options::REFERENCE)
|
||||
.help("set or adjust the size of each file according to SIZE, which is in bytes unless --io-blocks is specified")
|
||||
.value_name("SIZE")
|
||||
)
|
||||
.arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true).min_values(1))
|
||||
.arg(Arg::with_name(options::ARG_FILES)
|
||||
.value_name("FILE")
|
||||
.multiple(true)
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.min_values(1))
|
||||
.get_matches_from(args);
|
||||
|
||||
let files: Vec<String> = matches
|
||||
.values_of(ARG_FILES)
|
||||
.values_of(options::ARG_FILES)
|
||||
.map(|v| v.map(ToString::to_string).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
|
@ -149,8 +157,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
crash!(
|
||||
1,
|
||||
"cannot stat '{}': No such file or directory",
|
||||
reference.unwrap()
|
||||
);
|
||||
reference.unwrap_or_else(|| "".to_string())
|
||||
); // TODO: fix '--no-create' see test_reference and test_truncate_bytes_size
|
||||
}
|
||||
_ => crash!(1, "{}", e.to_string()),
|
||||
}
|
||||
|
@ -172,10 +180,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
///
|
||||
/// If the file could not be opened, or there was a problem setting the
|
||||
/// size of the file.
|
||||
fn file_truncate(filename: &str, create: bool, size: u64) -> std::io::Result<()> {
|
||||
fn file_truncate(filename: &str, create: bool, size: usize) -> std::io::Result<()> {
|
||||
let path = Path::new(filename);
|
||||
let f = OpenOptions::new().write(true).create(create).open(path)?;
|
||||
f.set_len(size)
|
||||
f.set_len(u64::try_from(size).unwrap())
|
||||
}
|
||||
|
||||
/// Truncate files to a size relative to a given file.
|
||||
|
@ -206,9 +214,9 @@ fn truncate_reference_and_size(
|
|||
}
|
||||
_ => m,
|
||||
},
|
||||
Err(_) => crash!(1, "Invalid number: ‘{}’", size_string),
|
||||
Err(e) => crash!(1, "Invalid number: {}", e.to_string()),
|
||||
};
|
||||
let fsize = metadata(rfilename)?.len();
|
||||
let fsize = usize::try_from(metadata(rfilename)?.len()).unwrap();
|
||||
let tsize = mode.to_size(fsize);
|
||||
for filename in &filenames {
|
||||
file_truncate(filename, create, tsize)?;
|
||||
|
@ -232,7 +240,7 @@ fn truncate_reference_file_only(
|
|||
filenames: Vec<String>,
|
||||
create: bool,
|
||||
) -> std::io::Result<()> {
|
||||
let tsize = metadata(rfilename)?.len();
|
||||
let tsize = usize::try_from(metadata(rfilename)?.len()).unwrap();
|
||||
for filename in &filenames {
|
||||
file_truncate(filename, create, tsize)?;
|
||||
}
|
||||
|
@ -261,10 +269,10 @@ fn truncate_size_only(
|
|||
) -> std::io::Result<()> {
|
||||
let mode = match parse_mode_and_size(size_string) {
|
||||
Ok(m) => m,
|
||||
Err(_) => crash!(1, "Invalid number: ‘{}’", size_string),
|
||||
Err(e) => crash!(1, "Invalid number: {}", e.to_string()),
|
||||
};
|
||||
for filename in &filenames {
|
||||
let fsize = metadata(filename).map(|m| m.len()).unwrap_or(0);
|
||||
let fsize = usize::try_from(metadata(filename)?.len()).unwrap();
|
||||
let tsize = mode.to_size(fsize);
|
||||
file_truncate(filename, create, tsize)?;
|
||||
}
|
||||
|
@ -290,7 +298,7 @@ fn truncate(
|
|||
}
|
||||
(Some(rfilename), None) => truncate_reference_file_only(&rfilename, filenames, create),
|
||||
(None, Some(size_string)) => truncate_size_only(&size_string, filenames, create),
|
||||
(None, None) => crash!(1, "you must specify either --reference or --size"),
|
||||
(None, None) => crash!(1, "you must specify either --reference or --size"), // this case cannot happen anymore because it's handled by clap
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -317,117 +325,35 @@ fn is_modifier(c: char) -> bool {
|
|||
/// ```rust,ignore
|
||||
/// assert_eq!(parse_mode_and_size("+123"), (TruncateMode::Extend, 123));
|
||||
/// ```
|
||||
fn parse_mode_and_size(size_string: &str) -> Result<TruncateMode, ()> {
|
||||
fn parse_mode_and_size(size_string: &str) -> Result<TruncateMode, ParseSizeError> {
|
||||
// Trim any whitespace.
|
||||
let size_string = size_string.trim();
|
||||
let mut size_string = size_string.trim();
|
||||
|
||||
// Get the modifier character from the size string, if any. For
|
||||
// example, if the argument is "+123", then the modifier is '+'.
|
||||
let c = size_string.chars().next().unwrap();
|
||||
let size_string = if is_modifier(c) {
|
||||
&size_string[1..]
|
||||
if let Some(c) = size_string.chars().next() {
|
||||
if is_modifier(c) {
|
||||
size_string = &size_string[1..];
|
||||
}
|
||||
parse_size(size_string).map(match c {
|
||||
'+' => TruncateMode::Extend,
|
||||
'-' => TruncateMode::Reduce,
|
||||
'<' => TruncateMode::AtMost,
|
||||
'>' => TruncateMode::AtLeast,
|
||||
'/' => TruncateMode::RoundDown,
|
||||
'%' => TruncateMode::RoundUp,
|
||||
_ => TruncateMode::Absolute,
|
||||
})
|
||||
} else {
|
||||
size_string
|
||||
};
|
||||
parse_size(size_string).map(match c {
|
||||
'+' => TruncateMode::Extend,
|
||||
'-' => TruncateMode::Reduce,
|
||||
'<' => TruncateMode::AtMost,
|
||||
'>' => TruncateMode::AtLeast,
|
||||
'/' => TruncateMode::RoundDown,
|
||||
'%' => TruncateMode::RoundUp,
|
||||
_ => TruncateMode::Absolute,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse a size string into a number of bytes.
|
||||
///
|
||||
/// A size string comprises an integer and an optional unit. The unit
|
||||
/// may be K, M, G, T, P, E, Z, or Y (powers of 1024) or KB, MB,
|
||||
/// etc. (powers of 1000).
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function returns an error if the string does not begin with a
|
||||
/// numeral, or if the unit is not one of the supported units described
|
||||
/// in the preceding section.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// assert_eq!(parse_size("123").unwrap(), 123);
|
||||
/// assert_eq!(parse_size("123K").unwrap(), 123 * 1024);
|
||||
/// assert_eq!(parse_size("123KB").unwrap(), 123 * 1000);
|
||||
/// ```
|
||||
fn parse_size(size: &str) -> Result<u64, ()> {
|
||||
// Get the numeric part of the size argument. For example, if the
|
||||
// argument is "123K", then the numeric part is "123".
|
||||
let numeric_string: String = size.chars().take_while(|c| c.is_digit(10)).collect();
|
||||
let number: u64 = match numeric_string.parse() {
|
||||
Ok(n) => n,
|
||||
Err(_) => return Err(()),
|
||||
};
|
||||
|
||||
// Get the alphabetic units part of the size argument and compute
|
||||
// the factor it represents. For example, if the argument is "123K",
|
||||
// then the unit part is "K" and the factor is 1024. This may be the
|
||||
// empty string, in which case, the factor is 1.
|
||||
let n = numeric_string.len();
|
||||
let (base, exponent): (u64, u32) = match &size[n..] {
|
||||
"" => (1, 0),
|
||||
"K" | "k" => (1024, 1),
|
||||
"M" | "m" => (1024, 2),
|
||||
"G" | "g" => (1024, 3),
|
||||
"T" | "t" => (1024, 4),
|
||||
"P" | "p" => (1024, 5),
|
||||
"E" | "e" => (1024, 6),
|
||||
"Z" | "z" => (1024, 7),
|
||||
"Y" | "y" => (1024, 8),
|
||||
"KB" | "kB" => (1000, 1),
|
||||
"MB" | "mB" => (1000, 2),
|
||||
"GB" | "gB" => (1000, 3),
|
||||
"TB" | "tB" => (1000, 4),
|
||||
"PB" | "pB" => (1000, 5),
|
||||
"EB" | "eB" => (1000, 6),
|
||||
"ZB" | "zB" => (1000, 7),
|
||||
"YB" | "yB" => (1000, 8),
|
||||
_ => return Err(()),
|
||||
};
|
||||
let factor = base.pow(exponent);
|
||||
Ok(number * factor)
|
||||
Err(ParseSizeError::ParseFailure(size_string.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::parse_mode_and_size;
|
||||
use crate::parse_size;
|
||||
use crate::TruncateMode;
|
||||
|
||||
#[test]
|
||||
fn test_parse_size_zero() {
|
||||
assert_eq!(parse_size("0").unwrap(), 0);
|
||||
assert_eq!(parse_size("0K").unwrap(), 0);
|
||||
assert_eq!(parse_size("0KB").unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_size_without_factor() {
|
||||
assert_eq!(parse_size("123").unwrap(), 123);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_size_kilobytes() {
|
||||
assert_eq!(parse_size("123K").unwrap(), 123 * 1024);
|
||||
assert_eq!(parse_size("123KB").unwrap(), 123 * 1000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_size_megabytes() {
|
||||
assert_eq!(parse_size("123").unwrap(), 123);
|
||||
assert_eq!(parse_size("123M").unwrap(), 123 * 1024 * 1024);
|
||||
assert_eq!(parse_size("123MB").unwrap(), 123 * 1000 * 1000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_mode_and_size() {
|
||||
assert_eq!(parse_mode_and_size("10"), Ok(TruncateMode::Absolute(10)));
|
||||
|
|
|
@ -179,124 +179,58 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
// Ignored for 'who am i'.
|
||||
let short_list = matches.is_present(options::COUNT);
|
||||
|
||||
// If true, display only name, line, and time fields.
|
||||
let mut short_output = false;
|
||||
|
||||
// If true, display the hours:minutes since each user has touched
|
||||
// the keyboard, or "." if within the last minute, or "old" if
|
||||
// not within the last day.
|
||||
let mut include_idle = false;
|
||||
let all = matches.is_present(options::ALL);
|
||||
|
||||
// If true, display a line at the top describing each field.
|
||||
let include_heading = matches.is_present(options::HEADING);
|
||||
|
||||
// If true, display a '+' for each user if mesg y, a '-' if mesg n,
|
||||
// or a '?' if their tty cannot be statted.
|
||||
let include_mesg = matches.is_present(options::ALL)
|
||||
|| matches.is_present(options::MESG)
|
||||
|| matches.is_present("w");
|
||||
|
||||
// If true, display process termination & exit status.
|
||||
let mut include_exit = false;
|
||||
let include_mesg = all || matches.is_present(options::MESG) || matches.is_present("w");
|
||||
|
||||
// If true, display the last boot time.
|
||||
let mut need_boottime = false;
|
||||
let need_boottime = all || matches.is_present(options::BOOT);
|
||||
|
||||
// If true, display dead processes.
|
||||
let mut need_deadprocs = false;
|
||||
let need_deadprocs = all || matches.is_present(options::DEAD);
|
||||
|
||||
// If true, display processes waiting for user login.
|
||||
let mut need_login = false;
|
||||
let need_login = all || matches.is_present(options::LOGIN);
|
||||
|
||||
// If true, display processes started by init.
|
||||
let mut need_initspawn = false;
|
||||
let need_initspawn = all || matches.is_present(options::PROCESS);
|
||||
|
||||
// If true, display the last clock change.
|
||||
let mut need_clockchange = false;
|
||||
let need_clockchange = all || matches.is_present(options::TIME);
|
||||
|
||||
// If true, display the current runlevel.
|
||||
let mut need_runlevel = false;
|
||||
let need_runlevel = all || matches.is_present(options::RUNLEVEL);
|
||||
|
||||
let use_defaults = !(all
|
||||
|| need_boottime
|
||||
|| need_deadprocs
|
||||
|| need_login
|
||||
|| need_initspawn
|
||||
|| need_runlevel
|
||||
|| need_clockchange
|
||||
|| matches.is_present(options::USERS));
|
||||
|
||||
// If true, display user processes.
|
||||
let mut need_users = false;
|
||||
let need_users = all || matches.is_present(options::USERS) || use_defaults;
|
||||
|
||||
// If true, display the hours:minutes since each user has touched
|
||||
// the keyboard, or "." if within the last minute, or "old" if
|
||||
// not within the last day.
|
||||
let include_idle = need_deadprocs || need_login || need_runlevel || need_users;
|
||||
|
||||
// If true, display process termination & exit status.
|
||||
let include_exit = need_deadprocs;
|
||||
|
||||
// If true, display only name, line, and time fields.
|
||||
let short_output = !include_exit && use_defaults;
|
||||
|
||||
// If true, display info only for the controlling tty.
|
||||
let mut my_line_only = false;
|
||||
|
||||
let mut assumptions = true;
|
||||
|
||||
#[allow(clippy::useless_let_if_seq)]
|
||||
{
|
||||
if matches.is_present(options::ALL) {
|
||||
need_boottime = true;
|
||||
need_deadprocs = true;
|
||||
need_login = true;
|
||||
need_initspawn = true;
|
||||
need_runlevel = true;
|
||||
need_clockchange = true;
|
||||
need_users = true;
|
||||
include_idle = true;
|
||||
include_exit = true;
|
||||
assumptions = false;
|
||||
}
|
||||
|
||||
if matches.is_present(options::BOOT) {
|
||||
need_boottime = true;
|
||||
assumptions = false;
|
||||
}
|
||||
|
||||
if matches.is_present(options::DEAD) {
|
||||
need_deadprocs = true;
|
||||
include_idle = true;
|
||||
include_exit = true;
|
||||
assumptions = false;
|
||||
}
|
||||
|
||||
if matches.is_present(options::LOGIN) {
|
||||
need_login = true;
|
||||
include_idle = true;
|
||||
assumptions = false;
|
||||
}
|
||||
|
||||
if matches.is_present(options::ONLY_HOSTNAME_USER) || files.len() == 2 {
|
||||
my_line_only = true;
|
||||
}
|
||||
|
||||
if matches.is_present(options::PROCESS) {
|
||||
need_initspawn = true;
|
||||
assumptions = false;
|
||||
}
|
||||
|
||||
if matches.is_present(options::RUNLEVEL) {
|
||||
need_runlevel = true;
|
||||
include_idle = true;
|
||||
assumptions = false;
|
||||
}
|
||||
|
||||
if matches.is_present(options::SHORT) {
|
||||
short_output = true;
|
||||
}
|
||||
|
||||
if matches.is_present(options::TIME) {
|
||||
need_clockchange = true;
|
||||
assumptions = false;
|
||||
}
|
||||
|
||||
if matches.is_present(options::USERS) {
|
||||
need_users = true;
|
||||
include_idle = true;
|
||||
assumptions = false;
|
||||
}
|
||||
|
||||
if assumptions {
|
||||
need_users = true;
|
||||
short_output = true;
|
||||
}
|
||||
|
||||
if include_exit {
|
||||
short_output = false;
|
||||
}
|
||||
}
|
||||
let my_line_only = matches.is_present(options::ONLY_HOSTNAME_USER) || files.len() == 2;
|
||||
|
||||
let mut who = Who {
|
||||
do_lookup,
|
||||
|
|
|
@ -20,7 +20,6 @@ uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["
|
|||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
advapi32-sys = "0.2.0"
|
||||
winapi = { version = "0.3", features = ["lmcons"] }
|
||||
|
||||
[[bin]]
|
||||
|
|
|
@ -11,7 +11,7 @@ extern crate winapi;
|
|||
|
||||
use self::winapi::shared::lmcons;
|
||||
use self::winapi::shared::minwindef;
|
||||
use self::winapi::um::winnt;
|
||||
use self::winapi::um::{winbase, winnt};
|
||||
use std::io::{Error, Result};
|
||||
use std::mem;
|
||||
use uucore::wide::FromWide;
|
||||
|
@ -20,7 +20,7 @@ pub unsafe fn get_username() -> Result<String> {
|
|||
#[allow(deprecated)]
|
||||
let mut buffer: [winnt::WCHAR; lmcons::UNLEN as usize + 1] = mem::uninitialized();
|
||||
let mut len = buffer.len() as minwindef::DWORD;
|
||||
if advapi32::GetUserNameW(buffer.as_mut_ptr(), &mut len) == 0 {
|
||||
if winbase::GetUserNameW(buffer.as_mut_ptr(), &mut len) == 0 {
|
||||
return Err(Error::last_os_error());
|
||||
}
|
||||
let username = String::from_wide(&buffer[..len as usize - 1]);
|
||||
|
|
|
@ -44,7 +44,6 @@ entries = ["libc"]
|
|||
fs = ["libc"]
|
||||
fsext = ["libc", "time"]
|
||||
mode = ["libc"]
|
||||
parse_time = []
|
||||
perms = ["libc"]
|
||||
process = ["libc"]
|
||||
ringbuffer = []
|
||||
|
|
|
@ -6,8 +6,6 @@ pub mod encoding;
|
|||
pub mod fs;
|
||||
#[cfg(feature = "fsext")]
|
||||
pub mod fsext;
|
||||
#[cfg(feature = "parse_time")]
|
||||
pub mod parse_time;
|
||||
#[cfg(feature = "ringbuffer")]
|
||||
pub mod ringbuffer;
|
||||
#[cfg(feature = "zero-copy")]
|
||||
|
|
|
@ -101,6 +101,7 @@ pub fn get_groups_gnu(arg_id: Option<u32>) -> IOResult<Vec<gid_t>> {
|
|||
Ok(sort_groups(groups, egid))
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "process"))]
|
||||
fn sort_groups(mut groups: Vec<gid_t>, egid: gid_t) -> Vec<gid_t> {
|
||||
if let Some(index) = groups.iter().position(|&x| x == egid) {
|
||||
groups[..=index].rotate_right(1);
|
||||
|
|
|
@ -113,22 +113,14 @@ fn resolve<P: AsRef<Path>>(original: P) -> IOResult<PathBuf> {
|
|||
));
|
||||
}
|
||||
|
||||
match fs::symlink_metadata(&result) {
|
||||
Err(e) => return Err(e),
|
||||
Ok(ref m) if !m.file_type().is_symlink() => break,
|
||||
Ok(..) => {
|
||||
followed += 1;
|
||||
match fs::read_link(&result) {
|
||||
Ok(path) => {
|
||||
result.pop();
|
||||
result.push(path);
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if !fs::symlink_metadata(&result)?.file_type().is_symlink() {
|
||||
break;
|
||||
}
|
||||
|
||||
followed += 1;
|
||||
let path = fs::read_link(&result)?;
|
||||
result.pop();
|
||||
result.push(path);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
@ -193,10 +185,8 @@ pub fn canonicalize<P: AsRef<Path>>(original: P, can_mode: CanonicalizeMode) ->
|
|||
}
|
||||
|
||||
match resolve(&result) {
|
||||
Err(e) => match can_mode {
|
||||
CanonicalizeMode::Missing => continue,
|
||||
_ => return Err(e),
|
||||
},
|
||||
Err(_) if can_mode == CanonicalizeMode::Missing => continue,
|
||||
Err(e) => return Err(e),
|
||||
Ok(path) => {
|
||||
result.pop();
|
||||
result.push(path);
|
||||
|
@ -211,15 +201,14 @@ pub fn canonicalize<P: AsRef<Path>>(original: P, can_mode: CanonicalizeMode) ->
|
|||
}
|
||||
|
||||
match resolve(&result) {
|
||||
Err(e) => {
|
||||
if can_mode == CanonicalizeMode::Existing {
|
||||
return Err(e);
|
||||
}
|
||||
Err(e) if can_mode == CanonicalizeMode::Existing => {
|
||||
return Err(e);
|
||||
}
|
||||
Ok(path) => {
|
||||
result.pop();
|
||||
result.push(path);
|
||||
}
|
||||
Err(_) => (),
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
|
|
|
@ -89,19 +89,19 @@ fn parse_levels(mode: &str) -> (u32, usize) {
|
|||
}
|
||||
|
||||
fn parse_op(mode: &str, default: Option<char>) -> Result<(char, usize), String> {
|
||||
match mode.chars().next() {
|
||||
Some(ch) => match ch {
|
||||
'+' | '-' | '=' => Ok((ch, 1)),
|
||||
_ => match default {
|
||||
Some(ch) => Ok((ch, 0)),
|
||||
None => Err(format!(
|
||||
"invalid operator (expected +, -, or =, but found {})",
|
||||
ch
|
||||
)),
|
||||
},
|
||||
},
|
||||
None => Err("unexpected end of mode".to_owned()),
|
||||
}
|
||||
let ch = mode
|
||||
.chars()
|
||||
.next()
|
||||
.ok_or_else(|| "unexpected end of mode".to_owned())?;
|
||||
Ok(match ch {
|
||||
'+' | '-' | '=' => (ch, 1),
|
||||
_ => {
|
||||
let ch = default.ok_or_else(|| {
|
||||
format!("invalid operator (expected +, -, or =, but found {})", ch)
|
||||
})?;
|
||||
(ch, 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_change(mode: &str, fperm: u32, considering_dir: bool) -> (u32, usize) {
|
||||
|
|
|
@ -92,7 +92,7 @@ pub fn wrap_chgrp<P: AsRef<Path>>(
|
|||
out = format!(
|
||||
"group of '{}' retained as {}",
|
||||
path.display(),
|
||||
entries::gid2grp(dest_gid).unwrap()
|
||||
entries::gid2grp(dest_gid).unwrap_or_default()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,10 +19,10 @@ pub extern crate winapi;
|
|||
|
||||
//## internal modules
|
||||
|
||||
mod macros; // crate macros (macro_rules-type; exported to `crate::...`)
|
||||
|
||||
mod features; // feature-gated code modules
|
||||
mod macros; // crate macros (macro_rules-type; exported to `crate::...`)
|
||||
mod mods; // core cross-platform modules
|
||||
mod parser; // string parsing modules
|
||||
|
||||
// * cross-platform modules
|
||||
pub use crate::mods::backup_control;
|
||||
|
@ -31,6 +31,10 @@ pub use crate::mods::os;
|
|||
pub use crate::mods::panic;
|
||||
pub use crate::mods::ranges;
|
||||
|
||||
// * string parsing modules
|
||||
pub use crate::parser::parse_size;
|
||||
pub use crate::parser::parse_time;
|
||||
|
||||
// * feature-gated modules
|
||||
#[cfg(feature = "encoding")]
|
||||
pub use crate::features::encoding;
|
||||
|
@ -38,8 +42,6 @@ pub use crate::features::encoding;
|
|||
pub use crate::features::fs;
|
||||
#[cfg(feature = "fsext")]
|
||||
pub use crate::features::fsext;
|
||||
#[cfg(feature = "parse_time")]
|
||||
pub use crate::features::parse_time;
|
||||
#[cfg(feature = "ringbuffer")]
|
||||
pub use crate::features::ringbuffer;
|
||||
#[cfg(feature = "zero-copy")]
|
||||
|
|
|
@ -85,10 +85,9 @@ impl Range {
|
|||
let mut ranges: Vec<Range> = vec![];
|
||||
|
||||
for item in list.split(',') {
|
||||
match FromStr::from_str(item) {
|
||||
Ok(range_item) => ranges.push(range_item),
|
||||
Err(e) => return Err(format!("range '{}' was invalid: {}", item, e)),
|
||||
}
|
||||
let range_item = FromStr::from_str(item)
|
||||
.map_err(|e| format!("range '{}' was invalid: {}", item, e))?;
|
||||
ranges.push(range_item);
|
||||
}
|
||||
|
||||
ranges.sort();
|
||||
|
|
2
src/uucore/src/lib/parser.rs
Normal file
2
src/uucore/src/lib/parser.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub mod parse_size;
|
||||
pub mod parse_time;
|
324
src/uucore/src/lib/parser/parse_size.rs
Normal file
324
src/uucore/src/lib/parser/parse_size.rs
Normal file
|
@ -0,0 +1,324 @@
|
|||
// * This file is part of the uutils coreutils package.
|
||||
// *
|
||||
// * For the full copyright and license information, please view the LICENSE
|
||||
// * file that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore (ToDO) hdsf ghead gtail
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
/// Parse a size string into a number of bytes.
|
||||
///
|
||||
/// A size string comprises an integer and an optional unit. The unit
|
||||
/// may be K, M, G, T, P, E, Z or Y (powers of 1024), or KB, MB,
|
||||
/// etc. (powers of 1000), or b which is 512.
|
||||
/// Binary prefixes can be used, too: KiB=K, MiB=M, and so on.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Will return `ParseSizeError` if it’s not possible to parse this
|
||||
/// string into a number, e.g. if the string does not begin with a
|
||||
/// numeral, or if the unit is not one of the supported units described
|
||||
/// in the preceding section.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use uucore::parse_size::parse_size;
|
||||
/// assert_eq!(Ok(123), parse_size("123"));
|
||||
/// assert_eq!(Ok(9 * 1000), parse_size("9kB")); // kB is 1000
|
||||
/// assert_eq!(Ok(2 * 1024), parse_size("2K")); // K is 1024
|
||||
/// ```
|
||||
pub fn parse_size(size: &str) -> Result<usize, ParseSizeError> {
|
||||
if size.is_empty() {
|
||||
return Err(ParseSizeError::parse_failure(size));
|
||||
}
|
||||
// Get the numeric part of the size argument. For example, if the
|
||||
// argument is "123K", then the numeric part is "123".
|
||||
let numeric_string: String = size.chars().take_while(|c| c.is_digit(10)).collect();
|
||||
let number: usize = if !numeric_string.is_empty() {
|
||||
match numeric_string.parse() {
|
||||
Ok(n) => n,
|
||||
Err(_) => return Err(ParseSizeError::parse_failure(size)),
|
||||
}
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
// Get the alphabetic units part of the size argument and compute
|
||||
// the factor it represents. For example, if the argument is "123K",
|
||||
// then the unit part is "K" and the factor is 1024. This may be the
|
||||
// empty string, in which case, the factor is 1.
|
||||
let unit = &size[numeric_string.len()..];
|
||||
let (base, exponent): (u128, u32) = match unit {
|
||||
"" => (1, 0),
|
||||
"b" => (512, 1), // (`od`, `head` and `tail` use "b")
|
||||
"KiB" | "kiB" | "K" | "k" => (1024, 1),
|
||||
"MiB" | "miB" | "M" | "m" => (1024, 2),
|
||||
"GiB" | "giB" | "G" | "g" => (1024, 3),
|
||||
"TiB" | "tiB" | "T" | "t" => (1024, 4),
|
||||
"PiB" | "piB" | "P" | "p" => (1024, 5),
|
||||
"EiB" | "eiB" | "E" | "e" => (1024, 6),
|
||||
"ZiB" | "ziB" | "Z" | "z" => (1024, 7),
|
||||
"YiB" | "yiB" | "Y" | "y" => (1024, 8),
|
||||
"KB" | "kB" => (1000, 1),
|
||||
"MB" | "mB" => (1000, 2),
|
||||
"GB" | "gB" => (1000, 3),
|
||||
"TB" | "tB" => (1000, 4),
|
||||
"PB" | "pB" => (1000, 5),
|
||||
"EB" | "eB" => (1000, 6),
|
||||
"ZB" | "zB" => (1000, 7),
|
||||
"YB" | "yB" => (1000, 8),
|
||||
_ => return Err(ParseSizeError::parse_failure(size)),
|
||||
};
|
||||
let factor = match usize::try_from(base.pow(exponent)) {
|
||||
Ok(n) => n,
|
||||
Err(_) => return Err(ParseSizeError::size_too_big(size)),
|
||||
};
|
||||
number
|
||||
.checked_mul(factor)
|
||||
.ok_or_else(|| ParseSizeError::size_too_big(size))
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ParseSizeError {
|
||||
ParseFailure(String), // Syntax
|
||||
SizeTooBig(String), // Overflow
|
||||
}
|
||||
|
||||
impl Error for ParseSizeError {
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
ParseSizeError::ParseFailure(ref s) => &*s,
|
||||
ParseSizeError::SizeTooBig(ref s) => &*s,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ParseSizeError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
let s = match self {
|
||||
ParseSizeError::ParseFailure(s) => s,
|
||||
ParseSizeError::SizeTooBig(s) => s,
|
||||
};
|
||||
write!(f, "{}", s)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseSizeError {
|
||||
fn parse_failure(s: &str) -> ParseSizeError {
|
||||
// stderr on linux (GNU coreutils 8.32)
|
||||
// has to be handled in the respective uutils because strings differ, e.g.:
|
||||
//
|
||||
// `NUM`
|
||||
// head: invalid number of bytes: ‘1fb’
|
||||
// tail: invalid number of bytes: ‘1fb’
|
||||
//
|
||||
// `SIZE`
|
||||
// split: invalid number of bytes: ‘1fb’
|
||||
// truncate: Invalid number: ‘1fb’
|
||||
//
|
||||
// `MODE`
|
||||
// stdbuf: invalid mode ‘1fb’
|
||||
//
|
||||
// `SIZE`
|
||||
// sort: invalid suffix in --buffer-size argument '1fb'
|
||||
// sort: invalid --buffer-size argument 'fb'
|
||||
//
|
||||
// `SIZE`
|
||||
// du: invalid suffix in --buffer-size argument '1fb'
|
||||
// du: invalid suffix in --threshold argument '1fb'
|
||||
// du: invalid --buffer-size argument 'fb'
|
||||
// du: invalid --threshold argument 'fb'
|
||||
//
|
||||
// `BYTES`
|
||||
// od: invalid suffix in --read-bytes argument '1fb'
|
||||
// od: invalid --read-bytes argument argument 'fb'
|
||||
// --skip-bytes
|
||||
// --width
|
||||
// --strings
|
||||
// etc.
|
||||
ParseSizeError::ParseFailure(format!("‘{}’", s))
|
||||
}
|
||||
|
||||
fn size_too_big(s: &str) -> ParseSizeError {
|
||||
// stderr on linux (GNU coreutils 8.32)
|
||||
// has to be handled in the respective uutils because strings differ, e.g.:
|
||||
//
|
||||
// head: invalid number of bytes: ‘1Y’: Value too large for defined data type
|
||||
// tail: invalid number of bytes: ‘1Y’: Value too large for defined data type
|
||||
// split: invalid number of bytes: ‘1Y’: Value too large for defined data type
|
||||
// truncate: Invalid number: ‘1Y’: Value too large for defined data type
|
||||
// stdbuf: invalid mode ‘1Y’: Value too large for defined data type
|
||||
// sort: -S argument '1Y' too large
|
||||
// du: -B argument '1Y' too large
|
||||
// od: -N argument '1Y' too large
|
||||
// etc.
|
||||
//
|
||||
// stderr on macos (brew - GNU coreutils 8.32) also differs for the same version, e.g.:
|
||||
// ghead: invalid number of bytes: ‘1Y’: Value too large to be stored in data type
|
||||
// gtail: invalid number of bytes: ‘1Y’: Value too large to be stored in data type
|
||||
ParseSizeError::SizeTooBig(format!("‘{}’: Value too large for defined data type", s))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn variant_eq(a: &ParseSizeError, b: &ParseSizeError) -> bool {
|
||||
std::mem::discriminant(a) == std::mem::discriminant(b)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_suffixes() {
|
||||
// Units are K,M,G,T,P,E,Z,Y (powers of 1024) or KB,MB,... (powers of 1000).
|
||||
// Binary prefixes can be used, too: KiB=K, MiB=M, and so on.
|
||||
let suffixes = [
|
||||
('K', 1u32),
|
||||
('M', 2u32),
|
||||
('G', 3u32),
|
||||
('T', 4u32),
|
||||
('P', 5u32),
|
||||
('E', 6u32),
|
||||
#[cfg(target_pointer_width = "128")]
|
||||
('Z', 7u32), // ParseSizeError::SizeTooBig on x64
|
||||
#[cfg(target_pointer_width = "128")]
|
||||
('Y', 8u32), // ParseSizeError::SizeTooBig on x64
|
||||
];
|
||||
|
||||
for &(c, exp) in &suffixes {
|
||||
let s = format!("2{}B", c); // KB
|
||||
assert_eq!(Ok((2 * (1000_u128).pow(exp)) as usize), parse_size(&s));
|
||||
let s = format!("2{}", c); // K
|
||||
assert_eq!(Ok((2 * (1024_u128).pow(exp)) as usize), parse_size(&s));
|
||||
let s = format!("2{}iB", c); // KiB
|
||||
assert_eq!(Ok((2 * (1024_u128).pow(exp)) as usize), parse_size(&s));
|
||||
let s = format!("2{}iB", c.to_lowercase()); // kiB
|
||||
assert_eq!(Ok((2 * (1024_u128).pow(exp)) as usize), parse_size(&s));
|
||||
|
||||
// suffix only
|
||||
let s = format!("{}B", c); // KB
|
||||
assert_eq!(Ok(((1000_u128).pow(exp)) as usize), parse_size(&s));
|
||||
let s = format!("{}", c); // K
|
||||
assert_eq!(Ok(((1024_u128).pow(exp)) as usize), parse_size(&s));
|
||||
let s = format!("{}iB", c); // KiB
|
||||
assert_eq!(Ok(((1024_u128).pow(exp)) as usize), parse_size(&s));
|
||||
let s = format!("{}iB", c.to_lowercase()); // kiB
|
||||
assert_eq!(Ok(((1024_u128).pow(exp)) as usize), parse_size(&s));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(target_pointer_width = "128"))]
|
||||
fn overflow_x64() {
|
||||
assert!(parse_size("10000000000000000000000").is_err());
|
||||
assert!(parse_size("1000000000T").is_err());
|
||||
assert!(parse_size("100000P").is_err());
|
||||
assert!(parse_size("100E").is_err());
|
||||
assert!(parse_size("1Z").is_err());
|
||||
assert!(parse_size("1Y").is_err());
|
||||
|
||||
assert!(variant_eq(
|
||||
&parse_size("1Z").unwrap_err(),
|
||||
&ParseSizeError::SizeTooBig(String::new())
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
ParseSizeError::SizeTooBig("‘1Y’: Value too large for defined data type".to_string()),
|
||||
parse_size("1Y").unwrap_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
fn overflow_x32() {
|
||||
assert!(variant_eq(
|
||||
&parse_size("1T").unwrap_err(),
|
||||
&ParseSizeError::SizeTooBig(String::new())
|
||||
));
|
||||
assert!(variant_eq(
|
||||
&parse_size("1000G").unwrap_err(),
|
||||
&ParseSizeError::SizeTooBig(String::new())
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_syntax() {
|
||||
let test_strings = [
|
||||
"328hdsf3290",
|
||||
"5MiB nonsense",
|
||||
"5mib",
|
||||
"biB",
|
||||
"-",
|
||||
"+",
|
||||
"",
|
||||
"-1",
|
||||
"1e2",
|
||||
"∞",
|
||||
];
|
||||
for &test_string in &test_strings {
|
||||
assert_eq!(
|
||||
parse_size(test_string).unwrap_err(),
|
||||
ParseSizeError::ParseFailure(format!("‘{}’", test_string))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn b_suffix() {
|
||||
assert_eq!(Ok(3 * 512), parse_size("3b")); // b is 512
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_suffix() {
|
||||
assert_eq!(Ok(1234), parse_size("1234"));
|
||||
assert_eq!(Ok(0), parse_size("0"));
|
||||
assert_eq!(Ok(5), parse_size("5"));
|
||||
assert_eq!(Ok(999), parse_size("999"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kilobytes_suffix() {
|
||||
assert_eq!(Ok(123 * 1000), parse_size("123KB")); // KB is 1000
|
||||
assert_eq!(Ok(9 * 1000), parse_size("9kB")); // kB is 1000
|
||||
assert_eq!(Ok(2 * 1024), parse_size("2K")); // K is 1024
|
||||
assert_eq!(Ok(0), parse_size("0K"));
|
||||
assert_eq!(Ok(0), parse_size("0KB"));
|
||||
assert_eq!(Ok(1000), parse_size("KB"));
|
||||
assert_eq!(Ok(1024), parse_size("K"));
|
||||
assert_eq!(Ok(2000), parse_size("2kB"));
|
||||
assert_eq!(Ok(4000), parse_size("4KB"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn megabytes_suffix() {
|
||||
assert_eq!(Ok(123 * 1024 * 1024), parse_size("123M"));
|
||||
assert_eq!(Ok(123 * 1000 * 1000), parse_size("123MB"));
|
||||
assert_eq!(Ok(1024 * 1024), parse_size("M"));
|
||||
assert_eq!(Ok(1000 * 1000), parse_size("MB"));
|
||||
assert_eq!(Ok(2 * 1_048_576), parse_size("2m"));
|
||||
assert_eq!(Ok(4 * 1_048_576), parse_size("4M"));
|
||||
assert_eq!(Ok(2_000_000), parse_size("2mB"));
|
||||
assert_eq!(Ok(4_000_000), parse_size("4MB"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gigabytes_suffix() {
|
||||
assert_eq!(Ok(1_073_741_824), parse_size("1G"));
|
||||
assert_eq!(Ok(2_000_000_000), parse_size("2GB"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
fn x64() {
|
||||
assert_eq!(Ok(1_099_511_627_776), parse_size("1T"));
|
||||
assert_eq!(Ok(1_125_899_906_842_624), parse_size("1P"));
|
||||
assert_eq!(Ok(1_152_921_504_606_846_976), parse_size("1E"));
|
||||
assert_eq!(Ok(2_000_000_000_000), parse_size("2TB"));
|
||||
assert_eq!(Ok(2_000_000_000_000_000), parse_size("2PB"));
|
||||
assert_eq!(Ok(2_000_000_000_000_000_000), parse_size("2EB"));
|
||||
}
|
||||
}
|
|
@ -20,20 +20,18 @@ pub fn from_str(string: &str) -> Result<Duration, String> {
|
|||
'm' | 'M' => (slice, 60),
|
||||
'h' | 'H' => (slice, 60 * 60),
|
||||
'd' | 'D' => (slice, 60 * 60 * 24),
|
||||
val => {
|
||||
if !val.is_alphabetic() {
|
||||
(string, 1)
|
||||
} else if string == "inf" || string == "infinity" {
|
||||
val if !val.is_alphabetic() => (string, 1),
|
||||
_ => {
|
||||
if string == "inf" || string == "infinity" {
|
||||
("inf", 1)
|
||||
} else {
|
||||
return Err(format!("invalid time interval '{}'", string));
|
||||
}
|
||||
}
|
||||
};
|
||||
let num = match numstr.parse::<f64>() {
|
||||
Ok(m) => m,
|
||||
Err(e) => return Err(format!("invalid time interval '{}': {}", string, e)),
|
||||
};
|
||||
let num = numstr
|
||||
.parse::<f64>()
|
||||
.map_err(|e| format!("invalid time interval '{}': {}", string, e))?;
|
||||
|
||||
const NANOS_PER_SEC: u32 = 1_000_000_000;
|
||||
let whole_secs = num.trunc();
|
|
@ -1,4 +1,4 @@
|
|||
// spell-checker:ignore (words) nosuchgroup
|
||||
// spell-checker:ignore (words) nosuchgroup groupname
|
||||
|
||||
use crate::common::util::*;
|
||||
use rust_users::*;
|
||||
|
@ -10,6 +10,33 @@ fn test_invalid_option() {
|
|||
|
||||
static DIR: &str = "/tmp";
|
||||
|
||||
// we should always get both arguments, regardless of whether --reference was used
|
||||
#[test]
|
||||
fn test_help() {
|
||||
new_ucmd!()
|
||||
.arg("--help")
|
||||
.succeeds()
|
||||
.stdout_contains("ARGS:\n <GROUP> \n <FILE>... ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_help_ref() {
|
||||
new_ucmd!()
|
||||
.arg("--help")
|
||||
.arg("--reference=ref_file")
|
||||
.succeeds()
|
||||
.stdout_contains("ARGS:\n <GROUP> \n <FILE>... ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ref_help() {
|
||||
new_ucmd!()
|
||||
.arg("--reference=ref_file")
|
||||
.arg("--help")
|
||||
.succeeds()
|
||||
.stdout_contains("ARGS:\n <GROUP> \n <FILE>... ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_group() {
|
||||
new_ucmd!()
|
||||
|
@ -121,9 +148,52 @@ fn test_reference() {
|
|||
fn test_reference() {
|
||||
new_ucmd!()
|
||||
.arg("-v")
|
||||
.arg("--reference=/etc/passwd")
|
||||
.arg("--reference=ref_file")
|
||||
.arg("/etc")
|
||||
.succeeds();
|
||||
.fails()
|
||||
// group name can differ, so just check the first part of the message
|
||||
.stderr_contains("chgrp: changing group of '/etc': Operation not permitted (os error 1)\nfailed to change group of '/etc' from ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||
fn test_reference_multi_no_equal() {
|
||||
new_ucmd!()
|
||||
.arg("-v")
|
||||
.arg("--reference")
|
||||
.arg("ref_file")
|
||||
.arg("file1")
|
||||
.arg("file2")
|
||||
.succeeds()
|
||||
.stderr_contains("chgrp: group of 'file1' retained as ")
|
||||
.stderr_contains("\nchgrp: group of 'file2' retained as ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||
fn test_reference_last() {
|
||||
new_ucmd!()
|
||||
.arg("-v")
|
||||
.arg("file1")
|
||||
.arg("file2")
|
||||
.arg("file3")
|
||||
.arg("--reference")
|
||||
.arg("ref_file")
|
||||
.succeeds()
|
||||
.stderr_contains("chgrp: group of 'file1' retained as ")
|
||||
.stderr_contains("\nchgrp: group of 'file2' retained as ")
|
||||
.stderr_contains("\nchgrp: group of 'file3' retained as ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_missing_files() {
|
||||
new_ucmd!()
|
||||
.arg("-v")
|
||||
.arg("groupname")
|
||||
.fails()
|
||||
.stderr_contains(
|
||||
"error: The following required arguments were not provided:\n <FILE>...\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -135,7 +205,7 @@ fn test_big_p() {
|
|||
.arg("bin")
|
||||
.arg("/proc/self/cwd")
|
||||
.fails()
|
||||
.stderr_is(
|
||||
.stderr_contains(
|
||||
"chgrp: changing group of '/proc/self/cwd': Operation not permitted (os error 1)\n",
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
// * This file is part of the uutils coreutils package.
|
||||
// *
|
||||
// * For the full copyright and license information, please view the LICENSE
|
||||
// * file that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore (paths) sublink subwords
|
||||
|
||||
use crate::common::util::*;
|
||||
|
@ -73,6 +78,23 @@ fn _du_basics_subdir(s: &str) {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_du_invalid_size() {
|
||||
new_ucmd!()
|
||||
.arg("--block-size=1fb4t")
|
||||
.arg("/tmp")
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stderr_only("du: invalid --block-size argument '1fb4t'");
|
||||
#[cfg(not(target_pointer_width = "128"))]
|
||||
new_ucmd!()
|
||||
.arg("--block-size=1Y")
|
||||
.arg("/tmp")
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stderr_only("du: --block-size argument '1Y' too large");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_du_basics_bad_name() {
|
||||
new_ucmd!()
|
||||
|
@ -312,3 +334,20 @@ fn _du_no_permission(s: &str) {
|
|||
fn _du_no_permission(s: &str) {
|
||||
assert_eq!(s, "4\tsubdir/links\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_du_one_file_system() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
|
||||
let result = scene.ucmd().arg("-x").arg(SUB_DIR).succeeds();
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let result_reference = scene.cmd("du").arg("-x").arg(SUB_DIR).run();
|
||||
if result_reference.succeeded() {
|
||||
assert_eq!(result.stdout_str(), result_reference.stdout_str());
|
||||
return;
|
||||
}
|
||||
}
|
||||
_du_basics_subdir(result.stdout_str());
|
||||
}
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
// spell-checker:ignore (words) bogusfile emptyfile
|
||||
// * This file is part of the uutils coreutils package.
|
||||
// *
|
||||
// * For the full copyright and license information, please view the LICENSE
|
||||
// * file that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore (words) bogusfile emptyfile abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstu
|
||||
|
||||
use crate::common::util::*;
|
||||
|
||||
|
@ -244,3 +249,61 @@ hello
|
|||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_head_invalid_num() {
|
||||
new_ucmd!()
|
||||
.args(&["-c", "1024R", "emptyfile.txt"])
|
||||
.fails()
|
||||
.stderr_is("head: invalid number of bytes: ‘1024R’");
|
||||
new_ucmd!()
|
||||
.args(&["-n", "1024R", "emptyfile.txt"])
|
||||
.fails()
|
||||
.stderr_is("head: invalid number of lines: ‘1024R’");
|
||||
#[cfg(not(target_pointer_width = "128"))]
|
||||
new_ucmd!()
|
||||
.args(&["-c", "1Y", "emptyfile.txt"])
|
||||
.fails()
|
||||
.stderr_is("head: invalid number of bytes: ‘1Y’: Value too large for defined data type");
|
||||
#[cfg(not(target_pointer_width = "128"))]
|
||||
new_ucmd!()
|
||||
.args(&["-n", "1Y", "emptyfile.txt"])
|
||||
.fails()
|
||||
.stderr_is("head: invalid number of lines: ‘1Y’: Value too large for defined data type");
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
{
|
||||
let sizes = ["1000G", "10T"];
|
||||
for size in &sizes {
|
||||
new_ucmd!()
|
||||
.args(&["-c", size])
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stderr_only(format!(
|
||||
"head: invalid number of bytes: ‘{}’: Value too large for defined data type",
|
||||
size
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_head_num_with_undocumented_sign_bytes() {
|
||||
// tail: '-' is not documented (8.32 man pages)
|
||||
// head: '+' is not documented (8.32 man pages)
|
||||
const ALPHABET: &str = "abcdefghijklmnopqrstuvwxyz";
|
||||
new_ucmd!()
|
||||
.args(&["-c", "5"])
|
||||
.pipe_in(ALPHABET)
|
||||
.succeeds()
|
||||
.stdout_is("abcde");
|
||||
new_ucmd!()
|
||||
.args(&["-c", "-5"])
|
||||
.pipe_in(ALPHABET)
|
||||
.succeeds()
|
||||
.stdout_is("abcdefghijklmnopqrstu");
|
||||
new_ucmd!()
|
||||
.args(&["-c", "+5"])
|
||||
.pipe_in(ALPHABET)
|
||||
.succeeds()
|
||||
.stdout_is("abcde");
|
||||
}
|
||||
|
|
|
@ -428,20 +428,6 @@ fn test_symlink_relative() {
|
|||
assert_eq!(at.resolve_link(link), file_a);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hardlink_relative() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let file_a = "test_hardlink_relative_a";
|
||||
let link = "test_hardlink_relative_link";
|
||||
|
||||
at.touch(file_a);
|
||||
|
||||
// relative hardlink
|
||||
ucmd.args(&["-r", "-v", file_a, link])
|
||||
.succeeds()
|
||||
.stdout_only(format!("'{}' -> '{}'\n", link, file_a));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_symlink_relative_path() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
@ -571,3 +557,26 @@ fn test_symlink_no_deref_file() {
|
|||
assert!(at.is_symlink(link));
|
||||
assert_eq!(at.resolve_link(link), file1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relative_requires_symbolic() {
|
||||
new_ucmd!().args(&["-r", "foo", "bar"]).fails();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relative_dst_already_symlink() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
at.touch("file1");
|
||||
at.symlink_file("file1", "file2");
|
||||
ucmd.arg("-srf").arg("file1").arg("file2").succeeds();
|
||||
at.is_symlink("file2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relative_src_already_symlink() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
at.touch("file1");
|
||||
at.symlink_file("file1", "file2");
|
||||
ucmd.arg("-sr").arg("file2").arg("file3").succeeds();
|
||||
assert!(at.resolve_link("file3").ends_with("file1"));
|
||||
}
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
// * This file is part of the uutils coreutils package.
|
||||
// *
|
||||
// * For the full copyright and license information, please view the LICENSE
|
||||
// * file that was distributed with this source code.
|
||||
|
||||
extern crate unindent;
|
||||
|
||||
use self::unindent::*;
|
||||
|
@ -804,3 +809,40 @@ fn test_traditional_only_label() {
|
|||
",
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_od_invalid_bytes() {
|
||||
const INVALID_SIZE: &str = "1fb4t";
|
||||
const BIG_SIZE: &str = "1Y";
|
||||
|
||||
// NOTE:
|
||||
// GNU's od (8.32) with option '--width' does not accept 'Y' as valid suffix.
|
||||
// According to the man page it should be valid in the same way it is valid for
|
||||
// '--read-bytes' and '--skip-bytes'.
|
||||
|
||||
let options = [
|
||||
"--read-bytes",
|
||||
"--skip-bytes",
|
||||
"--width",
|
||||
// "--strings", // TODO: consider testing here once '--strings' is implemented
|
||||
];
|
||||
for option in &options {
|
||||
new_ucmd!()
|
||||
.arg(format!("{}={}", option, INVALID_SIZE))
|
||||
.arg("file")
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stderr_only(format!(
|
||||
"od: invalid {} argument '{}'",
|
||||
option, INVALID_SIZE
|
||||
));
|
||||
|
||||
#[cfg(not(target_pointer_width = "128"))]
|
||||
new_ucmd!()
|
||||
.arg(format!("{}={}", option, BIG_SIZE))
|
||||
.arg("file")
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stderr_only(format!("od: {} argument '{}' too large", option, BIG_SIZE));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
// * This file is part of the uutils coreutils package.
|
||||
// *
|
||||
// * For the full copyright and license information, please view the LICENSE
|
||||
// * file that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore (words) ints
|
||||
|
||||
use crate::common::util::*;
|
||||
|
@ -21,9 +26,7 @@ fn test_helper(file_name: &str, possible_args: &[&str]) {
|
|||
|
||||
#[test]
|
||||
fn test_buffer_sizes() {
|
||||
let buffer_sizes = [
|
||||
"0", "50K", "50k", "1M", "100M", "1000G", "10T", "500E", "1Y",
|
||||
];
|
||||
let buffer_sizes = ["0", "50K", "50k", "1M", "100M"];
|
||||
for buffer_size in &buffer_sizes {
|
||||
new_ucmd!()
|
||||
.arg("-n")
|
||||
|
@ -32,6 +35,20 @@ fn test_buffer_sizes() {
|
|||
.arg("ext_sort.txt")
|
||||
.succeeds()
|
||||
.stdout_is_fixture("ext_sort.expected");
|
||||
|
||||
#[cfg(not(target_pointer_width = "32"))]
|
||||
{
|
||||
let buffer_sizes = ["1000G", "10T"];
|
||||
for buffer_size in &buffer_sizes {
|
||||
new_ucmd!()
|
||||
.arg("-n")
|
||||
.arg("-S")
|
||||
.arg(buffer_size)
|
||||
.arg("ext_sort.txt")
|
||||
.succeeds()
|
||||
.stdout_is_fixture("ext_sort.expected");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,11 +60,39 @@ fn test_invalid_buffer_size() {
|
|||
.arg("-S")
|
||||
.arg(invalid_buffer_size)
|
||||
.fails()
|
||||
.code_is(2)
|
||||
.stderr_only(format!(
|
||||
"sort: failed to parse buffer size `{}`: invalid digit found in string",
|
||||
"sort: invalid --buffer-size argument '{}'",
|
||||
invalid_buffer_size
|
||||
));
|
||||
}
|
||||
#[cfg(not(target_pointer_width = "128"))]
|
||||
new_ucmd!()
|
||||
.arg("-n")
|
||||
.arg("-S")
|
||||
.arg("1Y")
|
||||
.arg("ext_sort.txt")
|
||||
.fails()
|
||||
.code_is(2)
|
||||
.stderr_only("sort: --buffer-size argument '1Y' too large");
|
||||
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
{
|
||||
let buffer_sizes = ["1000G", "10T"];
|
||||
for buffer_size in &buffer_sizes {
|
||||
new_ucmd!()
|
||||
.arg("-n")
|
||||
.arg("-S")
|
||||
.arg(buffer_size)
|
||||
.arg("ext_sort.txt")
|
||||
.fails()
|
||||
.code_is(2)
|
||||
.stderr_only(format!(
|
||||
"sort: --buffer-size argument '{}' too large",
|
||||
buffer_size
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -804,7 +849,7 @@ fn sort_multiple() {
|
|||
#[test]
|
||||
fn sort_empty_chunk() {
|
||||
new_ucmd!()
|
||||
.args(&["-S", "40B"])
|
||||
.args(&["-S", "40b"])
|
||||
.pipe_in("a\na\n")
|
||||
.succeeds()
|
||||
.stdout_is("a\na\n");
|
||||
|
@ -844,7 +889,7 @@ fn test_compress_fail() {
|
|||
#[test]
|
||||
fn test_merge_batches() {
|
||||
new_ucmd!()
|
||||
.args(&["ext_sort.txt", "-n", "-S", "150B"])
|
||||
.args(&["ext_sort.txt", "-n", "-S", "150b"])
|
||||
.succeeds()
|
||||
.stdout_only_fixture("ext_sort.expected");
|
||||
}
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
// * This file is part of the uutils coreutils package.
|
||||
// *
|
||||
// * For the full copyright and license information, please view the LICENSE
|
||||
// * file that was distributed with this source code.
|
||||
|
||||
extern crate rand;
|
||||
extern crate regex;
|
||||
|
||||
|
@ -285,3 +290,53 @@ fn test_filter_command_fails() {
|
|||
ucmd.args(&["--filter=/a/path/that/totally/does/not/exist", name])
|
||||
.fails();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_lines_number() {
|
||||
// Test if stdout/stderr for '--lines' option is correct
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
at.touch("file");
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.args(&["--lines", "2", "file"])
|
||||
.succeeds()
|
||||
.no_stderr()
|
||||
.no_stdout();
|
||||
scene
|
||||
.ucmd()
|
||||
.args(&["--lines", "2fb", "file"])
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stderr_only("split: invalid number of lines: ‘2fb’");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_invalid_bytes_size() {
|
||||
new_ucmd!()
|
||||
.args(&["-b", "1024R"])
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stderr_only("split: invalid number of bytes: ‘1024R’");
|
||||
#[cfg(not(target_pointer_width = "128"))]
|
||||
new_ucmd!()
|
||||
.args(&["-b", "1Y"])
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stderr_only("split: invalid number of bytes: ‘1Y’: Value too large for defined data type");
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
{
|
||||
let sizes = ["1000G", "10T"];
|
||||
for size in &sizes {
|
||||
new_ucmd!()
|
||||
.args(&["-b", size])
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stderr_only(format!(
|
||||
"split: invalid number of bytes: ‘{}’: Value too large for defined data type",
|
||||
size
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,8 +57,18 @@ fn test_stdbuf_line_buffering_stdin_fails() {
|
|||
#[cfg(not(target_os = "windows"))]
|
||||
#[test]
|
||||
fn test_stdbuf_invalid_mode_fails() {
|
||||
new_ucmd!()
|
||||
.args(&["-i", "1024R", "head"])
|
||||
.fails()
|
||||
.stderr_is("stdbuf: invalid mode 1024R\nTry 'stdbuf --help' for more information.");
|
||||
let options = ["--input", "--output", "--error"];
|
||||
for option in &options {
|
||||
new_ucmd!()
|
||||
.args(&[*option, "1024R", "head"])
|
||||
.fails()
|
||||
.code_is(125)
|
||||
.stderr_only("stdbuf: invalid mode ‘1024R’");
|
||||
#[cfg(not(target_pointer_width = "128"))]
|
||||
new_ucmd!()
|
||||
.args(&[*option, "1Y", "head"])
|
||||
.fails()
|
||||
.code_is(125)
|
||||
.stderr_contains("stdbuf: invalid mode ‘1Y’: Value too large for defined data type");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
// * This file is part of the uutils coreutils package.
|
||||
// *
|
||||
// * For the full copyright and license information, please view the LICENSE
|
||||
// * file that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore (ToDO) abcdefghijklmnopqrstuvwxyz efghijklmnopqrstuvwxyz vwxyz emptyfile
|
||||
|
||||
extern crate tail;
|
||||
|
||||
use self::tail::parse_size;
|
||||
use crate::common::util::*;
|
||||
use std::char::from_digit;
|
||||
use std::io::Write;
|
||||
|
@ -236,41 +242,6 @@ fn test_bytes_big() {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_size() {
|
||||
// No suffix.
|
||||
assert_eq!(Ok(1234), parse_size("1234"));
|
||||
|
||||
// kB is 1000
|
||||
assert_eq!(Ok(9 * 1000), parse_size("9kB"));
|
||||
|
||||
// K is 1024
|
||||
assert_eq!(Ok(2 * 1024), parse_size("2K"));
|
||||
|
||||
let suffixes = [
|
||||
('M', 2u32),
|
||||
('G', 3u32),
|
||||
('T', 4u32),
|
||||
('P', 5u32),
|
||||
('E', 6u32),
|
||||
];
|
||||
|
||||
for &(c, exp) in &suffixes {
|
||||
let s = format!("2{}B", c);
|
||||
assert_eq!(Ok(2 * (1000_u64).pow(exp)), parse_size(&s));
|
||||
|
||||
let s = format!("2{}", c);
|
||||
assert_eq!(Ok(2 * (1024_u64).pow(exp)), parse_size(&s));
|
||||
}
|
||||
|
||||
// Sizes that are too big.
|
||||
assert!(parse_size("1Z").is_err());
|
||||
assert!(parse_size("1Y").is_err());
|
||||
|
||||
// Bad number
|
||||
assert!(parse_size("328hdsf3290").is_err()); // spell-checker:disable-line
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lines_with_size_suffix() {
|
||||
const FILE: &str = "test_lines_with_size_suffix.txt";
|
||||
|
@ -320,12 +291,11 @@ fn test_multiple_input_files_with_suppressed_headers() {
|
|||
|
||||
#[test]
|
||||
fn test_multiple_input_quiet_flag_overrides_verbose_flag_for_suppressing_headers() {
|
||||
// TODO: actually the later one should win, i.e. -qv should lead to headers being printed, -vq to them being suppressed
|
||||
new_ucmd!()
|
||||
.arg(FOOBAR_TXT)
|
||||
.arg(FOOBAR_2_TXT)
|
||||
.arg("-q")
|
||||
.arg("-v")
|
||||
.arg("-q")
|
||||
.run()
|
||||
.stdout_is_fixture("foobar_multiple_quiet.expected");
|
||||
}
|
||||
|
@ -388,3 +358,61 @@ fn test_positive_zero_lines() {
|
|||
.succeeds()
|
||||
.stdout_is("a\nb\nc\nd\ne\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tail_invalid_num() {
|
||||
new_ucmd!()
|
||||
.args(&["-c", "1024R", "emptyfile.txt"])
|
||||
.fails()
|
||||
.stderr_is("tail: invalid number of bytes: ‘1024R’");
|
||||
new_ucmd!()
|
||||
.args(&["-n", "1024R", "emptyfile.txt"])
|
||||
.fails()
|
||||
.stderr_is("tail: invalid number of lines: ‘1024R’");
|
||||
#[cfg(not(target_pointer_width = "128"))]
|
||||
new_ucmd!()
|
||||
.args(&["-c", "1Y", "emptyfile.txt"])
|
||||
.fails()
|
||||
.stderr_is("tail: invalid number of bytes: ‘1Y’: Value too large for defined data type");
|
||||
#[cfg(not(target_pointer_width = "128"))]
|
||||
new_ucmd!()
|
||||
.args(&["-n", "1Y", "emptyfile.txt"])
|
||||
.fails()
|
||||
.stderr_is("tail: invalid number of lines: ‘1Y’: Value too large for defined data type");
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
{
|
||||
let sizes = ["1000G", "10T"];
|
||||
for size in &sizes {
|
||||
new_ucmd!()
|
||||
.args(&["-c", size])
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stderr_only(format!(
|
||||
"tail: invalid number of bytes: ‘{}’: Value too large for defined data type",
|
||||
size
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tail_num_with_undocumented_sign_bytes() {
|
||||
// tail: '-' is not documented (8.32 man pages)
|
||||
// head: '+' is not documented (8.32 man pages)
|
||||
const ALPHABET: &str = "abcdefghijklmnopqrstuvwxyz";
|
||||
new_ucmd!()
|
||||
.args(&["-c", "5"])
|
||||
.pipe_in(ALPHABET)
|
||||
.succeeds()
|
||||
.stdout_is("vwxyz");
|
||||
new_ucmd!()
|
||||
.args(&["-c", "-5"])
|
||||
.pipe_in(ALPHABET)
|
||||
.succeeds()
|
||||
.stdout_is("vwxyz");
|
||||
new_ucmd!()
|
||||
.args(&["-c", "+5"])
|
||||
.pipe_in(ALPHABET)
|
||||
.succeeds()
|
||||
.stdout_is("efghijklmnopqrstuvwxyz");
|
||||
}
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
// * This file is part of the uutils coreutils package.
|
||||
// *
|
||||
// * For the full copyright and license information, please view the LICENSE
|
||||
// * file that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore (words) RFILE
|
||||
|
||||
use crate::common::util::*;
|
||||
use std::io::{Seek, SeekFrom, Write};
|
||||
|
||||
|
@ -45,9 +52,18 @@ fn test_reference() {
|
|||
let at = &scene.fixtures;
|
||||
let mut file = at.make_file(FILE2);
|
||||
|
||||
scene.ucmd().arg("-s").arg("+5KB").arg(FILE1).run();
|
||||
// manpage: "A FILE argument that does not exist is created."
|
||||
// TODO: 'truncate' does not create the file in this case,
|
||||
// but should because '--no-create' wasn't specified.
|
||||
at.touch(FILE1); // TODO: remove this when 'no-create' is fixed
|
||||
scene.ucmd().arg("-s").arg("+5KB").arg(FILE1).succeeds();
|
||||
|
||||
scene.ucmd().arg("--reference").arg(FILE1).arg(FILE2).run();
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("--reference")
|
||||
.arg(FILE1)
|
||||
.arg(FILE2)
|
||||
.succeeds();
|
||||
|
||||
file.seek(SeekFrom::End(0)).unwrap();
|
||||
let actual = file.seek(SeekFrom::Current(0)).unwrap();
|
||||
|
@ -231,11 +247,18 @@ fn test_size_and_reference() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_filename_only() {
|
||||
// truncate: you must specify either ‘--size’ or ‘--reference’
|
||||
new_ucmd!().args(&["file"]).fails().stderr_contains(
|
||||
"error: The following required arguments were not provided:
|
||||
--reference <RFILE>
|
||||
--size <SIZE>",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_numbers() {
|
||||
// TODO For compatibility with GNU, `truncate -s 0X` should cause
|
||||
// the same error as `truncate -s 0X file`, but currently it returns
|
||||
// a different error.
|
||||
new_ucmd!()
|
||||
.args(&["-s", "0X", "file"])
|
||||
.fails()
|
||||
|
@ -265,3 +288,36 @@ fn test_reference_with_size_file_not_found() {
|
|||
.fails()
|
||||
.stderr_contains("cannot stat 'a': No such file or directory");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_truncate_bytes_size() {
|
||||
// TODO: this should succeed without error, uncomment when '--no-create' is fixed
|
||||
// new_ucmd!()
|
||||
// .args(&["--no-create", "--size", "K", "file"])
|
||||
// .succeeds();
|
||||
new_ucmd!()
|
||||
.args(&["--size", "1024R", "file"])
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stderr_only("truncate: Invalid number: ‘1024R’");
|
||||
#[cfg(not(target_pointer_width = "128"))]
|
||||
new_ucmd!()
|
||||
.args(&["--size", "1Y", "file"])
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stderr_only("truncate: Invalid number: ‘1Y’: Value too large for defined data type");
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
{
|
||||
let sizes = ["1000G", "10T"];
|
||||
for size in &sizes {
|
||||
new_ucmd!()
|
||||
.args(&["--size", size, "file"])
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stderr_only(format!(
|
||||
"truncate: Invalid number: ‘{}’: Value too large for defined data type",
|
||||
size
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,6 +69,22 @@ pub struct CmdResult {
|
|||
}
|
||||
|
||||
impl CmdResult {
|
||||
pub fn new(
|
||||
tmpd: Option<Rc<TempDir>>,
|
||||
code: Option<i32>,
|
||||
success: bool,
|
||||
stdout: &[u8],
|
||||
stderr: &[u8],
|
||||
) -> CmdResult {
|
||||
CmdResult {
|
||||
tmpd,
|
||||
code,
|
||||
success,
|
||||
stdout: stdout.to_vec(),
|
||||
stderr: stderr.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the program's standard output as a slice of bytes
|
||||
pub fn stdout(&self) -> &[u8] {
|
||||
&self.stdout
|
||||
|
|
1
tests/fixtures/chgrp/file1
vendored
Normal file
1
tests/fixtures/chgrp/file1
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
target file 1
|
1
tests/fixtures/chgrp/file2
vendored
Normal file
1
tests/fixtures/chgrp/file2
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
target file 2
|
1
tests/fixtures/chgrp/file3
vendored
Normal file
1
tests/fixtures/chgrp/file3
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
target file 3
|
1
tests/fixtures/chgrp/ref_file
vendored
Normal file
1
tests/fixtures/chgrp/ref_file
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Reference file
|
|
@ -5,12 +5,12 @@
|
|||
set -e
|
||||
if test ! -d ../gnu; then
|
||||
echo "Could not find ../gnu"
|
||||
echo "git clone git@github.com:coreutils/coreutils.git ../gnu"
|
||||
echo "git clone git@github.com:coreutils/coreutils.git gnu"
|
||||
exit 1
|
||||
fi
|
||||
if test ! -d ../gnulib; then
|
||||
echo "Could not find ../gnulib"
|
||||
echo "git clone git@github.com:coreutils/gnulib.git ../gnulib"
|
||||
echo "git clone git@github.com:coreutils/gnulib.git gnulib"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue