Parse decimal units (#3243)

* parse decimal units

* linting

* stop clippy complaining

* Added tests to parsing decimals

* Fixed bug

* Fixed testing and add more
This commit is contained in:
Luccas Mateus 2021-04-03 05:06:13 -03:00 committed by GitHub
parent e737222a5d
commit 2146ede15d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 176 additions and 41 deletions

View File

@ -187,6 +187,18 @@ fn duration_math() {
assert_eq!(actual.out, "8day"); assert_eq!(actual.out, "8day");
} }
#[test]
fn duration_decimal_math() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
= 0.5mon + 1day
"#
));
assert_eq!(actual.out, "16day");
}
#[test] #[test]
fn duration_math_with_nanoseconds() { fn duration_math_with_nanoseconds() {
let actual = nu!( let actual = nu!(
@ -199,6 +211,18 @@ fn duration_math_with_nanoseconds() {
assert_eq!(actual.out, "7day 10ns"); assert_eq!(actual.out, "7day 10ns");
} }
#[test]
fn duration_decimal_math_with_nanoseconds() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
= 1.5wk + 10ns
"#
));
assert_eq!(actual.out, "10day 10ns");
}
#[test] #[test]
fn duration_math_with_negative() { fn duration_math_with_negative() {
let actual = nu!( let actual = nu!(

View File

@ -325,51 +325,74 @@ fn parse_operator(lite_arg: &Spanned<String>) -> (SpannedExpression, Option<Pars
/// Parse a unit type, eg '10kb' /// Parse a unit type, eg '10kb'
fn parse_unit(lite_arg: &Spanned<String>) -> (SpannedExpression, Option<ParseError>) { fn parse_unit(lite_arg: &Spanned<String>) -> (SpannedExpression, Option<ParseError>) {
fn parse_decimal_str_to_number(decimal: &str) -> Option<i64> {
let string_to_parse = format!("0.{}", decimal);
if let Ok(x) = string_to_parse.parse::<f64>() {
return Some((1_f64 / x) as i64);
}
None
}
let unit_groups = [ let unit_groups = [
(Unit::Byte, vec!["b", "B"]), (Unit::Kilobyte, "KB", Some((Unit::Byte, 1000))),
(Unit::Kilobyte, vec!["kb", "KB", "Kb", "kB"]), (Unit::Megabyte, "MB", Some((Unit::Kilobyte, 1000))),
(Unit::Megabyte, vec!["mb", "MB", "Mb", "mB"]), (Unit::Gigabyte, "GB", Some((Unit::Megabyte, 1000))),
(Unit::Gigabyte, vec!["gb", "GB", "Gb", "gB"]), (Unit::Terabyte, "TB", Some((Unit::Gigabyte, 1000))),
(Unit::Terabyte, vec!["tb", "TB", "Tb", "tB"]), (Unit::Petabyte, "PB", Some((Unit::Terabyte, 1000))),
(Unit::Petabyte, vec!["pb", "PB", "Pb", "pB"]), (Unit::Kibibyte, "KIB", Some((Unit::Byte, 1024))),
(Unit::Kibibyte, vec!["KiB", "kib", "kiB", "Kib"]), (Unit::Mebibyte, "MIB", Some((Unit::Kibibyte, 1024))),
(Unit::Mebibyte, vec!["MiB", "mib", "miB", "Mib"]), (Unit::Gibibyte, "GIB", Some((Unit::Mebibyte, 1024))),
(Unit::Gibibyte, vec!["GiB", "gib", "giB", "Gib"]), (Unit::Byte, "B", None),
(Unit::Nanosecond, vec!["ns"]), (Unit::Nanosecond, "NS", None),
(Unit::Microsecond, vec!["us"]), (Unit::Microsecond, "US", Some((Unit::Nanosecond, 1000))),
(Unit::Millisecond, vec!["ms"]), (Unit::Millisecond, "MS", Some((Unit::Microsecond, 1000))),
(Unit::Second, vec!["sec"]), (Unit::Second, "SEC", Some((Unit::Millisecond, 1000))),
(Unit::Minute, vec!["min"]), (Unit::Minute, "MIN", Some((Unit::Second, 60))),
(Unit::Hour, vec!["hr"]), (Unit::Hour, "HR", Some((Unit::Minute, 60))),
(Unit::Day, vec!["day"]), (Unit::Day, "DAY", Some((Unit::Minute, 1440))),
(Unit::Week, vec!["wk"]), (Unit::Week, "WK", Some((Unit::Day, 7))),
(Unit::Month, vec!["mon"]), (Unit::Month, "MON", Some((Unit::Day, 30))),
(Unit::Year, vec!["yr"]), (Unit::Year, "YR", Some((Unit::Day, 365))),
]; ];
if let Some(unit) = unit_groups
.iter()
.find(|&x| lite_arg.to_uppercase().ends_with(x.1))
{
let mut lhs = lite_arg.item.clone();
for _ in 0..unit.1.len() {
lhs.pop();
}
for unit_group in unit_groups.iter() { let input: Vec<&str> = lhs.split('.').collect();
for unit in unit_group.1.iter() { let (value, unit_to_use) = match &input[..] {
if !lite_arg.item.ends_with(unit) { [number_str] => (number_str.parse::<i64>().ok(), unit.0),
continue; [number_str, decimal_part_str] => match unit.2 {
} Some(unit_to_convert_to) => match (
let mut lhs = lite_arg.item.clone(); number_str.parse::<i64>(),
parse_decimal_str_to_number(decimal_part_str),
for _ in 0..unit.len() { ) {
lhs.pop(); (Ok(number), Some(decimal_part)) => (
} Some(
(number * unit_to_convert_to.1) + (unit_to_convert_to.1 / decimal_part),
// these units are allowed to be signed ),
if let Ok(x) = lhs.parse::<i64>() { unit_to_convert_to.0,
let lhs_span = Span::new(lite_arg.span.start(), lite_arg.span.start() + lhs.len());
let unit_span = Span::new(lite_arg.span.start() + lhs.len(), lite_arg.span.end());
return (
SpannedExpression::new(
Expression::unit(x.spanned(lhs_span), unit_group.0.spanned(unit_span)),
lite_arg.span,
), ),
None, _ => (None, unit.0),
); },
} None => (None, unit.0),
},
_ => (None, unit.0),
};
if let Some(x) = value {
let lhs_span = Span::new(lite_arg.span.start(), lite_arg.span.start() + lhs.len());
let unit_span = Span::new(lite_arg.span.start() + lhs.len(), lite_arg.span.end());
return (
SpannedExpression::new(
Expression::unit(x.spanned(lhs_span), unit_to_use.spanned(unit_span)),
lite_arg.span,
),
None,
);
} }
} }
@ -2179,3 +2202,91 @@ fn unit_parse_byte_units() {
); );
} }
} }
#[test]
fn unit_parse_byte_units_decimal() {
struct TestCase {
string: String,
value: i64,
value_str: String,
unit: Unit,
}
let cases = [
TestCase {
string: String::from("0.25KB"),
value: 250,
value_str: String::from("0.25"),
unit: Unit::Byte,
},
TestCase {
string: String::from("2.5Mb"),
value: 2500,
value_str: String::from("2.5"),
unit: Unit::Kilobyte,
},
TestCase {
string: String::from("0.5Gb"),
value: 500,
value_str: String::from("0.5"),
unit: Unit::Megabyte,
},
TestCase {
string: String::from("811.5Gb"),
value: 811500,
value_str: String::from("811.5"),
unit: Unit::Megabyte,
},
TestCase {
string: String::from("11.5Tb"),
value: 11500,
value_str: String::from("11.5"),
unit: Unit::Gigabyte,
},
TestCase {
string: String::from("12.5Pb"),
value: 12500,
value_str: String::from("12.5"),
unit: Unit::Terabyte,
},
TestCase {
string: String::from("10.5kib"),
value: 10752,
value_str: String::from("10.5"),
unit: Unit::Byte,
},
TestCase {
string: String::from("0.5mib"),
value: 512,
value_str: String::from("0.5"),
unit: Unit::Kibibyte,
},
TestCase {
string: String::from("3.25gib"),
value: 3328,
value_str: String::from("3.25"),
unit: Unit::Mebibyte,
},
];
for case in cases.iter() {
let input_len = case.string.len();
let value_len = case.value_str.to_string().len();
let input = case.string.clone().spanned(Span::new(0, input_len));
let result = parse_unit(&input);
assert_eq!(result.1, None);
assert_eq!(
result.0.expr,
Expression::unit(
Spanned {
span: Span::new(0, value_len),
item: case.value
},
Spanned {
span: Span::new(value_len, input_len),
item: case.unit
}
)
);
}
}