1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-29 12:07:46 +00:00

Merge branch 'master' of https://github.com/uutils/coreutils into sort/args

This commit is contained in:
Michael Debertol 2021-06-12 13:16:46 +02:00
commit af6e92e012
57 changed files with 1628 additions and 1237 deletions

1
Cargo.lock generated
View file

@ -1775,6 +1775,7 @@ dependencies = [
name = "uu_chgrp" name = "uu_chgrp"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"clap",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
"walkdir", "walkdir",

View file

@ -54,15 +54,13 @@ impl Config {
None => None, None => None,
}; };
let cols = match options.value_of(options::WRAP) { let cols = options
Some(num) => match num.parse::<usize>() { .value_of(options::WRAP)
Ok(n) => Some(n), .map(|num| {
Err(e) => { num.parse::<usize>()
return Err(format!("Invalid wrap size: {}: {}", num, e)); .map_err(|e| format!("Invalid wrap size: {}: {}", num, e))
} })
}, .transpose()?;
None => None,
};
Ok(Config { Ok(Config {
decode: options.is_present(options::DECODE), decode: options.is_present(options::DECODE),

View file

@ -15,6 +15,7 @@ edition = "2018"
path = "src/chgrp.rs" path = "src/chgrp.rs"
[dependencies] [dependencies]
clap = "2.33"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] } 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" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
walkdir = "2.2" walkdir = "2.2"

View file

@ -14,6 +14,8 @@ use uucore::fs::resolve_relative_path;
use uucore::libc::gid_t; use uucore::libc::gid_t;
use uucore::perms::{wrap_chgrp, Verbosity}; use uucore::perms::{wrap_chgrp, Verbosity};
use clap::{App, Arg};
extern crate walkdir; extern crate walkdir;
use walkdir::WalkDir; use walkdir::WalkDir;
@ -24,76 +26,194 @@ use std::os::unix::fs::MetadataExt;
use std::path::Path; use std::path::Path;
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
static SYNTAX: &str = static ABOUT: &str = "Change the group of each FILE to GROUP.";
"chgrp [OPTION]... GROUP FILE...\n or : chgrp [OPTION]... --reference=RFILE FILE..."; static VERSION: &str = env!("CARGO_PKG_VERSION");
static SUMMARY: &str = "Change the group of each FILE to GROUP.";
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_COMFOLLOW: u8 = 1;
const FTS_PHYSICAL: u8 = 1 << 1; const FTS_PHYSICAL: u8 = 1 << 1;
const FTS_LOGICAL: u8 = 1 << 2; 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 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args let args = args
.collect_str(InvalidEncodingHandling::ConvertLossy) .collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any(); .accept_any();
let mut opts = app!(SYNTAX, SUMMARY, ""); let usage = get_usage();
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 mut bit_flag = FTS_PHYSICAL; let mut app = App::new(executable!())
let mut preserve_root = false; .version(VERSION)
let mut derefer = -1; .about(ABOUT)
let flags: &[char] = &['H', 'L', 'P']; .usage(&usage[..])
for opt in &args { .arg(
match opt.as_str() { Arg::with_name(options::verbosity::CHANGES)
// If more than one is specified, only the final one takes effect. .short("c")
s if s.contains(flags) => { .long(options::verbosity::CHANGES)
if let Some(idx) = s.rfind(flags) { .help("like verbose but report only when a change is made"),
match s.chars().nth(idx).unwrap() { )
'H' => bit_flag = FTS_COMFOLLOW | FTS_PHYSICAL, .arg(
'L' => bit_flag = FTS_LOGICAL, Arg::with_name(options::verbosity::SILENT)
'P' => bit_flag = FTS_PHYSICAL, .short("f")
_ => (), .long(options::verbosity::SILENT),
} )
} .arg(
} Arg::with_name(options::verbosity::QUIET)
"--no-preserve-root" => preserve_root = false, .long(options::verbosity::QUIET)
"--preserve-root" => preserve_root = true, .help("suppress most error messages"),
"--dereference" => derefer = 1, )
"--no-dereference" => derefer = 0, .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); if help || !reference {
let recursive = matches.opt_present("recursive"); // 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 recursive {
if bit_flag == FTS_PHYSICAL { if bit_flag == FTS_PHYSICAL {
if derefer == 1 { if derefer == 1 {
@ -106,27 +226,20 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
bit_flag = FTS_PHYSICAL; bit_flag = FTS_PHYSICAL;
} }
let verbosity = if matches.opt_present("changes") { let verbosity = if matches.is_present(options::verbosity::CHANGES) {
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 Verbosity::Silent
} else if matches.opt_present("verbose") { } else if matches.is_present(options::verbosity::VERBOSE) {
Verbosity::Verbose Verbosity::Verbose
} else { } else {
Verbosity::Normal Verbosity::Normal
}; };
if matches.free.is_empty() { let dest_gid: u32;
show_usage_error!("missing operand"); if let Some(file) = matches.value_of(options::REFERENCE) {
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") {
match fs::metadata(&file) { match fs::metadata(&file) {
Ok(meta) => { Ok(meta) => {
dest_gid = meta.gid(); dest_gid = meta.gid();
@ -136,19 +249,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
return 1; return 1;
} }
} }
files = matches.free;
} else { } else {
match entries::grp2gid(&matches.free[0]) { let group = matches.value_of(options::ARG_GROUP).unwrap_or_default();
match entries::grp2gid(group) {
Ok(g) => { Ok(g) => {
dest_gid = g; dest_gid = g;
} }
_ => { _ => {
show_error!("invalid group: {}", matches.free[0].as_str()); show_error!("invalid group: {}", group);
return 1; return 1;
} }
} }
files = matches.free;
files.remove(0);
} }
let executor = Chgrper { let executor = Chgrper {

View file

@ -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 usr_only = args.len() == 1 && !args[0].is_empty();
let grp_only = args.len() == 2 && 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(); let usr_grp = args.len() == 2 && !args[0].is_empty() && !args[1].is_empty();
let uid = if usr_only || usr_grp {
if usr_only { Some(
Ok(( Passwd::locate(args[0])
Some(match Passwd::locate(args[0]) { .map_err(|_| format!("invalid user: {}", spec))?
Ok(v) => v.uid(), .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)),
}),
))
} else { } 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 { 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: "));
}
}

View file

@ -160,8 +160,7 @@ fn cksum(fname: &str) -> io::Result<(u32, usize)> {
let mut bytes = init_byte_array(); let mut bytes = init_byte_array();
loop { loop {
match rd.read(&mut bytes) { let num_bytes = rd.read(&mut bytes)?;
Ok(num_bytes) => {
if num_bytes == 0 { if num_bytes == 0 {
return Ok((crc_final(crc, size), size)); return Ok((crc_final(crc, size), size));
} }
@ -170,9 +169,6 @@ fn cksum(fname: &str) -> io::Result<(u32, usize)> {
} }
size += num_bytes; size += num_bytes;
} }
Err(err) => return Err(err),
}
}
} }
mod options { mod options {

View file

@ -709,27 +709,26 @@ fn parse_path_args(path_args: &[String], options: &Options) -> CopyResult<(Vec<S
return Err(format!("extra operand {:?}", paths[2]).into()); 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) => { Some(ref target) => {
// All path args are sources, and the target dir was // All path args are sources, and the target dir was
// specified separately // specified separately
(paths, PathBuf::from(target)) PathBuf::from(target)
} }
None => { None => {
// If there was no explicit target-dir, then use the last // If there was no explicit target-dir, then use the last
// path_arg // path_arg
let target = paths.pop().unwrap(); paths.pop().unwrap()
(paths, target)
} }
}; };
if options.strip_trailing_slashes { if options.strip_trailing_slashes {
for source in sources.iter_mut() { for source in paths.iter_mut() {
*source = source.components().as_path().to_owned() *source = source.components().as_path().to_owned()
} }
} }
Ok((sources, target)) Ok((paths, target))
} }
fn preserve_hardlinks( fn preserve_hardlinks(
@ -1271,15 +1270,15 @@ fn copy_on_write_linux(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyRes
ReflinkMode::Always => unsafe { ReflinkMode::Always => unsafe {
let result = ficlone(dst_file.as_raw_fd(), src_file.as_raw_fd() as *const i32); let result = ficlone(dst_file.as_raw_fd(), src_file.as_raw_fd() as *const i32);
if result != 0 { if result != 0 {
return Err(format!( Err(format!(
"failed to clone {:?} from {:?}: {}", "failed to clone {:?} from {:?}: {}",
source, source,
dest, dest,
std::io::Error::last_os_error() std::io::Error::last_os_error()
) )
.into()); .into())
} else { } else {
return Ok(()); Ok(())
} }
}, },
ReflinkMode::Auto => unsafe { ReflinkMode::Auto => unsafe {
@ -1287,11 +1286,10 @@ fn copy_on_write_linux(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyRes
if result != 0 { if result != 0 {
fs::copy(source, dest).context(&*context_for(source, dest))?; fs::copy(source, dest).context(&*context_for(source, dest))?;
} }
Ok(())
}, },
ReflinkMode::Never => unreachable!(), ReflinkMode::Never => unreachable!(),
} }
Ok(())
} }
/// Copies `source` to `dest` using copy-on-write if possible. /// Copies `source` to `dest` using copy-on-write if possible.

View file

@ -133,20 +133,12 @@ fn extract_patterns(args: &[String]) -> Result<Vec<Pattern>, CsplitError> {
Some(m) => m.as_str().parse().unwrap(), Some(m) => m.as_str().parse().unwrap(),
}; };
if let Some(up_to_match) = captures.name("UPTO") { if let Some(up_to_match) = captures.name("UPTO") {
let pattern = match Regex::new(up_to_match.as_str()) { let pattern = Regex::new(up_to_match.as_str())
Err(_) => { .map_err(|_| CsplitError::InvalidPattern(arg.to_string()))?;
return Err(CsplitError::InvalidPattern(arg.to_string()));
}
Ok(reg) => reg,
};
patterns.push(Pattern::UpToMatch(pattern, offset, execute_ntimes)); patterns.push(Pattern::UpToMatch(pattern, offset, execute_ntimes));
} else if let Some(skip_to_match) = captures.name("SKIPTO") { } else if let Some(skip_to_match) = captures.name("SKIPTO") {
let pattern = match Regex::new(skip_to_match.as_str()) { let pattern = Regex::new(skip_to_match.as_str())
Err(_) => { .map_err(|_| CsplitError::InvalidPattern(arg.to_string()))?;
return Err(CsplitError::InvalidPattern(arg.to_string()));
}
Ok(reg) => reg,
};
patterns.push(Pattern::SkipToMatch(pattern, offset, execute_ntimes)); patterns.push(Pattern::SkipToMatch(pattern, offset, execute_ntimes));
} }
} else if let Ok(line_number) = arg.parse::<usize>() { } else if let Ok(line_number) = arg.parse::<usize>() {

View file

@ -33,13 +33,13 @@ impl SplitName {
// get the prefix // get the prefix
let prefix = prefix_opt.unwrap_or_else(|| "xx".to_string()); let prefix = prefix_opt.unwrap_or_else(|| "xx".to_string());
// the width for the split offset // the width for the split offset
let n_digits = match n_digits_opt { let n_digits = n_digits_opt
None => 2, .map(|opt| {
Some(opt) => match opt.parse::<usize>() { opt.parse::<usize>()
Ok(digits) => digits, .map_err(|_| CsplitError::InvalidNumber(opt))
Err(_) => return Err(CsplitError::InvalidNumber(opt)), })
}, .transpose()?
}; .unwrap_or(2);
// translate the custom format into a function // translate the custom format into a function
let fn_split_name: Box<dyn Fn(usize) -> String> = match format_opt { let fn_split_name: Box<dyn Fn(usize) -> String> = match format_opt {
None => Box::new(move |n: usize| -> String { None => Box::new(move |n: usize| -> String {

View file

@ -1,9 +1,9 @@
// This file is part of the uutils coreutils package. // * This file is part of the uutils coreutils package.
// // *
// (c) Derek Chiang <derekchiang93@gmail.com> // * (c) Derek Chiang <derekchiang93@gmail.com>
// // *
// For the full copyright and license information, please view the LICENSE // * For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // * file that was distributed with this source code.
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
@ -12,6 +12,7 @@ use chrono::prelude::DateTime;
use chrono::Local; use chrono::Local;
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg};
use std::collections::HashSet; use std::collections::HashSet;
use std::convert::TryFrom;
use std::env; use std::env;
use std::fs; use std::fs;
#[cfg(not(windows))] #[cfg(not(windows))]
@ -28,6 +29,7 @@ use std::os::windows::io::AsRawHandle;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::time::{Duration, UNIX_EPOCH}; use std::time::{Duration, UNIX_EPOCH};
use uucore::parse_size::{parse_size, ParseSizeError};
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
#[cfg(windows)] #[cfg(windows)]
use winapi::shared::minwindef::{DWORD, LPVOID}; use winapi::shared::minwindef::{DWORD, LPVOID};
@ -44,7 +46,7 @@ mod options {
pub const NULL: &str = "0"; pub const NULL: &str = "0";
pub const ALL: &str = "all"; pub const ALL: &str = "all";
pub const APPARENT_SIZE: &str = "apparent-size"; 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 BYTES: &str = "b";
pub const TOTAL: &str = "c"; pub const TOTAL: &str = "c";
pub const MAX_DEPTH: &str = "d"; pub const MAX_DEPTH: &str = "d";
@ -227,65 +229,24 @@ fn get_file_info(path: &Path) -> Option<FileInfo> {
result result
} }
fn unit_string_to_number(s: &str) -> Option<u64> { fn read_block_size(s: Option<&str>) -> usize {
let mut offset = 0; if let Some(s) = s {
let mut s_chars = s.chars().rev(); parse_size(s)
.unwrap_or_else(|e| crash!(1, "{}", format_error_message(e, s, options::BLOCK_SIZE)))
let (mut ch, multiple) = match s_chars.next()? { } else {
'B' | 'b' => ('B', 1000u64),
ch => (ch, 1024u64),
};
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"] { for env_var in &["DU_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] {
let env_size = env::var(env_var).ok(); if let Ok(env_size) = env::var(env_var) {
if let Some(quantity) = translate_to_pure_number(&env_size.as_deref()) { if let Ok(v) = parse_size(&env_size) {
return quantity; return v;
}
} }
} }
if env::var("POSIXLY_CORRECT").is_ok() { if env::var("POSIXLY_CORRECT").is_ok() {
512 512
} else { } else {
1024 1024
} }
} }
}
} }
// this takes `my_stat` to avoid having to stat files multiple times. // this takes `my_stat` to avoid having to stat files multiple times.
@ -449,7 +410,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.arg( .arg(
Arg::with_name(options::BLOCK_SIZE) Arg::with_name(options::BLOCK_SIZE)
.short("B") .short("B")
.long("block-size") .long(options::BLOCK_SIZE)
.value_name("SIZE") .value_name("SIZE")
.help( .help(
"scale sizes by SIZE before printing them. \ "scale sizes by SIZE before printing them. \
@ -623,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) { let multiplier: u64 = if matches.is_present(options::SI) {
1000 1000
@ -759,31 +720,27 @@ Try '{} --help' for more information.",
0 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)] #[cfg(test)]
mod test_du { mod test_du {
#[allow(unused_imports)] #[allow(unused_imports)]
use super::*; 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] #[test]
fn test_read_block_size() { fn test_read_block_size() {
let test_data = [ let test_data = [
(Some("10".to_string()), 10), (Some("1024".to_string()), 1024),
(Some("K".to_string()), 1024),
(None, 1024), (None, 1024),
(Some("BAD_STRING".to_string()), 1024),
]; ];
for it in test_data.iter() { for it in test_data.iter() {
assert_eq!(read_block_size(it.0.as_deref()), it.1); assert_eq!(read_block_size(it.0.as_deref()), it.1);

16
src/uu/env/src/env.rs vendored
View file

@ -82,13 +82,10 @@ fn load_config_file(opts: &mut Options) -> Result<(), i32> {
Ini::load_from_file(file) Ini::load_from_file(file)
}; };
let conf = match conf { let conf = conf.map_err(|error| {
Ok(config) => config,
Err(error) => {
eprintln!("env: error: \"{}\": {}", file, error); eprintln!("env: error: \"{}\": {}", file, error);
return Err(1); 1
} })?;
};
for (_, prop) in &conf { for (_, prop) in &conf {
// ignore all INI section lines (treat them as comments) // 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 // FIXME: this should just use execvp() (no fork()) on Unix-like systems
match Command::new(&*prog).args(args).status() { match Command::new(&*prog).args(args).status() {
Ok(exit) => { Ok(exit) if !exit.success() => return Err(exit.code().unwrap()),
if !exit.success() {
return Err(exit.code().unwrap());
}
}
Err(ref err) if err.kind() == io::ErrorKind::NotFound => return Err(127), Err(ref err) if err.kind() == io::ErrorKind::NotFound => return Err(127),
Err(_) => return Err(126), Err(_) => return Err(126),
Ok(_) => (),
} }
} else { } else {
// no program provided, so just dump all env vars to stdout // no program provided, so just dump all env vars to stdout

View file

@ -160,10 +160,8 @@ impl AstNode {
if let AstNode::Node { operands, .. } = self { if let AstNode::Node { operands, .. } = self {
let mut out = Vec::with_capacity(operands.len()); let mut out = Vec::with_capacity(operands.len());
for operand in operands { for operand in operands {
match operand.evaluate() { let value = operand.evaluate()?;
Ok(value) => out.push(value), out.push(value);
Err(reason) => return Err(reason),
}
} }
Ok(out) Ok(out)
} else { } else {
@ -252,10 +250,8 @@ fn maybe_ast_node(
) -> Result<Box<AstNode>, String> { ) -> Result<Box<AstNode>, String> {
let mut operands = Vec::with_capacity(arity); let mut operands = Vec::with_capacity(arity);
for _ in 0..arity { for _ in 0..arity {
match ast_from_rpn(rpn) { let operand = ast_from_rpn(rpn)?;
Err(reason) => return Err(reason), operands.push(operand);
Ok(operand) => operands.push(operand),
}
} }
operands.reverse(); operands.reverse();
Ok(AstNode::new_node(token_idx, op_type, operands)) Ok(AstNode::new_node(token_idx, op_type, operands))
@ -399,10 +395,12 @@ fn move_till_match_paren(
op_stack: &mut TokenStack, op_stack: &mut TokenStack,
) -> Result<(), String> { ) -> Result<(), String> {
loop { loop {
match op_stack.pop() { let op = op_stack
None => return Err("syntax error (Mismatched close-parenthesis)".to_string()), .pop()
Some((_, Token::ParOpen)) => return Ok(()), .ok_or_else(|| "syntax error (Mismatched close-parenthesis)".to_string())?;
Some(other) => out_stack.push(other), 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> { fn operator_match(values: &[String]) -> Result<String, String> {
assert!(values.len() == 2); assert!(values.len() == 2);
let re = match Regex::with_options(&values[1], RegexOptions::REGEX_OPTION_NONE, Syntax::grep()) let re = Regex::with_options(&values[1], RegexOptions::REGEX_OPTION_NONE, Syntax::grep())
{ .map_err(|err| err.description().to_string())?;
Ok(m) => m, Ok(if re.captures_len() > 0 {
Err(err) => return Err(err.description().to_string()), re.captures(&values[0])
}; .map(|captures| captures.at(1).unwrap())
if re.captures_len() > 0 { .unwrap_or("")
Ok(match re.captures(&values[0]) { .to_string()
Some(captures) => captures.at(1).unwrap().to_string(),
None => "".to_string(),
})
} else { } else {
Ok(match re.find(&values[0]) { re.find(&values[0])
Some((start, end)) => (end - start).to_string(), .map_or("0".to_string(), |(start, end)| (end - start).to_string())
None => "0".to_string(),
}) })
}
} }
fn prefix_operator_length(values: &[String]) -> String { fn prefix_operator_length(values: &[String]) -> String {

View file

@ -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 // spell-checker:ignore (vars) zlines
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg};
@ -75,7 +80,7 @@ fn app<'a>() -> App<'a, 'a> {
.arg( .arg(
Arg::with_name(options::QUIET_NAME) Arg::with_name(options::QUIET_NAME)
.short("q") .short("q")
.long("--quiet") .long("quiet")
.visible_alias("silent") .visible_alias("silent")
.help("never print headers giving file names") .help("never print headers giving file names")
.overrides_with_all(&[options::VERBOSE_NAME, options::QUIET_NAME]), .overrides_with_all(&[options::VERBOSE_NAME, options::QUIET_NAME]),
@ -108,12 +113,7 @@ where
{ {
match parse::parse_num(src) { match parse::parse_num(src) {
Ok((n, last)) => Ok((closure(n), last)), Ok((n, last)) => Ok((closure(n), last)),
Err(reason) => match reason { Err(e) => Err(e.to_string()),
parse::ParseError::Syntax => Err(format!("'{}'", src)),
parse::ParseError::Overflow => {
Err(format!("'{}': Value too large for defined datatype", src))
}
},
} }
} }
@ -176,19 +176,11 @@ impl HeadOptions {
options.zeroed = matches.is_present(options::ZERO_NAME); options.zeroed = matches.is_present(options::ZERO_NAME);
let mode_and_from_end = if let Some(v) = matches.value_of(options::BYTES_NAME) { let mode_and_from_end = if let Some(v) = matches.value_of(options::BYTES_NAME) {
match parse_mode(v, Modes::Bytes) { parse_mode(v, Modes::Bytes)
Ok(v) => v, .map_err(|err| format!("invalid number of bytes: {}", err))?
Err(err) => {
return Err(format!("invalid number of bytes: {}", err));
}
}
} else if let Some(v) = matches.value_of(options::LINES_NAME) { } else if let Some(v) = matches.value_of(options::LINES_NAME) {
match parse_mode(v, Modes::Lines) { parse_mode(v, Modes::Lines)
Ok(v) => v, .map_err(|err| format!("invalid number of lines: {}", err))?
Err(err) => {
return Err(format!("invalid number of lines: {}", err));
}
}
} else { } else {
(Modes::Lines(10), false) (Modes::Lines(10), false)
}; };
@ -474,7 +466,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let args = match HeadOptions::get_from(args) { let args = match HeadOptions::get_from(args) {
Ok(o) => o, Ok(o) => o,
Err(s) => { Err(s) => {
crash!(EXIT_FAILURE, "head: {}", s); crash!(EXIT_FAILURE, "{}", s);
} }
}; };
match uu_head(&args) { match uu_head(&args) {

View file

@ -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 std::ffi::OsString;
use uucore::parse_size::{parse_size, ParseSizeError};
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
pub enum ParseError { 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, /// Parses an -c or -n argument,
/// the bool specifies whether to read from the end /// the bool specifies whether to read from the end
pub fn parse_num(src: &str) -> Result<(usize, bool), ParseError> { pub fn parse_num(src: &str) -> Result<(usize, bool), ParseSizeError> {
let mut num_start = 0; let mut size_string = src.trim();
let mut chars = src.char_indices(); let mut all_but_last = false;
let (mut chars, all_but_last) = match chars.next() {
Some((_, c)) => { 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 == '-' { if c == '-' {
num_start += 1; all_but_last = true;
(chars, true) }
}
} else { } else {
(src.char_indices(), false) return Err(ParseSizeError::ParseFailure(src.to_string()));
}
}
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;
}
} }
let num = if num_count > 0 { parse_size(size_string).map(|n| (n, all_but_last))
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),
}
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -195,44 +133,6 @@ mod tests {
Some(Ok(src.iter().map(|s| s.to_string()).collect())) Some(Ok(src.iter().map(|s| s.to_string()).collect()))
} }
#[test] #[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() { fn test_parse_numbers_obsolete() {
assert_eq!(obsolete("-5"), obsolete_result(&["-n", "5"])); assert_eq!(obsolete("-5"), obsolete_result(&["-n", "5"]));
assert_eq!(obsolete("-100"), obsolete_result(&["-n", "100"])); assert_eq!(obsolete("-100"), obsolete_result(&["-n", "100"]));

View file

@ -299,29 +299,17 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
let considering_dir: bool = MainFunction::Directory == main_function; let considering_dir: bool = MainFunction::Directory == main_function;
let specified_mode: Option<u32> = if matches.is_present(OPT_MODE) { let specified_mode: Option<u32> = if matches.is_present(OPT_MODE) {
match matches.value_of(OPT_MODE) { let x = matches.value_of(OPT_MODE).ok_or(1)?;
Some(x) => match mode::parse(x, considering_dir) { Some(mode::parse(x, considering_dir).map_err(|err| {
Ok(y) => Some(y),
Err(err) => {
show_error!("Invalid mode string: {}", err); show_error!("Invalid mode string: {}", err);
return Err(1); 1
} })?)
},
None => {
return Err(1);
}
}
} else { } else {
None None
}; };
let backup_suffix = if matches.is_present(OPT_SUFFIX) { let backup_suffix = if matches.is_present(OPT_SUFFIX) {
match matches.value_of(OPT_SUFFIX) { matches.value_of(OPT_SUFFIX).ok_or(1)?
Some(x) => x,
None => {
return Err(1);
}
}
} else { } else {
"~" "~"
}; };

View file

@ -27,7 +27,6 @@ use uucore::fs::{canonicalize, CanonicalizeMode};
pub struct Settings { pub struct Settings {
overwrite: OverwriteMode, overwrite: OverwriteMode,
backup: BackupMode, backup: BackupMode,
force: bool,
suffix: String, suffix: String,
symbolic: bool, symbolic: bool,
relative: bool, relative: bool,
@ -54,7 +53,7 @@ pub enum BackupMode {
fn get_usage() -> String { fn get_usage() -> String {
format!( 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 (2nd form)
{0} [OPTION]... TARGET... DIRECTORY (3rd form) {0} [OPTION]... TARGET... DIRECTORY (3rd form)
{0} [OPTION]... -t DIRECTORY TARGET... (4th form)", {0} [OPTION]... -t DIRECTORY TARGET... (4th form)",
@ -64,7 +63,7 @@ fn get_usage() -> String {
fn get_long_usage() -> String { fn get_long_usage() -> String {
String::from( 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 2nd form, create a link to TARGET in the current directory.
In the 3rd and 4th forms, create links to each TARGET in DIRECTORY. In the 3rd and 4th forms, create links to each TARGET in DIRECTORY.
Create hard links by default, symbolic links with --symbolic. Create hard links by default, symbolic links with --symbolic.
@ -140,7 +139,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.short("n") .short("n")
.long(options::NO_DEREFERENCE) .long(options::NO_DEREFERENCE)
.help( .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", symbolic link to a directory",
), ),
) )
@ -177,13 +176,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
Arg::with_name(options::NO_TARGET_DIRECTORY) Arg::with_name(options::NO_TARGET_DIRECTORY)
.short("T") .short("T")
.long(options::NO_TARGET_DIRECTORY) .long(options::NO_TARGET_DIRECTORY)
.help("treat LINK_executable!() as a normal file always"), .help("treat LINK_NAME as a normal file always"),
) )
.arg( .arg(
Arg::with_name(options::RELATIVE) Arg::with_name(options::RELATIVE)
.short("r") .short("r")
.long(options::RELATIVE) .long(options::RELATIVE)
.help("create symbolic links relative to link location"), .help("create symbolic links relative to link location")
.requires(options::SYMBOLIC),
) )
.arg( .arg(
Arg::with_name(options::VERBOSE) Arg::with_name(options::VERBOSE)
@ -242,7 +242,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let settings = Settings { let settings = Settings {
overwrite: overwrite_mode, overwrite: overwrite_mode,
backup: backup_mode, backup: backup_mode,
force: matches.is_present(options::FORCE),
suffix: backup_suffix.to_string(), suffix: backup_suffix.to_string(),
symbolic: matches.is_present(options::SYMBOLIC), symbolic: matches.is_present(options::SYMBOLIC),
relative: matches.is_present(options::RELATIVE), relative: matches.is_present(options::RELATIVE),
@ -311,7 +310,8 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings)
let mut all_successful = true; let mut all_successful = true;
for srcpath in files.iter() { for srcpath in files.iter() {
let targetpath = if settings.no_dereference && settings.force { let targetpath =
if settings.no_dereference && matches!(settings.overwrite, OverwriteMode::Force) {
// In that case, we don't want to do link resolution // In that case, we don't want to do link resolution
// We need to clean the target // We need to clean the target
if is_symlink(target_dir) { if is_symlink(target_dir) {
@ -372,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>> { fn relative_path<'a>(src: &Path, dst: &Path) -> Result<Cow<'a, Path>> {
let src_abs = canonicalize(src, CanonicalizeMode::Normal)?; 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 let suffix_pos = src_abs
.components() .components()
.zip(dst_abs.components()) .zip(dst_abs.components())
@ -422,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 { if settings.symbolic {
symlink(&source, dst)?; symlink(&source, dst)?;
} else { } else {

View file

@ -11,7 +11,6 @@
extern crate uucore; extern crate uucore;
use std::{ use std::{
convert::TryInto,
fs::File, fs::File,
io::{stdin, stdout, BufReader, Read, Stdout, Write}, io::{stdin, stdout, BufReader, Read, Stdout, Write},
path::Path, path::Path,
@ -210,40 +209,16 @@ fn reset_term(_: &mut usize) {}
fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>, silent: bool) { fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>, silent: bool) {
let (cols, rows) = terminal::size().unwrap(); let (cols, rows) = terminal::size().unwrap();
let lines = break_buff(buff, usize::from(cols)); let lines = break_buff(buff, usize::from(cols));
let line_count: u16 = lines.len().try_into().unwrap();
let mut upper_mark = 0; let mut pager = Pager::new(rows as usize, lines, next_file, silent);
let mut lines_left = line_count.saturating_sub(upper_mark + rows); pager.draw(stdout, false);
let mut wrong_key = false; if pager.should_close() {
draw(
&mut upper_mark,
rows,
&mut stdout,
lines.clone(),
line_count,
next_file,
silent,
wrong_key,
);
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; return;
} else {
to_be_done = true;
}
} }
loop { loop {
let mut wrong_key = false;
if event::poll(Duration::from_millis(10)).unwrap() { if event::poll(Duration::from_millis(10)).unwrap() {
wrong_key = false;
match event::read().unwrap() { match event::read().unwrap() {
Event::Key(KeyEvent { Event::Key(KeyEvent {
code: KeyCode::Char('q'), code: KeyCode::Char('q'),
@ -264,66 +239,127 @@ fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>, silent: bo
code: KeyCode::Char(' '), code: KeyCode::Char(' '),
modifiers: KeyModifiers::NONE, modifiers: KeyModifiers::NONE,
}) => { }) => {
upper_mark = upper_mark.saturating_add(rows.saturating_sub(1)); pager.page_down();
} }
Event::Key(KeyEvent { Event::Key(KeyEvent {
code: KeyCode::Up, code: KeyCode::Up,
modifiers: KeyModifiers::NONE, modifiers: KeyModifiers::NONE,
}) => { }) => {
upper_mark = upper_mark.saturating_sub(rows.saturating_sub(1)); pager.page_up();
} }
_ => { _ => {
wrong_key = true; wrong_key = true;
} }
} }
lines_left = line_count.saturating_sub(upper_mark + rows);
draw(
&mut upper_mark,
rows,
&mut stdout,
lines.clone(),
line_count,
next_file,
silent,
wrong_key,
);
if lines_left == 0 { pager.draw(stdout, wrong_key);
if to_be_done || is_last { if pager.should_close() {
return; return;
} }
to_be_done = true;
}
} }
} }
} }
#[allow(clippy::too_many_arguments)] struct Pager<'a> {
fn draw( // The current line at the top of the screen
upper_mark: &mut u16, upper_mark: usize,
rows: u16, // The number of rows that fit on the screen
mut stdout: &mut std::io::Stdout, content_rows: usize,
lines: Vec<String>, lines: Vec<String>,
lc: u16, next_file: Option<&'a str>,
next_file: Option<&str>, line_count: usize,
close_on_down: bool,
silent: bool, silent: bool,
wrong_key: bool, }
) {
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(); execute!(stdout, terminal::Clear(terminal::ClearType::CurrentLine)).unwrap();
let (up_mark, lower_mark) = calc_range(*upper_mark, rows, lc); let displayed_lines = self
// Reduce the row by 1 for the prompt .lines
let displayed_lines = lines
.iter() .iter()
.skip(up_mark.into()) .skip(self.upper_mark)
.take(usize::from(rows.saturating_sub(1))); .take(self.content_rows);
for line in displayed_lines { for line in displayed_lines {
stdout stdout
.write_all(format!("\r{}\n", line).as_bytes()) .write_all(format!("\r{}\n", line).as_bytes())
.unwrap(); .unwrap();
} }
make_prompt_and_flush(&mut stdout, lower_mark, lc, next_file, silent, wrong_key); }
*upper_mark = up_mark;
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();
}
} }
// Break the lines on the cols of the terminal // Break the lines on the cols of the terminal
@ -364,69 +400,11 @@ fn break_line(line: &str, cols: usize) -> Vec<String> {
lines 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>,
silent: bool,
wrong_key: bool,
) {
let status_inner = 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
)
};
let status = format!("--More--({})", status_inner);
let banner = match (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();
stdout.flush().unwrap();
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{break_line, calc_range}; use super::break_line;
use unicode_width::UnicodeWidthStr; 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] #[test]
fn test_break_lines_long() { fn test_break_lines_long() {
let mut test_string = String::with_capacity(100); let mut test_string = String::with_capacity(100);

View file

@ -247,7 +247,7 @@ fn nl<T: Read>(reader: &mut BufReader<T>, settings: &Settings) {
let mut line_filter: fn(&str, &regex::Regex) -> bool = pass_regex; let mut line_filter: fn(&str, &regex::Regex) -> bool = pass_regex;
for mut l in reader.lines().map(|r| r.unwrap()) { for mut l in reader.lines().map(|r| r.unwrap()) {
// Sanitize the string. We want to print the newline ourselves. // Sanitize the string. We want to print the newline ourselves.
if l.chars().last() == Some('\n') { if l.ends_with('\n') {
l.pop(); l.pop();
} }
// Next we iterate through the individual chars to see if this // Next we iterate through the individual chars to see if this

View file

@ -43,6 +43,7 @@ use crate::partialreader::*;
use crate::peekreader::*; use crate::peekreader::*;
use crate::prn_char::format_ascii_dump; use crate::prn_char::format_ascii_dump;
use clap::{self, crate_version, AppSettings, Arg, ArgMatches}; use clap::{self, crate_version, AppSettings, Arg, ArgMatches};
use uucore::parse_size::ParseSizeError;
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
const PEEK_BUFFER_SIZE: usize = 4; // utf-8 can be 4 bytes 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) { let mut skip_bytes = matches.value_of(options::SKIP_BYTES).map_or(0, |s| {
None => 0, parse_number_of_bytes(s).unwrap_or_else(|e| {
Some(s) => match parse_number_of_bytes(s) { crash!(1, "{}", format_error_message(e, s, options::SKIP_BYTES))
Ok(i) => i, })
Err(_) => { });
return Err(format!("Invalid argument --skip-bytes={}", s));
}
},
};
let mut label: Option<usize> = None; let mut label: Option<usize> = None;
let input_strings = match parse_inputs(&matches) { let parsed_input = parse_inputs(&matches).map_err(|e| format!("Invalid inputs: {}", e))?;
Ok(CommandLineInputs::FileNames(v)) => v, let input_strings = match parsed_input {
Ok(CommandLineInputs::FileAndOffset((f, s, l))) => { CommandLineInputs::FileNames(v) => v,
CommandLineInputs::FileAndOffset((f, s, l)) => {
skip_bytes = s; skip_bytes = s;
label = l; label = l;
vec![f] vec![f]
} }
Err(e) => {
return Err(format!("Invalid inputs: {}", e));
}
}; };
let formats = match parse_format_flags(&args) { let formats = parse_format_flags(&args)?;
Ok(f) => f,
Err(e) => {
return Err(e);
}
};
let mut line_bytes = match matches.value_of(options::WIDTH) { let mut line_bytes = matches.value_of(options::WIDTH).map_or(16, |s| {
None => 16, if matches.occurrences_of(options::WIDTH) == 0 {
Some(_) if matches.occurrences_of(options::WIDTH) == 0 => 16, return 16;
Some(s) => s.parse::<usize>().unwrap_or(0),
}; };
parse_number_of_bytes(s)
.unwrap_or_else(|e| crash!(1, "{}", format_error_message(e, s, options::WIDTH)))
});
let min_bytes = formats.iter().fold(1, |max, next| { let min_bytes = formats.iter().fold(1, |max, next| {
cmp::max(max, next.formatter_item_info.byte_size) 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 output_duplicates = matches.is_present(options::OUTPUT_DUPLICATES);
let read_bytes = match matches.value_of(options::READ_BYTES) { let read_bytes = matches.value_of(options::READ_BYTES).map(|s| {
None => None, parse_number_of_bytes(s).unwrap_or_else(|e| {
Some(s) => match parse_number_of_bytes(s) { crash!(1, "{}", format_error_message(e, s, options::READ_BYTES))
Ok(i) => Some(i), })
Err(_) => { });
return Err(format!("Invalid argument --read-bytes={}", s));
}
},
};
let radix = match matches.value_of(options::ADDRESS_RADIX) { let radix = match matches.value_of(options::ADDRESS_RADIX) {
None => Radix::Octal, None => Radix::Octal,
@ -263,7 +252,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.short("S") .short("S")
.long(options::STRINGS) .long(options::STRINGS)
.help( .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.", BYTES is not specified.",
) )
.default_value("3") .default_value("3")
@ -453,8 +442,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let od_options = match OdOptions::new(clap_matches, args) { let od_options = match OdOptions::new(clap_matches, args) {
Err(s) => { Err(s) => {
show_usage_error!("{}", s); crash!(1, "{}", s);
return 1;
} }
Ok(o) => o, Ok(o) => o,
}; };
@ -636,3 +624,13 @@ fn open_input_peek_reader(
let pr = PartialReader::new(mf, skip_bytes, read_bytes); let pr = PartialReader::new(mf, skip_bytes, read_bytes);
PeekReader::new(pr) 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),
}
}

View file

@ -108,10 +108,8 @@ pub fn parse_format_flags(args: &[String]) -> Result<Vec<ParsedFormatterItemInfo
for arg in arg_iter { for arg in arg_iter {
if expect_type_string { if expect_type_string {
match parse_type_string(arg) { let v = parse_type_string(arg)?;
Ok(v) => formats.extend(v.into_iter()), formats.extend(v.into_iter());
Err(e) => return Err(e),
}
expect_type_string = false; expect_type_string = false;
} else if arg.starts_with("--") { } else if arg.starts_with("--") {
if arg.len() == 2 { if arg.len() == 2 {
@ -119,10 +117,8 @@ pub fn parse_format_flags(args: &[String]) -> Result<Vec<ParsedFormatterItemInfo
} }
if arg.starts_with("--format=") { if arg.starts_with("--format=") {
let params: String = arg.chars().skip_while(|c| *c != '=').skip(1).collect(); let params: String = arg.chars().skip_while(|c| *c != '=').skip(1).collect();
match parse_type_string(&params) { let v = parse_type_string(&params)?;
Ok(v) => formats.extend(v.into_iter()), formats.extend(v.into_iter());
Err(e) => return Err(e),
}
} }
if arg == "--format" { if arg == "--format" {
expect_type_string = true; expect_type_string = true;
@ -145,10 +141,8 @@ pub fn parse_format_flags(args: &[String]) -> Result<Vec<ParsedFormatterItemInfo
} }
} }
if !format_spec.is_empty() { if !format_spec.is_empty() {
match parse_type_string(&format_spec) { let v = parse_type_string(&format_spec)?;
Ok(v) => formats.extend(v.into_iter()), formats.extend(v.into_iter());
Err(e) => return Err(e),
}
expect_type_string = false; expect_type_string = false;
} }
} }

View file

@ -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 start = 0;
let mut len = s.len(); let mut len = s.len();
let mut radix = 10; let mut radix = 16;
let mut multiply = 1; let mut multiply = 1;
if s.starts_with("0x") || s.starts_with("0X") { if s.starts_with("0x") || s.starts_with("0X") {
start = 2; start = 2;
radix = 16;
} else if s.starts_with('0') { } else if s.starts_with('0') {
radix = 8; radix = 8;
} else {
return parse_size(&s[start..]);
} }
let mut ends_with = s.chars().rev(); 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, Some('P') => 1000 * 1000 * 1000 * 1000 * 1000,
#[cfg(target_pointer_width = "64")] #[cfg(target_pointer_width = "64")]
Some('E') => 1000 * 1000 * 1000 * 1000 * 1000 * 1000, 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) { let factor = match usize::from_str_radix(&s[start..len], radix) {
Ok(i) => Ok(i * multiply), Ok(f) => f,
Err(_) => Err("parse failed"), Err(e) => return Err(ParseSizeError::ParseFailure(e.to_string())),
} };
} factor
.checked_mul(multiply)
#[allow(dead_code)] .ok_or_else(|| ParseSizeError::SizeTooBig(s.to_string()))
fn parse_number_of_bytes_str(s: &str) -> Result<usize, &'static str> {
parse_number_of_bytes(&String::from(s))
} }
#[test] #[test]
fn test_parse_number_of_bytes() { 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 // octal input
assert_eq!(8, parse_number_of_bytes_str("010").unwrap()); assert_eq!(8, parse_number_of_bytes("010").unwrap());
assert_eq!(8 * 512, parse_number_of_bytes_str("010b").unwrap()); assert_eq!(8 * 512, parse_number_of_bytes("010b").unwrap());
assert_eq!(8 * 1024, parse_number_of_bytes_str("010k").unwrap()); assert_eq!(8 * 1024, parse_number_of_bytes("010k").unwrap());
assert_eq!(8 * 1048576, parse_number_of_bytes_str("010m").unwrap()); assert_eq!(8 * 1_048_576, parse_number_of_bytes("010m").unwrap());
// hex input // hex input
assert_eq!(15, parse_number_of_bytes_str("0xf").unwrap()); assert_eq!(15, parse_number_of_bytes("0xf").unwrap());
assert_eq!(15, parse_number_of_bytes_str("0XF").unwrap()); assert_eq!(15, parse_number_of_bytes("0XF").unwrap());
assert_eq!(27, parse_number_of_bytes_str("0x1b").unwrap()); assert_eq!(27, parse_number_of_bytes("0x1b").unwrap());
assert_eq!(16 * 1024, parse_number_of_bytes_str("0x10k").unwrap()); assert_eq!(16 * 1024, parse_number_of_bytes("0x10k").unwrap());
assert_eq!(16 * 1048576, parse_number_of_bytes_str("0x10m").unwrap()); assert_eq!(16 * 1_048_576, parse_number_of_bytes("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()
);
} }

View file

@ -36,16 +36,15 @@ impl<R: Read> Read for PartialReader<R> {
while self.skip > 0 { while self.skip > 0 {
let skip_count = cmp::min(self.skip, MAX_SKIP_BUFFER); let skip_count = cmp::min(self.skip, MAX_SKIP_BUFFER);
match self.inner.read(&mut bytes[..skip_count]) { match self.inner.read(&mut bytes[..skip_count])? {
Ok(0) => { 0 => {
// this is an error as we still have more to skip // this is an error as we still have more to skip
return Err(io::Error::new( return Err(io::Error::new(
io::ErrorKind::UnexpectedEof, io::ErrorKind::UnexpectedEof,
"tried to skip past end of input", "tried to skip past end of input",
)); ));
} }
Ok(n) => self.skip -= n, n => self.skip -= n,
Err(e) => return Err(e),
} }
} }
} }

View file

@ -671,8 +671,7 @@ fn build_options(
if start_page > end_page { if start_page > end_page {
return Err(PrError::EncounteredErrors(format!( return Err(PrError::EncounteredErrors(format!(
"invalid --pages argument '{}:{}'", "invalid --pages argument '{}:{}'",
start_page, start_page, end_page
end_page
))); )));
} }
} }
@ -999,8 +998,8 @@ fn mpr(paths: &[String], options: &OutputOptions) -> Result<i32, PrError> {
for (_key, file_line_group) in file_line_groups.into_iter() { for (_key, file_line_group) in file_line_groups.into_iter() {
for file_line in file_line_group { for file_line in file_line_group {
if file_line.line_content.is_err() { if let Err(e) = file_line.line_content {
return Err(file_line.line_content.unwrap_err().into()); return Err(e.into());
} }
let new_page_number = file_line.page_number; let new_page_number = file_line.page_number;
if page_counter != new_page_number { if page_counter != new_page_number {

View file

@ -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> { fn remove_dir(path: &Path, ignore: bool, verbose: bool) -> Result<(), i32> {
let mut read_dir = match fs::read_dir(path) { let mut read_dir = fs::read_dir(path).map_err(|e| {
Ok(m) => m, if e.raw_os_error() == Some(ENOTDIR) {
Err(e) if e.raw_os_error() == Some(ENOTDIR) => {
show_error!("failed to remove '{}': Not a directory", path.display()); show_error!("failed to remove '{}': Not a directory", path.display());
return Err(1); } else {
}
Err(e) => {
show_error!("reading directory '{}': {}", path.display(), e); show_error!("reading directory '{}': {}", path.display(), e);
return Err(1);
} }
}; 1
})?;
let mut r = Ok(()); let mut r = Ok(());

View file

@ -285,14 +285,12 @@ fn parse_range(input_range: &str) -> Result<(usize, usize), String> {
if split.len() != 2 { if split.len() != 2 {
Err(format!("invalid input range: '{}'", input_range)) Err(format!("invalid input range: '{}'", input_range))
} else { } else {
let begin = match split[0].parse::<usize>() { let begin = split[0]
Ok(m) => m, .parse::<usize>()
Err(_) => return Err(format!("invalid input range: '{}'", split[0])), .map_err(|_| format!("invalid input range: '{}'", split[0]))?;
}; let end = split[1]
let end = match split[1].parse::<usize>() { .parse::<usize>()
Ok(m) => m, .map_err(|_| format!("invalid input range: '{}'", split[1]))?;
Err(_) => return Err(format!("invalid input range: '{}'", split[1])),
};
Ok((begin, end + 1)) Ok((begin, end + 1))
} }
} }

View file

@ -16,7 +16,7 @@ path = "src/sleep.rs"
[dependencies] [dependencies]
clap = "2.33" 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" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]] [[bin]]

View file

@ -43,6 +43,7 @@ use std::ops::Range;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use uucore::parse_size::{parse_size, ParseSizeError};
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
const NAME: &str = "sort"; const NAME: &str = "sort";
@ -172,32 +173,29 @@ pub struct GlobalSettings {
} }
impl GlobalSettings { impl GlobalSettings {
/// Interpret this `&str` as a number with an optional trailing si unit. /// Parse a SIZE string into a number of bytes.
/// /// A size string comprises an integer and an optional unit.
/// If there is no trailing si unit, the implicit unit is K. /// The unit may be k, K, m, M, g, G, t, T, P, E, Z, Y (powers of 1024), or b which is 1.
/// The suffix B causes the number to be interpreted as a byte count. /// Default is K.
fn parse_byte_count(input: &str) -> usize { fn parse_byte_count(input: &str) -> Result<usize, ParseSizeError> {
const SI_UNITS: &[char] = &['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']; // 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(); if size_string.ends_with(|c: char| ALLOW_LIST.contains(&c) || c.is_digit(10)) {
// b 1, K 1024 (default)
let (num_str, si_unit) = if size_string.ends_with(|c: char| c.is_digit(10)) {
if input.ends_with(|c: char| SI_UNITS.contains(&c.to_ascii_uppercase())) { size_string.push('K');
let mut chars = input.chars(); } else if size_string.ends_with('b') {
let si_suffix = chars.next_back().unwrap().to_ascii_uppercase(); size_string.pop();
let si_unit = SI_UNITS.iter().position(|&c| c == si_suffix).unwrap(); }
let num_str = chars.as_str(); parse_size(&size_string)
(num_str, si_unit)
} else { } else {
(input, 1) Err(ParseSizeError::ParseFailure("invalid suffix".to_string()))
}; }
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))
} }
fn out_writer(&self) -> BufWriter<Box<dyn Write>> { fn out_writer(&self) -> BufWriter<Box<dyn Write>> {
@ -1217,8 +1215,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
settings.buffer_size = matches settings.buffer_size = matches
.value_of(options::BUF_SIZE) .value_of(options::BUF_SIZE)
.map(GlobalSettings::parse_byte_count) .map_or(DEFAULT_BUF_SIZE, |s| {
.unwrap_or(DEFAULT_BUF_SIZE); GlobalSettings::parse_byte_count(s)
.unwrap_or_else(|e| crash!(2, "{}", format_error_message(e, s, options::BUF_SIZE)))
});
settings.tmp_dir = matches settings.tmp_dir = matches
.value_of(options::TMP_DIR) .value_of(options::TMP_DIR)
@ -1633,6 +1633,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)] #[cfg(test)]
mod tests { mod tests {
@ -1722,4 +1732,48 @@ mod tests {
// How big is a selection? Constant cost all lines pay when we need selections. // How big is a selection? Constant cost all lines pay when we need selections.
assert_eq!(std::mem::size_of::<Selection>(), 24); 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());
}
}
} }

View file

@ -13,11 +13,13 @@ extern crate uucore;
mod platform; mod platform;
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg};
use std::convert::TryFrom;
use std::env; use std::env;
use std::fs::File; use std::fs::File;
use std::io::{stdin, BufRead, BufReader, BufWriter, Read, Write}; use std::io::{stdin, BufRead, BufReader, BufWriter, Read, Write};
use std::path::Path; use std::path::Path;
use std::{char, fs::remove_file}; use std::{char, fs::remove_file};
use uucore::parse_size::parse_size;
static NAME: &str = "split"; static NAME: &str = "split";
@ -231,10 +233,9 @@ struct LineSplitter {
impl LineSplitter { impl LineSplitter {
fn new(settings: &Settings) -> LineSplitter { fn new(settings: &Settings) -> LineSplitter {
LineSplitter { LineSplitter {
lines_per_split: settings lines_per_split: settings.strategy_param.parse().unwrap_or_else(|_| {
.strategy_param crash!(1, "invalid number of lines: {}", settings.strategy_param)
.parse() }),
.unwrap_or_else(|e| crash!(1, "invalid number of lines: {}", e)),
} }
} }
} }
@ -276,40 +277,14 @@ struct ByteSplitter {
impl ByteSplitter { impl ByteSplitter {
fn new(settings: &Settings) -> ByteSplitter { fn new(settings: &Settings) -> ByteSplitter {
// These multipliers are the same as supported by GNU coreutils. let size_string = &settings.strategy_param;
let modifiers: Vec<(&str, u128)> = vec![ let size_num = match parse_size(size_string) {
("K", 1024u128), Ok(n) => n,
("M", 1024 * 1024), Err(e) => crash!(1, "invalid number of bytes: {}", e.to_string()),
("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));
ByteSplitter { ByteSplitter {
bytes_per_split: n * multiplier, bytes_per_split: u128::try_from(size_num).unwrap(),
} }
} }
} }

View file

@ -19,6 +19,7 @@ use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use tempfile::tempdir; use tempfile::tempdir;
use tempfile::TempDir; use tempfile::TempDir;
use uucore::parse_size::parse_size;
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
static ABOUT: &str = static ABOUT: &str =
@ -55,7 +56,7 @@ const STDBUF_INJECT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/libstdbuf
enum BufferType { enum BufferType {
Default, Default,
Line, Line,
Size(u64), Size(usize),
} }
struct ProgramOptions { struct ProgramOptions {
@ -104,41 +105,6 @@ fn preload_strings() -> (&'static str, &'static str) {
crash!(1, "Command not supported for this operating system!") 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> { fn check_option(matches: &ArgMatches, name: &str) -> Result<BufferType, ProgramOptionsError> {
match matches.value_of(name) { match matches.value_of(name) {
Some(value) => match value { Some(value) => match value {
@ -151,13 +117,10 @@ fn check_option(matches: &ArgMatches, name: &str) -> Result<BufferType, ProgramO
Ok(BufferType::Line) Ok(BufferType::Line)
} }
} }
x => { x => parse_size(x).map_or_else(
let size = match parse_size(x) { |e| crash!(125, "invalid mode {}", e),
Some(m) => m, |m| Ok(BufferType::Size(m)),
None => return Err(ProgramOptionsError(format!("invalid mode {}", x))), ),
};
Ok(BufferType::Size(size))
}
}, },
None => Ok(BufferType::Default), None => Ok(BufferType::Default),
} }

View file

@ -5,7 +5,6 @@
// * // *
// * For the full copyright and license information, please view the LICENSE // * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code. // * file that was distributed with this source code.
// *
// spell-checker:ignore (ToDO) seekable seek'd tail'ing ringbuffer ringbuf // spell-checker:ignore (ToDO) seekable seek'd tail'ing ringbuffer ringbuf
@ -21,19 +20,18 @@ use chunks::ReverseChunks;
use clap::{App, Arg}; use clap::{App, Arg};
use std::collections::VecDeque; use std::collections::VecDeque;
use std::error::Error;
use std::fmt; use std::fmt;
use std::fs::File; use std::fs::File;
use std::io::{stdin, stdout, BufRead, BufReader, Read, Seek, SeekFrom, Write}; use std::io::{stdin, stdout, BufRead, BufReader, Read, Seek, SeekFrom, Write};
use std::path::Path; use std::path::Path;
use std::thread::sleep; use std::thread::sleep;
use std::time::Duration; use std::time::Duration;
use uucore::parse_size::{parse_size, ParseSizeError};
use uucore::ringbuffer::RingBuffer; use uucore::ringbuffer::RingBuffer;
pub mod options { pub mod options {
pub mod verbosity { pub mod verbosity {
pub static QUIET: &str = "quiet"; pub static QUIET: &str = "quiet";
pub static SILENT: &str = "silent";
pub static VERBOSE: &str = "verbose"; pub static VERBOSE: &str = "verbose";
} }
pub static BYTES: &str = "bytes"; pub static BYTES: &str = "bytes";
@ -42,13 +40,12 @@ pub mod options {
pub static PID: &str = "pid"; pub static PID: &str = "pid";
pub static SLEEP_INT: &str = "sleep-interval"; pub static SLEEP_INT: &str = "sleep-interval";
pub static ZERO_TERM: &str = "zero-terminated"; pub static ZERO_TERM: &str = "zero-terminated";
pub static ARG_FILES: &str = "files";
} }
static ARG_FILES: &str = "files";
enum FilterMode { enum FilterMode {
Bytes(u64), Bytes(usize),
Lines(u64, u8), // (number of lines, delimiter) Lines(usize, u8), // (number of lines, delimiter)
} }
struct Settings { struct Settings {
@ -78,12 +75,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let app = App::new(executable!()) let app = App::new(executable!())
.version(crate_version!()) .version(crate_version!())
.about("output the last part of files") .about("output the last part of files")
// TODO: add usage
.arg( .arg(
Arg::with_name(options::BYTES) Arg::with_name(options::BYTES)
.short("c") .short("c")
.long(options::BYTES) .long(options::BYTES)
.takes_value(true) .takes_value(true)
.allow_hyphen_values(true) .allow_hyphen_values(true)
.overrides_with_all(&[options::BYTES, options::LINES])
.help("Number of bytes to print"), .help("Number of bytes to print"),
) )
.arg( .arg(
@ -98,6 +97,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.long(options::LINES) .long(options::LINES)
.takes_value(true) .takes_value(true)
.allow_hyphen_values(true) .allow_hyphen_values(true)
.overrides_with_all(&[options::BYTES, options::LINES])
.help("Number of lines to print"), .help("Number of lines to print"),
) )
.arg( .arg(
@ -110,13 +110,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
Arg::with_name(options::verbosity::QUIET) Arg::with_name(options::verbosity::QUIET)
.short("q") .short("q")
.long(options::verbosity::QUIET) .long(options::verbosity::QUIET)
.visible_alias("silent")
.overrides_with_all(&[options::verbosity::QUIET, options::verbosity::VERBOSE])
.help("never output headers giving file names"), .help("never output headers giving file names"),
) )
.arg(
Arg::with_name(options::verbosity::SILENT)
.long(options::verbosity::SILENT)
.help("synonym of --quiet"),
)
.arg( .arg(
Arg::with_name(options::SLEEP_INT) Arg::with_name(options::SLEEP_INT)
.short("s") .short("s")
@ -128,6 +125,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
Arg::with_name(options::verbosity::VERBOSE) Arg::with_name(options::verbosity::VERBOSE)
.short("v") .short("v")
.long(options::verbosity::VERBOSE) .long(options::verbosity::VERBOSE)
.overrides_with_all(&[options::verbosity::QUIET, options::verbosity::VERBOSE])
.help("always output headers giving file names"), .help("always output headers giving file names"),
) )
.arg( .arg(
@ -137,7 +135,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.help("Line delimiter is NUL, not newline"), .help("Line delimiter is NUL, not newline"),
) )
.arg( .arg(
Arg::with_name(ARG_FILES) Arg::with_name(options::ARG_FILES)
.multiple(true) .multiple(true)
.takes_value(true) .takes_value(true)
.min_values(1), .min_values(1),
@ -171,38 +169,21 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
} }
match matches.value_of(options::LINES) { let mode_and_beginning = if let Some(arg) = matches.value_of(options::BYTES) {
Some(n) => { match parse_num(arg) {
let mut slice: &str = n; Ok((n, beginning)) => (FilterMode::Bytes(n), beginning),
if slice.as_bytes().first() == Some(&b'+') { Err(e) => crash!(1, "invalid number of bytes: {}", e.to_string()),
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;
}
}
}
None => {
if let Some(n) = matches.value_of(options::BYTES) {
let mut slice: &str = n;
if slice.as_bytes().first() == Some(&b'+') {
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 matches.is_present(options::ZERO_TERM) {
if let FilterMode::Lines(count, _) = settings.mode { 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 verbose = matches.is_present(options::verbosity::VERBOSE);
let quiet = matches.is_present(options::verbosity::QUIET) let quiet = matches.is_present(options::verbosity::QUIET);
|| matches.is_present(options::verbosity::SILENT);
let files: Vec<String> = matches let files: Vec<String> = matches
.values_of(ARG_FILES) .values_of(options::ARG_FILES)
.map(|v| v.map(ToString::to_string).collect()) .map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default(); .unwrap_or_default();
@ -264,92 +244,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
0 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.as_bytes().last() == Some(&b'B') {
size_slice = &size_slice[..size_slice.len() - 1];
1000u64
} else {
1024u64
};
let exponent = match size_slice.as_bytes().last() {
Some(unit) => match unit {
b'K' | b'k' => 1u64,
b'M' => 2u64,
b'G' => 3u64,
b'T' => 4u64,
b'P' => 5u64,
b'E' => 6u64,
b'Z' | b'Y' => {
return Err(ParseSizeErr::size_too_big(size_slice));
}
b'b' => {
base = 512u64;
1u64
}
_ => 0u64,
},
None => 0u64,
};
if exponent != 0 {
size_slice = &size_slice[..size_slice.len() - 1];
}
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) { fn follow<T: Read>(readers: &mut [BufReader<T>], filenames: &[String], settings: &Settings) {
assert!(settings.follow); assert!(settings.follow);
let mut last = readers.len() - 1; let mut last = readers.len() - 1;
@ -463,7 +357,7 @@ fn bounded_tail(file: &mut File, settings: &Settings) {
/// If any element of `iter` is an [`Err`], then this function panics. /// If any element of `iter` is an [`Err`], then this function panics.
fn unbounded_tail_collect<T, E>( fn unbounded_tail_collect<T, E>(
iter: impl Iterator<Item = Result<T, E>>, iter: impl Iterator<Item = Result<T, E>>,
count: u64, count: usize,
beginning: bool, beginning: bool,
) -> VecDeque<T> ) -> VecDeque<T>
where where
@ -508,3 +402,22 @@ fn print_byte<T: Write>(stdout: &mut T, ch: u8) {
crash!(1, "{}", err); 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))
}

View file

@ -17,7 +17,7 @@ path = "src/timeout.rs"
[dependencies] [dependencies]
clap = "2.33" clap = "2.33"
libc = "0.2.42" libc = "0.2.42"
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" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -11,19 +11,21 @@
extern crate uucore; extern crate uucore;
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg};
use std::convert::TryFrom;
use std::fs::{metadata, OpenOptions}; use std::fs::{metadata, OpenOptions};
use std::io::ErrorKind; use std::io::ErrorKind;
use std::path::Path; use std::path::Path;
use uucore::parse_size::{parse_size, ParseSizeError};
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
enum TruncateMode { enum TruncateMode {
Absolute(u64), Absolute(usize),
Extend(u64), Extend(usize),
Reduce(u64), Reduce(usize),
AtMost(u64), AtMost(usize),
AtLeast(u64), AtLeast(usize),
RoundDown(u64), RoundDown(usize),
RoundUp(u64), RoundUp(usize),
} }
impl TruncateMode { impl TruncateMode {
@ -38,7 +40,7 @@ impl TruncateMode {
/// let fsize = 10; /// let fsize = 10;
/// assert_eq!(mode.to_size(fsize), 15); /// assert_eq!(mode.to_size(fsize), 15);
/// ``` /// ```
fn to_size(&self, fsize: u64) -> u64 { fn to_size(&self, fsize: usize) -> usize {
match self { match self {
TruncateMode::Absolute(size) => *size, TruncateMode::Absolute(size) => *size,
TruncateMode::Extend(size) => fsize + size, TruncateMode::Extend(size) => fsize + size,
@ -58,10 +60,9 @@ pub mod options {
pub static NO_CREATE: &str = "no-create"; pub static NO_CREATE: &str = "no-create";
pub static REFERENCE: &str = "reference"; pub static REFERENCE: &str = "reference";
pub static SIZE: &str = "size"; pub static SIZE: &str = "size";
pub static ARG_FILES: &str = "files";
} }
static ARG_FILES: &str = "files";
fn get_usage() -> String { fn get_usage() -> String {
format!("{0} [OPTION]... [FILE]...", executable!()) format!("{0} [OPTION]... [FILE]...", executable!())
} }
@ -113,21 +114,28 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
Arg::with_name(options::REFERENCE) Arg::with_name(options::REFERENCE)
.short("r") .short("r")
.long(options::REFERENCE) .long(options::REFERENCE)
.required_unless(options::SIZE)
.help("base the size of each file on the size of RFILE") .help("base the size of each file on the size of RFILE")
.value_name("RFILE") .value_name("RFILE")
) )
.arg( .arg(
Arg::with_name(options::SIZE) Arg::with_name(options::SIZE)
.short("s") .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") .help("set or adjust the size of each file according to SIZE, which is in bytes unless --io-blocks is specified")
.value_name("SIZE") .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); .get_matches_from(args);
let files: Vec<String> = matches let files: Vec<String> = matches
.values_of(ARG_FILES) .values_of(options::ARG_FILES)
.map(|v| v.map(ToString::to_string).collect()) .map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default(); .unwrap_or_default();
@ -149,8 +157,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
crash!( crash!(
1, 1,
"cannot stat '{}': No such file or directory", "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()), _ => 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 /// If the file could not be opened, or there was a problem setting the
/// size of the file. /// 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 path = Path::new(filename);
let f = OpenOptions::new().write(true).create(create).open(path)?; 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. /// Truncate files to a size relative to a given file.
@ -206,9 +214,9 @@ fn truncate_reference_and_size(
} }
_ => m, _ => 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); let tsize = mode.to_size(fsize);
for filename in &filenames { for filename in &filenames {
file_truncate(filename, create, tsize)?; file_truncate(filename, create, tsize)?;
@ -232,7 +240,7 @@ fn truncate_reference_file_only(
filenames: Vec<String>, filenames: Vec<String>,
create: bool, create: bool,
) -> std::io::Result<()> { ) -> std::io::Result<()> {
let tsize = metadata(rfilename)?.len(); let tsize = usize::try_from(metadata(rfilename)?.len()).unwrap();
for filename in &filenames { for filename in &filenames {
file_truncate(filename, create, tsize)?; file_truncate(filename, create, tsize)?;
} }
@ -261,10 +269,10 @@ fn truncate_size_only(
) -> std::io::Result<()> { ) -> std::io::Result<()> {
let mode = match parse_mode_and_size(size_string) { let mode = match parse_mode_and_size(size_string) {
Ok(m) => m, Ok(m) => m,
Err(_) => crash!(1, "Invalid number: {}", size_string), Err(e) => crash!(1, "Invalid number: {}", e.to_string()),
}; };
for filename in &filenames { 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); let tsize = mode.to_size(fsize);
file_truncate(filename, create, tsize)?; file_truncate(filename, create, tsize)?;
} }
@ -290,7 +298,7 @@ fn truncate(
} }
(Some(rfilename), None) => truncate_reference_file_only(&rfilename, filenames, create), (Some(rfilename), None) => truncate_reference_file_only(&rfilename, filenames, create),
(None, Some(size_string)) => truncate_size_only(&size_string, 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,18 +325,16 @@ fn is_modifier(c: char) -> bool {
/// ```rust,ignore /// ```rust,ignore
/// assert_eq!(parse_mode_and_size("+123"), (TruncateMode::Extend, 123)); /// 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. // 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 // Get the modifier character from the size string, if any. For
// example, if the argument is "+123", then the modifier is '+'. // example, if the argument is "+123", then the modifier is '+'.
let c = size_string.chars().next().unwrap(); if let Some(c) = size_string.chars().next() {
let size_string = if is_modifier(c) { if is_modifier(c) {
&size_string[1..] size_string = &size_string[1..];
} else { }
size_string
};
parse_size(size_string).map(match c { parse_size(size_string).map(match c {
'+' => TruncateMode::Extend, '+' => TruncateMode::Extend,
'-' => TruncateMode::Reduce, '-' => TruncateMode::Reduce,
@ -338,93 +344,16 @@ fn parse_mode_and_size(size_string: &str) -> Result<TruncateMode, ()> {
'%' => TruncateMode::RoundUp, '%' => TruncateMode::RoundUp,
_ => TruncateMode::Absolute, _ => TruncateMode::Absolute,
}) })
} } else {
Err(ParseSizeError::ParseFailure(size_string.to_string()))
/// 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 = numeric_string.parse().map_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)
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::parse_mode_and_size; use crate::parse_mode_and_size;
use crate::parse_size;
use crate::TruncateMode; 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] #[test]
fn test_parse_mode_and_size() { fn test_parse_mode_and_size() {
assert_eq!(parse_mode_and_size("10"), Ok(TruncateMode::Absolute(10))); assert_eq!(parse_mode_and_size("10"), Ok(TruncateMode::Absolute(10)));

View file

@ -44,7 +44,6 @@ entries = ["libc"]
fs = ["libc"] fs = ["libc"]
fsext = ["libc", "time"] fsext = ["libc", "time"]
mode = ["libc"] mode = ["libc"]
parse_time = []
perms = ["libc"] perms = ["libc"]
process = ["libc"] process = ["libc"]
ringbuffer = [] ringbuffer = []

View file

@ -6,8 +6,6 @@ pub mod encoding;
pub mod fs; pub mod fs;
#[cfg(feature = "fsext")] #[cfg(feature = "fsext")]
pub mod fsext; pub mod fsext;
#[cfg(feature = "parse_time")]
pub mod parse_time;
#[cfg(feature = "ringbuffer")] #[cfg(feature = "ringbuffer")]
pub mod ringbuffer; pub mod ringbuffer;
#[cfg(feature = "zero-copy")] #[cfg(feature = "zero-copy")]

View file

@ -101,6 +101,7 @@ pub fn get_groups_gnu(arg_id: Option<u32>) -> IOResult<Vec<gid_t>> {
Ok(sort_groups(groups, egid)) Ok(sort_groups(groups, egid))
} }
#[cfg(all(unix, feature = "process"))]
fn sort_groups(mut groups: Vec<gid_t>, egid: gid_t) -> Vec<gid_t> { fn sort_groups(mut groups: Vec<gid_t>, egid: gid_t) -> Vec<gid_t> {
if let Some(index) = groups.iter().position(|&x| x == egid) { if let Some(index) = groups.iter().position(|&x| x == egid) {
groups[..=index].rotate_right(1); groups[..=index].rotate_right(1);

View file

@ -113,23 +113,15 @@ fn resolve<P: AsRef<Path>>(original: P) -> IOResult<PathBuf> {
)); ));
} }
match fs::symlink_metadata(&result) { if !fs::symlink_metadata(&result)?.file_type().is_symlink() {
Err(e) => return Err(e), break;
Ok(ref m) if !m.file_type().is_symlink() => break, }
Ok(..) => {
followed += 1; followed += 1;
match fs::read_link(&result) { let path = fs::read_link(&result)?;
Ok(path) => {
result.pop(); result.pop();
result.push(path); result.push(path);
} }
Err(e) => {
return Err(e);
}
}
}
}
}
Ok(result) Ok(result)
} }
@ -193,10 +185,8 @@ pub fn canonicalize<P: AsRef<Path>>(original: P, can_mode: CanonicalizeMode) ->
} }
match resolve(&result) { match resolve(&result) {
Err(e) => match can_mode { Err(_) if can_mode == CanonicalizeMode::Missing => continue,
CanonicalizeMode::Missing => continue, Err(e) => return Err(e),
_ => return Err(e),
},
Ok(path) => { Ok(path) => {
result.pop(); result.pop();
result.push(path); result.push(path);
@ -211,15 +201,14 @@ pub fn canonicalize<P: AsRef<Path>>(original: P, can_mode: CanonicalizeMode) ->
} }
match resolve(&result) { match resolve(&result) {
Err(e) => { Err(e) if can_mode == CanonicalizeMode::Existing => {
if can_mode == CanonicalizeMode::Existing {
return Err(e); return Err(e);
} }
}
Ok(path) => { Ok(path) => {
result.pop(); result.pop();
result.push(path); result.push(path);
} }
Err(_) => (),
} }
} }
Ok(result) Ok(result)

View file

@ -89,19 +89,19 @@ fn parse_levels(mode: &str) -> (u32, usize) {
} }
fn parse_op(mode: &str, default: Option<char>) -> Result<(char, usize), String> { fn parse_op(mode: &str, default: Option<char>) -> Result<(char, usize), String> {
match mode.chars().next() { let ch = mode
Some(ch) => match ch { .chars()
'+' | '-' | '=' => Ok((ch, 1)), .next()
_ => match default { .ok_or_else(|| "unexpected end of mode".to_owned())?;
Some(ch) => Ok((ch, 0)), Ok(match ch {
None => Err(format!( '+' | '-' | '=' => (ch, 1),
"invalid operator (expected +, -, or =, but found {})", _ => {
ch let ch = default.ok_or_else(|| {
)), format!("invalid operator (expected +, -, or =, but found {})", ch)
}, })?;
}, (ch, 0)
None => Err("unexpected end of mode".to_owned()),
} }
})
} }
fn parse_change(mode: &str, fperm: u32, considering_dir: bool) -> (u32, usize) { fn parse_change(mode: &str, fperm: u32, considering_dir: bool) -> (u32, usize) {

View file

@ -92,7 +92,7 @@ pub fn wrap_chgrp<P: AsRef<Path>>(
out = format!( out = format!(
"group of '{}' retained as {}", "group of '{}' retained as {}",
path.display(), path.display(),
entries::gid2grp(dest_gid).unwrap() entries::gid2grp(dest_gid).unwrap_or_default()
); );
} }
} }

View file

@ -19,10 +19,10 @@ pub extern crate winapi;
//## internal modules //## internal modules
mod macros; // crate macros (macro_rules-type; exported to `crate::...`)
mod features; // feature-gated code modules mod features; // feature-gated code modules
mod macros; // crate macros (macro_rules-type; exported to `crate::...`)
mod mods; // core cross-platform modules mod mods; // core cross-platform modules
mod parser; // string parsing modules
// * cross-platform modules // * cross-platform modules
pub use crate::mods::backup_control; pub use crate::mods::backup_control;
@ -31,6 +31,10 @@ pub use crate::mods::os;
pub use crate::mods::panic; pub use crate::mods::panic;
pub use crate::mods::ranges; pub use crate::mods::ranges;
// * string parsing modules
pub use crate::parser::parse_size;
pub use crate::parser::parse_time;
// * feature-gated modules // * feature-gated modules
#[cfg(feature = "encoding")] #[cfg(feature = "encoding")]
pub use crate::features::encoding; pub use crate::features::encoding;
@ -38,8 +42,6 @@ pub use crate::features::encoding;
pub use crate::features::fs; pub use crate::features::fs;
#[cfg(feature = "fsext")] #[cfg(feature = "fsext")]
pub use crate::features::fsext; pub use crate::features::fsext;
#[cfg(feature = "parse_time")]
pub use crate::features::parse_time;
#[cfg(feature = "ringbuffer")] #[cfg(feature = "ringbuffer")]
pub use crate::features::ringbuffer; pub use crate::features::ringbuffer;
#[cfg(feature = "zero-copy")] #[cfg(feature = "zero-copy")]

View file

@ -85,10 +85,9 @@ impl Range {
let mut ranges: Vec<Range> = vec![]; let mut ranges: Vec<Range> = vec![];
for item in list.split(',') { for item in list.split(',') {
match FromStr::from_str(item) { let range_item = FromStr::from_str(item)
Ok(range_item) => ranges.push(range_item), .map_err(|e| format!("range '{}' was invalid: {}", item, e))?;
Err(e) => return Err(format!("range '{}' was invalid: {}", item, e)), ranges.push(range_item);
}
} }
ranges.sort(); ranges.sort();

View file

@ -0,0 +1,2 @@
pub mod parse_size;
pub mod parse_time;

View 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 its 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"));
}
}

View file

@ -20,20 +20,18 @@ pub fn from_str(string: &str) -> Result<Duration, String> {
'm' | 'M' => (slice, 60), 'm' | 'M' => (slice, 60),
'h' | 'H' => (slice, 60 * 60), 'h' | 'H' => (slice, 60 * 60),
'd' | 'D' => (slice, 60 * 60 * 24), 'd' | 'D' => (slice, 60 * 60 * 24),
val => { val if !val.is_alphabetic() => (string, 1),
if !val.is_alphabetic() { _ => {
(string, 1) if string == "inf" || string == "infinity" {
} else if string == "inf" || string == "infinity" {
("inf", 1) ("inf", 1)
} else { } else {
return Err(format!("invalid time interval '{}'", string)); return Err(format!("invalid time interval '{}'", string));
} }
} }
}; };
let num = match numstr.parse::<f64>() { let num = numstr
Ok(m) => m, .parse::<f64>()
Err(e) => return Err(format!("invalid time interval '{}': {}", string, e)), .map_err(|e| format!("invalid time interval '{}': {}", string, e))?;
};
const NANOS_PER_SEC: u32 = 1_000_000_000; const NANOS_PER_SEC: u32 = 1_000_000_000;
let whole_secs = num.trunc(); let whole_secs = num.trunc();

View file

@ -1,4 +1,4 @@
// spell-checker:ignore (words) nosuchgroup // spell-checker:ignore (words) nosuchgroup groupname
use crate::common::util::*; use crate::common::util::*;
use rust_users::*; use rust_users::*;
@ -10,6 +10,33 @@ fn test_invalid_option() {
static DIR: &str = "/tmp"; 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] #[test]
fn test_invalid_group() { fn test_invalid_group() {
new_ucmd!() new_ucmd!()
@ -121,9 +148,52 @@ fn test_reference() {
fn test_reference() { fn test_reference() {
new_ucmd!() new_ucmd!()
.arg("-v") .arg("-v")
.arg("--reference=/etc/passwd") .arg("--reference=ref_file")
.arg("/etc") .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] #[test]
@ -135,7 +205,7 @@ fn test_big_p() {
.arg("bin") .arg("bin")
.arg("/proc/self/cwd") .arg("/proc/self/cwd")
.fails() .fails()
.stderr_is( .stderr_contains(
"chgrp: changing group of '/proc/self/cwd': Operation not permitted (os error 1)\n", "chgrp: changing group of '/proc/self/cwd': Operation not permitted (os error 1)\n",
); );
} }

View file

@ -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 // spell-checker:ignore (paths) sublink subwords
use crate::common::util::*; 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] #[test]
fn test_du_basics_bad_name() { fn test_du_basics_bad_name() {
new_ucmd!() new_ucmd!()

View file

@ -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::*; 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");
}

View file

@ -428,20 +428,6 @@ fn test_symlink_relative() {
assert_eq!(at.resolve_link(link), file_a); 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] #[test]
fn test_symlink_relative_path() { fn test_symlink_relative_path() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
@ -571,3 +557,26 @@ fn test_symlink_no_deref_file() {
assert!(at.is_symlink(link)); assert!(at.is_symlink(link));
assert_eq!(at.resolve_link(link), file1); 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"));
}

View file

@ -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; extern crate unindent;
use self::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));
}
}

View file

@ -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 // spell-checker:ignore (words) ints
use crate::common::util::*; use crate::common::util::*;
@ -21,9 +26,7 @@ fn test_helper(file_name: &str, possible_args: &[&str]) {
#[test] #[test]
fn test_buffer_sizes() { fn test_buffer_sizes() {
let buffer_sizes = [ let buffer_sizes = ["0", "50K", "50k", "1M", "100M"];
"0", "50K", "50k", "1M", "100M", "1000G", "10T", "500E", "1Y",
];
for buffer_size in &buffer_sizes { for buffer_size in &buffer_sizes {
new_ucmd!() new_ucmd!()
.arg("-n") .arg("-n")
@ -32,6 +35,20 @@ fn test_buffer_sizes() {
.arg("ext_sort.txt") .arg("ext_sort.txt")
.succeeds() .succeeds()
.stdout_is_fixture("ext_sort.expected"); .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("-S")
.arg(invalid_buffer_size) .arg(invalid_buffer_size)
.fails() .fails()
.code_is(2)
.stderr_only(format!( .stderr_only(format!(
"sort: failed to parse buffer size `{}`: invalid digit found in string", "sort: invalid --buffer-size argument '{}'",
invalid_buffer_size 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] #[test]
@ -808,7 +853,7 @@ fn sort_multiple() {
#[test] #[test]
fn sort_empty_chunk() { fn sort_empty_chunk() {
new_ucmd!() new_ucmd!()
.args(&["-S", "40B"]) .args(&["-S", "40b"])
.pipe_in("a\na\n") .pipe_in("a\na\n")
.succeeds() .succeeds()
.stdout_is("a\na\n"); .stdout_is("a\na\n");
@ -848,7 +893,7 @@ fn test_compress_fail() {
#[test] #[test]
fn test_merge_batches() { fn test_merge_batches() {
new_ucmd!() new_ucmd!()
.args(&["ext_sort.txt", "-n", "-S", "150B"]) .args(&["ext_sort.txt", "-n", "-S", "150b"])
.succeeds() .succeeds()
.stdout_only_fixture("ext_sort.expected"); .stdout_only_fixture("ext_sort.expected");
} }

View file

@ -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 rand;
extern crate regex; extern crate regex;
@ -285,3 +290,53 @@ fn test_filter_command_fails() {
ucmd.args(&["--filter=/a/path/that/totally/does/not/exist", name]) ucmd.args(&["--filter=/a/path/that/totally/does/not/exist", name])
.fails(); .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
));
}
}
}

View file

@ -57,8 +57,18 @@ fn test_stdbuf_line_buffering_stdin_fails() {
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
#[test] #[test]
fn test_stdbuf_invalid_mode_fails() { fn test_stdbuf_invalid_mode_fails() {
let options = ["--input", "--output", "--error"];
for option in &options {
new_ucmd!() new_ucmd!()
.args(&["-i", "1024R", "head"]) .args(&[*option, "1024R", "head"])
.fails() .fails()
.stderr_is("stdbuf: invalid mode 1024R\nTry 'stdbuf --help' for more information."); .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");
}
} }

View file

@ -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; extern crate tail;
use self::tail::parse_size;
use crate::common::util::*; use crate::common::util::*;
use std::char::from_digit; use std::char::from_digit;
use std::io::Write; 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] #[test]
fn test_lines_with_size_suffix() { fn test_lines_with_size_suffix() {
const FILE: &str = "test_lines_with_size_suffix.txt"; const FILE: &str = "test_lines_with_size_suffix.txt";
@ -320,12 +291,11 @@ fn test_multiple_input_files_with_suppressed_headers() {
#[test] #[test]
fn test_multiple_input_quiet_flag_overrides_verbose_flag_for_suppressing_headers() { 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!() new_ucmd!()
.arg(FOOBAR_TXT) .arg(FOOBAR_TXT)
.arg(FOOBAR_2_TXT) .arg(FOOBAR_2_TXT)
.arg("-q")
.arg("-v") .arg("-v")
.arg("-q")
.run() .run()
.stdout_is_fixture("foobar_multiple_quiet.expected"); .stdout_is_fixture("foobar_multiple_quiet.expected");
} }
@ -388,3 +358,61 @@ fn test_positive_zero_lines() {
.succeeds() .succeeds()
.stdout_is("a\nb\nc\nd\ne\n"); .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");
}

View file

@ -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 crate::common::util::*;
use std::io::{Seek, SeekFrom, Write}; use std::io::{Seek, SeekFrom, Write};
@ -45,9 +52,18 @@ fn test_reference() {
let at = &scene.fixtures; let at = &scene.fixtures;
let mut file = at.make_file(FILE2); 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(); file.seek(SeekFrom::End(0)).unwrap();
let actual = file.seek(SeekFrom::Current(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] #[test]
fn test_invalid_numbers() { 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!() new_ucmd!()
.args(&["-s", "0X", "file"]) .args(&["-s", "0X", "file"])
.fails() .fails()
@ -265,3 +288,36 @@ fn test_reference_with_size_file_not_found() {
.fails() .fails()
.stderr_contains("cannot stat 'a': No such file or directory"); .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
));
}
}
}

1
tests/fixtures/chgrp/file1 vendored Normal file
View file

@ -0,0 +1 @@
target file 1

1
tests/fixtures/chgrp/file2 vendored Normal file
View file

@ -0,0 +1 @@
target file 2

1
tests/fixtures/chgrp/file3 vendored Normal file
View file

@ -0,0 +1 @@
target file 3

1
tests/fixtures/chgrp/ref_file vendored Normal file
View file

@ -0,0 +1 @@
Reference file