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 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};
|
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
|
/// 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, 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.
|
/// 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> {
|
fn transform_to(s: f64, opts: &TransformOptions, round_method: RoundMethod) -> Result<String> {
|
||||||
let (i2, s) = consider_suffix(s, opts, round_method)?;
|
let (i2, s) = consider_suffix(s, &opts.to, round_method)?;
|
||||||
|
let i2 = i2 / (opts.to_unit as f64);
|
||||||
Ok(match s {
|
Ok(match s {
|
||||||
None => format!("{}", i2),
|
None => format!("{}", i2),
|
||||||
Some(s) if i2.abs() < 10.0 => format!("{:.1}{}", i2, DisplayableSuffix(s)),
|
Some(s) if i2.abs() < 10.0 => format!("{:.1}{}", i2, DisplayableSuffix(s)),
|
||||||
|
@ -227,8 +229,8 @@ fn format_string(
|
||||||
};
|
};
|
||||||
|
|
||||||
let number = transform_to(
|
let number = transform_to(
|
||||||
transform_from(source_without_suffix, &options.transform.from)?,
|
transform_from(source_without_suffix, &options.transform)?,
|
||||||
&options.transform.to,
|
&options.transform,
|
||||||
options.round,
|
options.round,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ use crate::options::*;
|
||||||
use crate::units::{Result, Unit};
|
use crate::units::{Result, Unit};
|
||||||
use clap::{crate_version, Arg, ArgMatches, Command};
|
use clap::{crate_version, Arg, ArgMatches, Command};
|
||||||
use std::io::{BufRead, Write};
|
use std::io::{BufRead, Write};
|
||||||
|
use units::{IEC_BASES, SI_BASES};
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::UResult;
|
use uucore::error::UResult;
|
||||||
use uucore::format_usage;
|
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> {
|
fn parse_options(args: &ArgMatches) -> Result<NumfmtOptions> {
|
||||||
let from = parse_unit(args.value_of(options::FROM).unwrap())?;
|
let from = parse_unit(args.value_of(options::FROM).unwrap())?;
|
||||||
let to = parse_unit(args.value_of(options::TO).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) {
|
let padding = match args.value_of(options::PADDING) {
|
||||||
Some(s) => s
|
Some(s) => s
|
||||||
|
@ -222,6 +278,13 @@ pub fn uu_app<'a>() -> Command<'a> {
|
||||||
.value_name("UNIT")
|
.value_name("UNIT")
|
||||||
.default_value(options::FROM_DEFAULT),
|
.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(
|
||||||
Arg::new(options::TO)
|
Arg::new(options::TO)
|
||||||
.long(options::TO)
|
.long(options::TO)
|
||||||
|
@ -229,6 +292,13 @@ pub fn uu_app<'a>() -> Command<'a> {
|
||||||
.value_name("UNIT")
|
.value_name("UNIT")
|
||||||
.default_value(options::TO_DEFAULT),
|
.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(
|
||||||
Arg::new(options::PADDING)
|
Arg::new(options::PADDING)
|
||||||
.long(options::PADDING)
|
.long(options::PADDING)
|
||||||
|
@ -280,7 +350,10 @@ pub fn uu_app<'a>() -> Command<'a> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
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};
|
use std::io::{BufReader, Error, ErrorKind, Read};
|
||||||
struct MockBuffer {}
|
struct MockBuffer {}
|
||||||
|
|
||||||
|
@ -294,7 +367,9 @@ mod tests {
|
||||||
NumfmtOptions {
|
NumfmtOptions {
|
||||||
transform: TransformOptions {
|
transform: TransformOptions {
|
||||||
from: Unit::None,
|
from: Unit::None,
|
||||||
|
from_unit: 1,
|
||||||
to: Unit::None,
|
to: Unit::None,
|
||||||
|
to_unit: 1,
|
||||||
},
|
},
|
||||||
padding: 10,
|
padding: 10,
|
||||||
header: 1,
|
header: 1,
|
||||||
|
@ -338,4 +413,35 @@ mod tests {
|
||||||
let result = handle_buffer(BufReader::new(&input_value[..]), &get_valid_options());
|
let result = handle_buffer(BufReader::new(&input_value[..]), &get_valid_options());
|
||||||
assert!(result.is_ok(), "did not return Ok for valid input");
|
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 FIELD_DEFAULT: &str = "1";
|
||||||
pub const FROM: &str = "from";
|
pub const FROM: &str = "from";
|
||||||
pub const FROM_DEFAULT: &str = "none";
|
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: &str = "header";
|
||||||
pub const HEADER_DEFAULT: &str = "1";
|
pub const HEADER_DEFAULT: &str = "1";
|
||||||
pub const NUMBER: &str = "NUMBER";
|
pub const NUMBER: &str = "NUMBER";
|
||||||
|
@ -14,10 +16,14 @@ pub const ROUND: &str = "round";
|
||||||
pub const SUFFIX: &str = "suffix";
|
pub const SUFFIX: &str = "suffix";
|
||||||
pub const TO: &str = "to";
|
pub const TO: &str = "to";
|
||||||
pub const TO_DEFAULT: &str = "none";
|
pub const TO_DEFAULT: &str = "none";
|
||||||
|
pub const TO_UNIT: &str = "to-unit";
|
||||||
|
pub const TO_UNIT_DEFAULT: &str = "1";
|
||||||
|
|
||||||
pub struct TransformOptions {
|
pub struct TransformOptions {
|
||||||
pub from: Unit,
|
pub from: Unit,
|
||||||
|
pub from_unit: usize,
|
||||||
pub to: Unit,
|
pub to: Unit,
|
||||||
|
pub to_unit: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NumfmtOptions {
|
pub struct NumfmtOptions {
|
||||||
|
|
|
@ -607,3 +607,35 @@ fn test_invalid_padding_value() {
|
||||||
.stderr_contains(format!("invalid padding value '{}'", 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