mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-29 12:07:46 +00:00
Merge pull request #2297 from jhscheer/refactoring_parse_size
Reducing duplicate code by refactoring `parse_size()` of head, tail, truncate, stdbuf, etc.
This commit is contained in:
commit
e595ee94da
26 changed files with 1017 additions and 706 deletions
|
@ -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,63 +229,22 @@ 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),
|
for env_var in &["DU_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] {
|
||||||
ch => (ch, 1024u64),
|
if let Ok(env_size) = env::var(env_var) {
|
||||||
};
|
if let Ok(v) = parse_size(&env_size) {
|
||||||
if ch == 'B' {
|
return v;
|
||||||
ch = s_chars.next()?;
|
|
||||||
offset += 1;
|
|
||||||
}
|
|
||||||
ch = ch.to_ascii_uppercase();
|
|
||||||
|
|
||||||
let unit = UNITS
|
|
||||||
.iter()
|
|
||||||
.rev()
|
|
||||||
.find(|&&(unit_ch, _)| unit_ch == ch)
|
|
||||||
.map(|&(_, val)| {
|
|
||||||
// we found a match, so increment offset
|
|
||||||
offset += 1;
|
|
||||||
val
|
|
||||||
})
|
|
||||||
.or_else(|| if multiple == 1024 { Some(0) } else { None })?;
|
|
||||||
|
|
||||||
let number = s[..s.len() - offset].parse::<u64>().ok()?;
|
|
||||||
|
|
||||||
Some(number * multiple.pow(unit))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn translate_to_pure_number(s: &Option<&str>) -> Option<u64> {
|
|
||||||
match *s {
|
|
||||||
Some(s) => unit_string_to_number(s),
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_block_size(s: Option<&str>) -> u64 {
|
|
||||||
match translate_to_pure_number(&s) {
|
|
||||||
Some(v) => v,
|
|
||||||
None => {
|
|
||||||
if let Some(value) = s {
|
|
||||||
show_error!("invalid --block-size argument '{}'", value);
|
|
||||||
};
|
|
||||||
|
|
||||||
for env_var in &["DU_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] {
|
|
||||||
let env_size = env::var(env_var).ok();
|
|
||||||
if let Some(quantity) = translate_to_pure_number(&env_size.as_deref()) {
|
|
||||||
return quantity;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if env::var("POSIXLY_CORRECT").is_ok() {
|
if env::var("POSIXLY_CORRECT").is_ok() {
|
||||||
512
|
512
|
||||||
} else {
|
} else {
|
||||||
1024
|
1024
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
|
|
@ -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))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -466,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) {
|
||||||
|
|
|
@ -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,88 +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, all_but_last) = {
|
let mut all_but_last = false;
|
||||||
let mut chars = src.char_indices();
|
|
||||||
let (_, c) = chars.next().ok_or(ParseError::Syntax)?;
|
|
||||||
if c == '-' {
|
|
||||||
num_start += 1;
|
|
||||||
(chars, true)
|
|
||||||
} else {
|
|
||||||
(src.char_indices(), false)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
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 {
|
if let Some(c) = size_string.chars().next() {
|
||||||
Some(
|
if c == '+' || c == '-' {
|
||||||
src[num_start..=num_end]
|
// head: '+' is not documented (8.32 man pages)
|
||||||
.parse::<usize>()
|
size_string = &size_string[1..];
|
||||||
.map_err(|_| ParseError::Overflow)?,
|
if c == '-' {
|
||||||
)
|
all_but_last = true;
|
||||||
} 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 = usize::try_from(mul).map_err(|_| ParseError::Overflow)?;
|
|
||||||
match num.unwrap_or(1).checked_mul(mul) {
|
|
||||||
Some(n) => Ok((n, all_but_last)),
|
|
||||||
None => Err(ParseError::Overflow),
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return Err(ParseSizeError::ParseFailure(src.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parse_size(size_string).map(|n| (n, all_but_last))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -191,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"]));
|
||||||
|
|
|
@ -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,13 +129,11 @@ impl OdOptions {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut skip_bytes = matches
|
let mut skip_bytes = matches.value_of(options::SKIP_BYTES).map_or(0, |s| {
|
||||||
.value_of(options::SKIP_BYTES)
|
parse_number_of_bytes(s).unwrap_or_else(|e| {
|
||||||
.map(|s| {
|
crash!(1, "{}", format_error_message(e, s, options::SKIP_BYTES))
|
||||||
parse_number_of_bytes(s).map_err(|_| format!("Invalid argument --skip-bytes={}", s))
|
|
||||||
})
|
})
|
||||||
.transpose()?
|
});
|
||||||
.unwrap_or(0);
|
|
||||||
|
|
||||||
let mut label: Option<usize> = None;
|
let mut label: Option<usize> = None;
|
||||||
|
|
||||||
|
@ -150,11 +149,14 @@ impl OdOptions {
|
||||||
|
|
||||||
let formats = parse_format_flags(&args)?;
|
let formats = parse_format_flags(&args)?;
|
||||||
|
|
||||||
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)
|
||||||
});
|
});
|
||||||
|
@ -165,12 +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 = matches
|
let read_bytes = matches.value_of(options::READ_BYTES).map(|s| {
|
||||||
.value_of(options::READ_BYTES)
|
parse_number_of_bytes(s).unwrap_or_else(|e| {
|
||||||
.map(|s| {
|
crash!(1, "{}", format_error_message(e, s, options::READ_BYTES))
|
||||||
parse_number_of_bytes(s).map_err(|_| format!("Invalid argument --read-bytes={}", s))
|
|
||||||
})
|
})
|
||||||
.transpose()?;
|
});
|
||||||
|
|
||||||
let radix = match matches.value_of(options::ADDRESS_RADIX) {
|
let radix = match matches.value_of(options::ADDRESS_RADIX) {
|
||||||
None => Radix::Octal,
|
None => Radix::Octal,
|
||||||
|
@ -251,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")
|
||||||
|
@ -441,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,
|
||||||
};
|
};
|
||||||
|
@ -624,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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]]
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
static NAME: &str = "sort";
|
static NAME: &str = "sort";
|
||||||
|
@ -162,32 +163,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 {
|
Err(ParseSizeError::ParseFailure("invalid suffix".to_string()))
|
||||||
(input, 1)
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let num_usize: usize = num_str
|
|
||||||
.trim()
|
|
||||||
.parse()
|
|
||||||
.unwrap_or_else(|e| crash!(1, "failed to parse buffer size `{}`: {}", num_str, e));
|
|
||||||
|
|
||||||
num_usize.saturating_mul(1000usize.saturating_pow(si_unit as u32))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn out_writer(&self) -> BufWriter<Box<dyn Write>> {
|
fn out_writer(&self) -> BufWriter<Box<dyn Write>> {
|
||||||
|
@ -1176,8 +1174,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
|
|
||||||
settings.buffer_size = matches
|
settings.buffer_size = matches
|
||||||
.value_of(OPT_BUF_SIZE)
|
.value_of(OPT_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, OPT_BUF_SIZE)))
|
||||||
|
});
|
||||||
|
|
||||||
settings.tmp_dir = matches
|
settings.tmp_dir = matches
|
||||||
.value_of(OPT_TMP_DIR)
|
.value_of(OPT_TMP_DIR)
|
||||||
|
@ -1587,6 +1587,16 @@ fn open(path: impl AsRef<OsStr>) -> Box<dyn Read + Send> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn format_error_message(error: ParseSizeError, s: &str, option: &str) -> String {
|
||||||
|
// NOTE:
|
||||||
|
// GNU's sort echos affected flag, -S or --buffer-size, depending user's selection
|
||||||
|
// GNU's sort does distinguish between "invalid (suffix in) argument"
|
||||||
|
match error {
|
||||||
|
ParseSizeError::ParseFailure(_) => format!("invalid --{} argument '{}'", option, s),
|
||||||
|
ParseSizeError::SizeTooBig(_) => format!("--{} argument '{}' too large", option, s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
|
@ -1676,4 +1686,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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,11 +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 = parse_size(x)
|
|e| crash!(125, "invalid mode {}", e),
|
||||||
.ok_or_else(|| ProgramOptionsError(format!("invalid mode {}", x)))?;
|
|m| Ok(BufferType::Size(m)),
|
||||||
Ok(BufferType::Size(size))
|
),
|
||||||
}
|
|
||||||
},
|
},
|
||||||
None => Ok(BufferType::Default),
|
None => Ok(BufferType::Default),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 => {
|
} else if let Some(arg) = matches.value_of(options::LINES) {
|
||||||
if let Some(n) = matches.value_of(options::BYTES) {
|
match parse_num(arg) {
|
||||||
let mut slice: &str = n;
|
Ok((n, beginning)) => (FilterMode::Lines(n, b'\n'), beginning),
|
||||||
if slice.as_bytes().first() == Some(&b'+') {
|
Err(e) => crash!(1, "invalid number of lines: {}", e.to_string()),
|
||||||
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 {
|
||||||
|
(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))
|
||||||
|
}
|
||||||
|
|
|
@ -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" }
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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,114 +325,35 @@ 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..];
|
||||||
|
}
|
||||||
|
parse_size(size_string).map(match c {
|
||||||
|
'+' => TruncateMode::Extend,
|
||||||
|
'-' => TruncateMode::Reduce,
|
||||||
|
'<' => TruncateMode::AtMost,
|
||||||
|
'>' => TruncateMode::AtLeast,
|
||||||
|
'/' => TruncateMode::RoundDown,
|
||||||
|
'%' => TruncateMode::RoundUp,
|
||||||
|
_ => TruncateMode::Absolute,
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
size_string
|
Err(ParseSizeError::ParseFailure(size_string.to_string()))
|
||||||
};
|
}
|
||||||
parse_size(size_string).map(match c {
|
|
||||||
'+' => TruncateMode::Extend,
|
|
||||||
'-' => TruncateMode::Reduce,
|
|
||||||
'<' => TruncateMode::AtMost,
|
|
||||||
'>' => TruncateMode::AtLeast,
|
|
||||||
'/' => TruncateMode::RoundDown,
|
|
||||||
'%' => TruncateMode::RoundUp,
|
|
||||||
_ => TruncateMode::Absolute,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse a size string into a number of bytes.
|
|
||||||
///
|
|
||||||
/// A size string comprises an integer and an optional unit. The unit
|
|
||||||
/// may be K, M, G, T, P, E, Z, or Y (powers of 1024) or KB, MB,
|
|
||||||
/// etc. (powers of 1000).
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// This function returns an error if the string does not begin with a
|
|
||||||
/// numeral, or if the unit is not one of the supported units described
|
|
||||||
/// in the preceding section.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```rust,ignore
|
|
||||||
/// assert_eq!(parse_size("123").unwrap(), 123);
|
|
||||||
/// assert_eq!(parse_size("123K").unwrap(), 123 * 1024);
|
|
||||||
/// assert_eq!(parse_size("123KB").unwrap(), 123 * 1000);
|
|
||||||
/// ```
|
|
||||||
fn parse_size(size: &str) -> Result<u64, ()> {
|
|
||||||
// Get the numeric part of the size argument. For example, if the
|
|
||||||
// argument is "123K", then the numeric part is "123".
|
|
||||||
let numeric_string: String = size.chars().take_while(|c| c.is_digit(10)).collect();
|
|
||||||
let number: u64 = 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)));
|
||||||
|
|
|
@ -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 = []
|
||||||
|
|
|
@ -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")]
|
||||||
|
|
|
@ -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")]
|
||||||
|
|
2
src/uucore/src/lib/parser.rs
Normal file
2
src/uucore/src/lib/parser.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod parse_size;
|
||||||
|
pub mod parse_time;
|
324
src/uucore/src/lib/parser/parse_size.rs
Normal file
324
src/uucore/src/lib/parser/parse_size.rs
Normal file
|
@ -0,0 +1,324 @@
|
||||||
|
// * This file is part of the uutils coreutils package.
|
||||||
|
// *
|
||||||
|
// * For the full copyright and license information, please view the LICENSE
|
||||||
|
// * file that was distributed with this source code.
|
||||||
|
|
||||||
|
// spell-checker:ignore (ToDO) hdsf ghead gtail
|
||||||
|
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
/// Parse a size string into a number of bytes.
|
||||||
|
///
|
||||||
|
/// A size string comprises an integer and an optional unit. The unit
|
||||||
|
/// may be K, M, G, T, P, E, Z or Y (powers of 1024), or KB, MB,
|
||||||
|
/// etc. (powers of 1000), or b which is 512.
|
||||||
|
/// Binary prefixes can be used, too: KiB=K, MiB=M, and so on.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Will return `ParseSizeError` if it’s not possible to parse this
|
||||||
|
/// string into a number, e.g. if the string does not begin with a
|
||||||
|
/// numeral, or if the unit is not one of the supported units described
|
||||||
|
/// in the preceding section.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use uucore::parse_size::parse_size;
|
||||||
|
/// assert_eq!(Ok(123), parse_size("123"));
|
||||||
|
/// assert_eq!(Ok(9 * 1000), parse_size("9kB")); // kB is 1000
|
||||||
|
/// assert_eq!(Ok(2 * 1024), parse_size("2K")); // K is 1024
|
||||||
|
/// ```
|
||||||
|
pub fn parse_size(size: &str) -> Result<usize, ParseSizeError> {
|
||||||
|
if size.is_empty() {
|
||||||
|
return Err(ParseSizeError::parse_failure(size));
|
||||||
|
}
|
||||||
|
// Get the numeric part of the size argument. For example, if the
|
||||||
|
// argument is "123K", then the numeric part is "123".
|
||||||
|
let numeric_string: String = size.chars().take_while(|c| c.is_digit(10)).collect();
|
||||||
|
let number: usize = if !numeric_string.is_empty() {
|
||||||
|
match numeric_string.parse() {
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(_) => return Err(ParseSizeError::parse_failure(size)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get the alphabetic units part of the size argument and compute
|
||||||
|
// the factor it represents. For example, if the argument is "123K",
|
||||||
|
// then the unit part is "K" and the factor is 1024. This may be the
|
||||||
|
// empty string, in which case, the factor is 1.
|
||||||
|
let unit = &size[numeric_string.len()..];
|
||||||
|
let (base, exponent): (u128, u32) = match unit {
|
||||||
|
"" => (1, 0),
|
||||||
|
"b" => (512, 1), // (`od`, `head` and `tail` use "b")
|
||||||
|
"KiB" | "kiB" | "K" | "k" => (1024, 1),
|
||||||
|
"MiB" | "miB" | "M" | "m" => (1024, 2),
|
||||||
|
"GiB" | "giB" | "G" | "g" => (1024, 3),
|
||||||
|
"TiB" | "tiB" | "T" | "t" => (1024, 4),
|
||||||
|
"PiB" | "piB" | "P" | "p" => (1024, 5),
|
||||||
|
"EiB" | "eiB" | "E" | "e" => (1024, 6),
|
||||||
|
"ZiB" | "ziB" | "Z" | "z" => (1024, 7),
|
||||||
|
"YiB" | "yiB" | "Y" | "y" => (1024, 8),
|
||||||
|
"KB" | "kB" => (1000, 1),
|
||||||
|
"MB" | "mB" => (1000, 2),
|
||||||
|
"GB" | "gB" => (1000, 3),
|
||||||
|
"TB" | "tB" => (1000, 4),
|
||||||
|
"PB" | "pB" => (1000, 5),
|
||||||
|
"EB" | "eB" => (1000, 6),
|
||||||
|
"ZB" | "zB" => (1000, 7),
|
||||||
|
"YB" | "yB" => (1000, 8),
|
||||||
|
_ => return Err(ParseSizeError::parse_failure(size)),
|
||||||
|
};
|
||||||
|
let factor = match usize::try_from(base.pow(exponent)) {
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(_) => return Err(ParseSizeError::size_too_big(size)),
|
||||||
|
};
|
||||||
|
number
|
||||||
|
.checked_mul(factor)
|
||||||
|
.ok_or_else(|| ParseSizeError::size_too_big(size))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum ParseSizeError {
|
||||||
|
ParseFailure(String), // Syntax
|
||||||
|
SizeTooBig(String), // Overflow
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for ParseSizeError {
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
match *self {
|
||||||
|
ParseSizeError::ParseFailure(ref s) => &*s,
|
||||||
|
ParseSizeError::SizeTooBig(ref s) => &*s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ParseSizeError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
let s = match self {
|
||||||
|
ParseSizeError::ParseFailure(s) => s,
|
||||||
|
ParseSizeError::SizeTooBig(s) => s,
|
||||||
|
};
|
||||||
|
write!(f, "{}", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParseSizeError {
|
||||||
|
fn parse_failure(s: &str) -> ParseSizeError {
|
||||||
|
// stderr on linux (GNU coreutils 8.32)
|
||||||
|
// has to be handled in the respective uutils because strings differ, e.g.:
|
||||||
|
//
|
||||||
|
// `NUM`
|
||||||
|
// head: invalid number of bytes: ‘1fb’
|
||||||
|
// tail: invalid number of bytes: ‘1fb’
|
||||||
|
//
|
||||||
|
// `SIZE`
|
||||||
|
// split: invalid number of bytes: ‘1fb’
|
||||||
|
// truncate: Invalid number: ‘1fb’
|
||||||
|
//
|
||||||
|
// `MODE`
|
||||||
|
// stdbuf: invalid mode ‘1fb’
|
||||||
|
//
|
||||||
|
// `SIZE`
|
||||||
|
// sort: invalid suffix in --buffer-size argument '1fb'
|
||||||
|
// sort: invalid --buffer-size argument 'fb'
|
||||||
|
//
|
||||||
|
// `SIZE`
|
||||||
|
// du: invalid suffix in --buffer-size argument '1fb'
|
||||||
|
// du: invalid suffix in --threshold argument '1fb'
|
||||||
|
// du: invalid --buffer-size argument 'fb'
|
||||||
|
// du: invalid --threshold argument 'fb'
|
||||||
|
//
|
||||||
|
// `BYTES`
|
||||||
|
// od: invalid suffix in --read-bytes argument '1fb'
|
||||||
|
// od: invalid --read-bytes argument argument 'fb'
|
||||||
|
// --skip-bytes
|
||||||
|
// --width
|
||||||
|
// --strings
|
||||||
|
// etc.
|
||||||
|
ParseSizeError::ParseFailure(format!("‘{}’", s))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_too_big(s: &str) -> ParseSizeError {
|
||||||
|
// stderr on linux (GNU coreutils 8.32)
|
||||||
|
// has to be handled in the respective uutils because strings differ, e.g.:
|
||||||
|
//
|
||||||
|
// head: invalid number of bytes: ‘1Y’: Value too large for defined data type
|
||||||
|
// tail: invalid number of bytes: ‘1Y’: Value too large for defined data type
|
||||||
|
// split: invalid number of bytes: ‘1Y’: Value too large for defined data type
|
||||||
|
// truncate: Invalid number: ‘1Y’: Value too large for defined data type
|
||||||
|
// stdbuf: invalid mode ‘1Y’: Value too large for defined data type
|
||||||
|
// sort: -S argument '1Y' too large
|
||||||
|
// du: -B argument '1Y' too large
|
||||||
|
// od: -N argument '1Y' too large
|
||||||
|
// etc.
|
||||||
|
//
|
||||||
|
// stderr on macos (brew - GNU coreutils 8.32) also differs for the same version, e.g.:
|
||||||
|
// ghead: invalid number of bytes: ‘1Y’: Value too large to be stored in data type
|
||||||
|
// gtail: invalid number of bytes: ‘1Y’: Value too large to be stored in data type
|
||||||
|
ParseSizeError::SizeTooBig(format!("‘{}’: Value too large for defined data type", s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn variant_eq(a: &ParseSizeError, b: &ParseSizeError) -> bool {
|
||||||
|
std::mem::discriminant(a) == std::mem::discriminant(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn all_suffixes() {
|
||||||
|
// Units are K,M,G,T,P,E,Z,Y (powers of 1024) or KB,MB,... (powers of 1000).
|
||||||
|
// Binary prefixes can be used, too: KiB=K, MiB=M, and so on.
|
||||||
|
let suffixes = [
|
||||||
|
('K', 1u32),
|
||||||
|
('M', 2u32),
|
||||||
|
('G', 3u32),
|
||||||
|
('T', 4u32),
|
||||||
|
('P', 5u32),
|
||||||
|
('E', 6u32),
|
||||||
|
#[cfg(target_pointer_width = "128")]
|
||||||
|
('Z', 7u32), // ParseSizeError::SizeTooBig on x64
|
||||||
|
#[cfg(target_pointer_width = "128")]
|
||||||
|
('Y', 8u32), // ParseSizeError::SizeTooBig on x64
|
||||||
|
];
|
||||||
|
|
||||||
|
for &(c, exp) in &suffixes {
|
||||||
|
let s = format!("2{}B", c); // KB
|
||||||
|
assert_eq!(Ok((2 * (1000_u128).pow(exp)) as usize), parse_size(&s));
|
||||||
|
let s = format!("2{}", c); // K
|
||||||
|
assert_eq!(Ok((2 * (1024_u128).pow(exp)) as usize), parse_size(&s));
|
||||||
|
let s = format!("2{}iB", c); // KiB
|
||||||
|
assert_eq!(Ok((2 * (1024_u128).pow(exp)) as usize), parse_size(&s));
|
||||||
|
let s = format!("2{}iB", c.to_lowercase()); // kiB
|
||||||
|
assert_eq!(Ok((2 * (1024_u128).pow(exp)) as usize), parse_size(&s));
|
||||||
|
|
||||||
|
// suffix only
|
||||||
|
let s = format!("{}B", c); // KB
|
||||||
|
assert_eq!(Ok(((1000_u128).pow(exp)) as usize), parse_size(&s));
|
||||||
|
let s = format!("{}", c); // K
|
||||||
|
assert_eq!(Ok(((1024_u128).pow(exp)) as usize), parse_size(&s));
|
||||||
|
let s = format!("{}iB", c); // KiB
|
||||||
|
assert_eq!(Ok(((1024_u128).pow(exp)) as usize), parse_size(&s));
|
||||||
|
let s = format!("{}iB", c.to_lowercase()); // kiB
|
||||||
|
assert_eq!(Ok(((1024_u128).pow(exp)) as usize), parse_size(&s));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(not(target_pointer_width = "128"))]
|
||||||
|
fn overflow_x64() {
|
||||||
|
assert!(parse_size("10000000000000000000000").is_err());
|
||||||
|
assert!(parse_size("1000000000T").is_err());
|
||||||
|
assert!(parse_size("100000P").is_err());
|
||||||
|
assert!(parse_size("100E").is_err());
|
||||||
|
assert!(parse_size("1Z").is_err());
|
||||||
|
assert!(parse_size("1Y").is_err());
|
||||||
|
|
||||||
|
assert!(variant_eq(
|
||||||
|
&parse_size("1Z").unwrap_err(),
|
||||||
|
&ParseSizeError::SizeTooBig(String::new())
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
ParseSizeError::SizeTooBig("‘1Y’: Value too large for defined data type".to_string()),
|
||||||
|
parse_size("1Y").unwrap_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_pointer_width = "32")]
|
||||||
|
fn overflow_x32() {
|
||||||
|
assert!(variant_eq(
|
||||||
|
&parse_size("1T").unwrap_err(),
|
||||||
|
&ParseSizeError::SizeTooBig(String::new())
|
||||||
|
));
|
||||||
|
assert!(variant_eq(
|
||||||
|
&parse_size("1000G").unwrap_err(),
|
||||||
|
&ParseSizeError::SizeTooBig(String::new())
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_syntax() {
|
||||||
|
let test_strings = [
|
||||||
|
"328hdsf3290",
|
||||||
|
"5MiB nonsense",
|
||||||
|
"5mib",
|
||||||
|
"biB",
|
||||||
|
"-",
|
||||||
|
"+",
|
||||||
|
"",
|
||||||
|
"-1",
|
||||||
|
"1e2",
|
||||||
|
"∞",
|
||||||
|
];
|
||||||
|
for &test_string in &test_strings {
|
||||||
|
assert_eq!(
|
||||||
|
parse_size(test_string).unwrap_err(),
|
||||||
|
ParseSizeError::ParseFailure(format!("‘{}’", test_string))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn b_suffix() {
|
||||||
|
assert_eq!(Ok(3 * 512), parse_size("3b")); // b is 512
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_suffix() {
|
||||||
|
assert_eq!(Ok(1234), parse_size("1234"));
|
||||||
|
assert_eq!(Ok(0), parse_size("0"));
|
||||||
|
assert_eq!(Ok(5), parse_size("5"));
|
||||||
|
assert_eq!(Ok(999), parse_size("999"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn kilobytes_suffix() {
|
||||||
|
assert_eq!(Ok(123 * 1000), parse_size("123KB")); // KB is 1000
|
||||||
|
assert_eq!(Ok(9 * 1000), parse_size("9kB")); // kB is 1000
|
||||||
|
assert_eq!(Ok(2 * 1024), parse_size("2K")); // K is 1024
|
||||||
|
assert_eq!(Ok(0), parse_size("0K"));
|
||||||
|
assert_eq!(Ok(0), parse_size("0KB"));
|
||||||
|
assert_eq!(Ok(1000), parse_size("KB"));
|
||||||
|
assert_eq!(Ok(1024), parse_size("K"));
|
||||||
|
assert_eq!(Ok(2000), parse_size("2kB"));
|
||||||
|
assert_eq!(Ok(4000), parse_size("4KB"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn megabytes_suffix() {
|
||||||
|
assert_eq!(Ok(123 * 1024 * 1024), parse_size("123M"));
|
||||||
|
assert_eq!(Ok(123 * 1000 * 1000), parse_size("123MB"));
|
||||||
|
assert_eq!(Ok(1024 * 1024), parse_size("M"));
|
||||||
|
assert_eq!(Ok(1000 * 1000), parse_size("MB"));
|
||||||
|
assert_eq!(Ok(2 * 1_048_576), parse_size("2m"));
|
||||||
|
assert_eq!(Ok(4 * 1_048_576), parse_size("4M"));
|
||||||
|
assert_eq!(Ok(2_000_000), parse_size("2mB"));
|
||||||
|
assert_eq!(Ok(4_000_000), parse_size("4MB"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gigabytes_suffix() {
|
||||||
|
assert_eq!(Ok(1_073_741_824), parse_size("1G"));
|
||||||
|
assert_eq!(Ok(2_000_000_000), parse_size("2GB"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_pointer_width = "64")]
|
||||||
|
fn x64() {
|
||||||
|
assert_eq!(Ok(1_099_511_627_776), parse_size("1T"));
|
||||||
|
assert_eq!(Ok(1_125_899_906_842_624), parse_size("1P"));
|
||||||
|
assert_eq!(Ok(1_152_921_504_606_846_976), parse_size("1E"));
|
||||||
|
assert_eq!(Ok(2_000_000_000_000), parse_size("2TB"));
|
||||||
|
assert_eq!(Ok(2_000_000_000_000_000), parse_size("2PB"));
|
||||||
|
assert_eq!(Ok(2_000_000_000_000_000_000), parse_size("2EB"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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!()
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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]
|
||||||
|
@ -804,7 +849,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");
|
||||||
|
@ -844,7 +889,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");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
new_ucmd!()
|
let options = ["--input", "--output", "--error"];
|
||||||
.args(&["-i", "1024R", "head"])
|
for option in &options {
|
||||||
.fails()
|
new_ucmd!()
|
||||||
.stderr_is("stdbuf: invalid mode 1024R\nTry 'stdbuf --help' for more information.");
|
.args(&[*option, "1024R", "head"])
|
||||||
|
.fails()
|
||||||
|
.code_is(125)
|
||||||
|
.stderr_only("stdbuf: invalid mode ‘1024R’");
|
||||||
|
#[cfg(not(target_pointer_width = "128"))]
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&[*option, "1Y", "head"])
|
||||||
|
.fails()
|
||||||
|
.code_is(125)
|
||||||
|
.stderr_contains("stdbuf: invalid mode ‘1Y’: Value too large for defined data type");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
|
// * This file is part of the uutils coreutils package.
|
||||||
|
// *
|
||||||
|
// * For the full copyright and license information, please view the LICENSE
|
||||||
|
// * file that was distributed with this source code.
|
||||||
|
|
||||||
|
// spell-checker:ignore (ToDO) abcdefghijklmnopqrstuvwxyz efghijklmnopqrstuvwxyz vwxyz emptyfile
|
||||||
|
|
||||||
extern crate tail;
|
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");
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue