mirror of
https://github.com/uutils/coreutils
synced 2024-12-13 14:52:41 +00:00
numfmt: implement "--to-unit" & "--from-unit"
This commit is contained in:
parent
37b754f462
commit
1f292dd834
4 changed files with 155 additions and 9 deletions
|
@ -1,6 +1,6 @@
|
|||
use uucore::display::Quotable;
|
||||
|
||||
use crate::options::{NumfmtOptions, RoundMethod};
|
||||
use crate::options::{NumfmtOptions, RoundMethod, TransformOptions};
|
||||
use crate::units::{DisplayableSuffix, RawSuffix, Result, Suffix, Unit, IEC_BASES, SI_BASES};
|
||||
|
||||
/// Iterate over a line's fields, where each field is a contiguous sequence of
|
||||
|
@ -127,10 +127,11 @@ fn remove_suffix(i: f64, s: Option<Suffix>, u: &Unit) -> Result<f64> {
|
|||
}
|
||||
}
|
||||
|
||||
fn transform_from(s: &str, opts: &Unit) -> Result<f64> {
|
||||
fn transform_from(s: &str, opts: &TransformOptions) -> Result<f64> {
|
||||
let (i, suffix) = parse_suffix(s)?;
|
||||
let i = i * (opts.from_unit as f64);
|
||||
|
||||
remove_suffix(i, suffix, opts).map(|n| if n < 0.0 { -n.abs().ceil() } else { n.ceil() })
|
||||
remove_suffix(i, suffix, &opts.from).map(|n| if n < 0.0 { -n.abs().ceil() } else { n.ceil() })
|
||||
}
|
||||
|
||||
/// Divide numerator by denominator, with rounding.
|
||||
|
@ -206,8 +207,9 @@ fn consider_suffix(n: f64, u: &Unit, round_method: RoundMethod) -> Result<(f64,
|
|||
}
|
||||
}
|
||||
|
||||
fn transform_to(s: f64, opts: &Unit, round_method: RoundMethod) -> Result<String> {
|
||||
let (i2, s) = consider_suffix(s, opts, round_method)?;
|
||||
fn transform_to(s: f64, opts: &TransformOptions, round_method: RoundMethod) -> Result<String> {
|
||||
let (i2, s) = consider_suffix(s, &opts.to, round_method)?;
|
||||
let i2 = i2 / (opts.to_unit as f64);
|
||||
Ok(match s {
|
||||
None => format!("{}", i2),
|
||||
Some(s) if i2.abs() < 10.0 => format!("{:.1}{}", i2, DisplayableSuffix(s)),
|
||||
|
@ -227,8 +229,8 @@ fn format_string(
|
|||
};
|
||||
|
||||
let number = transform_to(
|
||||
transform_from(source_without_suffix, &options.transform.from)?,
|
||||
&options.transform.to,
|
||||
transform_from(source_without_suffix, &options.transform)?,
|
||||
&options.transform,
|
||||
options.round,
|
||||
)?;
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ use crate::options::*;
|
|||
use crate::units::{Result, Unit};
|
||||
use clap::{crate_version, Arg, ArgMatches, Command};
|
||||
use std::io::{BufRead, Write};
|
||||
use units::{IEC_BASES, SI_BASES};
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::UResult;
|
||||
use uucore::format_usage;
|
||||
|
@ -96,11 +97,66 @@ fn parse_unit(s: &str) -> Result<Unit> {
|
|||
}
|
||||
}
|
||||
|
||||
// Parses a unit size. Suffixes are turned into their integer representations. For example, 'K'
|
||||
// will return `Ok(1000)`, and '2K' will return `Ok(2000)`.
|
||||
fn parse_unit_size(s: &str) -> Result<usize> {
|
||||
let number: String = s.chars().take_while(char::is_ascii_digit).collect();
|
||||
let suffix = &s[number.len()..];
|
||||
|
||||
if number.is_empty() || "0".repeat(number.len()) != number {
|
||||
if let Some(multiplier) = parse_unit_size_suffix(suffix) {
|
||||
if number.is_empty() {
|
||||
return Ok(multiplier);
|
||||
}
|
||||
|
||||
if let Ok(n) = number.parse::<usize>() {
|
||||
return Ok(n * multiplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(format!("invalid unit size: {}", s.quote()))
|
||||
}
|
||||
|
||||
// Parses a suffix of a unit size and returns the corresponding multiplier. For example,
|
||||
// the suffix 'K' will return `Some(1000)`, and 'Ki' will return `Some(1024)`.
|
||||
//
|
||||
// If the suffix is empty, `Some(1)` is returned.
|
||||
//
|
||||
// If the suffix is unknown, `None` is returned.
|
||||
fn parse_unit_size_suffix(s: &str) -> Option<usize> {
|
||||
if s.is_empty() {
|
||||
return Some(1);
|
||||
}
|
||||
|
||||
let suffix = s.chars().next().unwrap();
|
||||
|
||||
if let Some(i) = ['K', 'M', 'G', 'T', 'P', 'E']
|
||||
.iter()
|
||||
.position(|&ch| ch == suffix)
|
||||
{
|
||||
return match s.len() {
|
||||
1 => Some(SI_BASES[i + 1] as usize),
|
||||
2 if s.ends_with('i') => Some(IEC_BASES[i + 1] as usize),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn parse_options(args: &ArgMatches) -> Result<NumfmtOptions> {
|
||||
let from = parse_unit(args.value_of(options::FROM).unwrap())?;
|
||||
let to = parse_unit(args.value_of(options::TO).unwrap())?;
|
||||
let from_unit = parse_unit_size(args.value_of(options::FROM_UNIT).unwrap())?;
|
||||
let to_unit = parse_unit_size(args.value_of(options::TO_UNIT).unwrap())?;
|
||||
|
||||
let transform = TransformOptions { from, to };
|
||||
let transform = TransformOptions {
|
||||
from,
|
||||
from_unit,
|
||||
to,
|
||||
to_unit,
|
||||
};
|
||||
|
||||
let padding = match args.value_of(options::PADDING) {
|
||||
Some(s) => s
|
||||
|
@ -222,6 +278,13 @@ pub fn uu_app<'a>() -> Command<'a> {
|
|||
.value_name("UNIT")
|
||||
.default_value(options::FROM_DEFAULT),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::FROM_UNIT)
|
||||
.long(options::FROM_UNIT)
|
||||
.help("specify the input unit size")
|
||||
.value_name("N")
|
||||
.default_value(options::FROM_UNIT_DEFAULT),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::TO)
|
||||
.long(options::TO)
|
||||
|
@ -229,6 +292,13 @@ pub fn uu_app<'a>() -> Command<'a> {
|
|||
.value_name("UNIT")
|
||||
.default_value(options::TO_DEFAULT),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::TO_UNIT)
|
||||
.long(options::TO_UNIT)
|
||||
.help("the output unit size")
|
||||
.value_name("N")
|
||||
.default_value(options::TO_UNIT_DEFAULT),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::PADDING)
|
||||
.long(options::PADDING)
|
||||
|
@ -280,7 +350,10 @@ pub fn uu_app<'a>() -> Command<'a> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{handle_buffer, NumfmtOptions, Range, RoundMethod, TransformOptions, Unit};
|
||||
use super::{
|
||||
handle_buffer, parse_unit_size, parse_unit_size_suffix, NumfmtOptions, Range, RoundMethod,
|
||||
TransformOptions, Unit,
|
||||
};
|
||||
use std::io::{BufReader, Error, ErrorKind, Read};
|
||||
struct MockBuffer {}
|
||||
|
||||
|
@ -294,7 +367,9 @@ mod tests {
|
|||
NumfmtOptions {
|
||||
transform: TransformOptions {
|
||||
from: Unit::None,
|
||||
from_unit: 1,
|
||||
to: Unit::None,
|
||||
to_unit: 1,
|
||||
},
|
||||
padding: 10,
|
||||
header: 1,
|
||||
|
@ -338,4 +413,35 @@ mod tests {
|
|||
let result = handle_buffer(BufReader::new(&input_value[..]), &get_valid_options());
|
||||
assert!(result.is_ok(), "did not return Ok for valid input");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_unit_size() {
|
||||
assert_eq!(1, parse_unit_size("1").unwrap());
|
||||
assert_eq!(1, parse_unit_size("01").unwrap());
|
||||
assert!(parse_unit_size("1.1").is_err());
|
||||
assert!(parse_unit_size("0").is_err());
|
||||
assert!(parse_unit_size("-1").is_err());
|
||||
assert!(parse_unit_size("A").is_err());
|
||||
assert!(parse_unit_size("18446744073709551616").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_unit_size_with_suffix() {
|
||||
assert_eq!(1000, parse_unit_size("K").unwrap());
|
||||
assert_eq!(1024, parse_unit_size("Ki").unwrap());
|
||||
assert_eq!(2000, parse_unit_size("2K").unwrap());
|
||||
assert_eq!(2048, parse_unit_size("2Ki").unwrap());
|
||||
assert!(parse_unit_size("0K").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_unit_size_suffix() {
|
||||
assert_eq!(1, parse_unit_size_suffix("").unwrap());
|
||||
assert_eq!(1000, parse_unit_size_suffix("K").unwrap());
|
||||
assert_eq!(1024, parse_unit_size_suffix("Ki").unwrap());
|
||||
assert_eq!(1000 * 1000, parse_unit_size_suffix("M").unwrap());
|
||||
assert_eq!(1024 * 1024, parse_unit_size_suffix("Mi").unwrap());
|
||||
assert!(parse_unit_size_suffix("Kii").is_none());
|
||||
assert!(parse_unit_size_suffix("A").is_none());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ pub const FIELD: &str = "field";
|
|||
pub const FIELD_DEFAULT: &str = "1";
|
||||
pub const FROM: &str = "from";
|
||||
pub const FROM_DEFAULT: &str = "none";
|
||||
pub const FROM_UNIT: &str = "from-unit";
|
||||
pub const FROM_UNIT_DEFAULT: &str = "1";
|
||||
pub const HEADER: &str = "header";
|
||||
pub const HEADER_DEFAULT: &str = "1";
|
||||
pub const NUMBER: &str = "NUMBER";
|
||||
|
@ -14,10 +16,14 @@ pub const ROUND: &str = "round";
|
|||
pub const SUFFIX: &str = "suffix";
|
||||
pub const TO: &str = "to";
|
||||
pub const TO_DEFAULT: &str = "none";
|
||||
pub const TO_UNIT: &str = "to-unit";
|
||||
pub const TO_UNIT_DEFAULT: &str = "1";
|
||||
|
||||
pub struct TransformOptions {
|
||||
pub from: Unit,
|
||||
pub from_unit: usize,
|
||||
pub to: Unit,
|
||||
pub to_unit: usize,
|
||||
}
|
||||
|
||||
pub struct NumfmtOptions {
|
||||
|
|
|
@ -607,3 +607,35 @@ fn test_invalid_padding_value() {
|
|||
.stderr_contains(format!("invalid padding value '{}'", padding_value));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_unit() {
|
||||
new_ucmd!()
|
||||
.args(&["--from-unit=512", "4"])
|
||||
.succeeds()
|
||||
.stdout_is("2048\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_unit() {
|
||||
new_ucmd!()
|
||||
.args(&["--to-unit=512", "2048"])
|
||||
.succeeds()
|
||||
.stdout_is("4\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_unit_size() {
|
||||
let commands = vec!["from", "to"];
|
||||
let invalid_sizes = vec!["A", "0", "18446744073709551616"];
|
||||
|
||||
for command in commands {
|
||||
for invalid_size in &invalid_sizes {
|
||||
new_ucmd!()
|
||||
.arg(format!("--{}-unit={}", command, invalid_size))
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stderr_contains(format!("invalid unit size: '{}'", invalid_size));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue