forked from extern/nushell
changed the way durations and filesizes are parsed (#6640)
This commit is contained in:
parent
6aa8a0073b
commit
6486364610
@ -402,6 +402,18 @@ fn duration_decimal_math_with_all_units() {
|
||||
assert_eq!(actual.out, "1wk 3day 8hr 10min 16sec 121ms 11µs 12ns");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_decimal_dans_test() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
3.14sec
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "3sec 140ms");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_math_with_negative() {
|
||||
let actual = nu!(
|
||||
|
@ -1538,20 +1538,36 @@ fn compute(size: i64, unit: Unit, span: Span) -> Value {
|
||||
val: size * 1000 * 1000 * 1000,
|
||||
span,
|
||||
},
|
||||
Unit::Minute => Value::Duration {
|
||||
val: size * 1000 * 1000 * 1000 * 60,
|
||||
span,
|
||||
Unit::Minute => match size.checked_mul(1000 * 1000 * 1000 * 60) {
|
||||
Some(val) => Value::Duration { val, span },
|
||||
None => Value::Error {
|
||||
error: ShellError::GenericError(
|
||||
"duration too large".into(),
|
||||
"duration too large".into(),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
),
|
||||
},
|
||||
},
|
||||
Unit::Hour => Value::Duration {
|
||||
val: size * 1000 * 1000 * 1000 * 60 * 60,
|
||||
span,
|
||||
Unit::Hour => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60) {
|
||||
Some(val) => Value::Duration { val, span },
|
||||
None => Value::Error {
|
||||
error: ShellError::GenericError(
|
||||
"duration too large".into(),
|
||||
"duration too large".into(),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
),
|
||||
},
|
||||
},
|
||||
Unit::Day => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24) {
|
||||
Some(val) => Value::Duration { val, span },
|
||||
None => Value::Error {
|
||||
error: ShellError::GenericError(
|
||||
"day duration too large".into(),
|
||||
"day duration too large".into(),
|
||||
"duration too large".into(),
|
||||
"duration too large".into(),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
@ -1562,8 +1578,8 @@ fn compute(size: i64, unit: Unit, span: Span) -> Value {
|
||||
Some(val) => Value::Duration { val, span },
|
||||
None => Value::Error {
|
||||
error: ShellError::GenericError(
|
||||
"week duration too large".into(),
|
||||
"week duration too large".into(),
|
||||
"duration too large".into(),
|
||||
"duration too large".into(),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
|
@ -2175,23 +2175,50 @@ pub fn parse_duration(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_duration_bytes(bytes: &[u8], span: Span) -> Option<Expression> {
|
||||
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);
|
||||
// Borrowed from libm at https://github.com/rust-lang/libm/blob/master/src/math/modf.rs
|
||||
pub fn modf(x: f64) -> (f64, f64) {
|
||||
let rv2: f64;
|
||||
let mut u = x.to_bits();
|
||||
let e = ((u >> 52 & 0x7ff) as i32) - 0x3ff;
|
||||
|
||||
/* no fractional part */
|
||||
if e >= 52 {
|
||||
rv2 = x;
|
||||
if e == 0x400 && (u << 12) != 0 {
|
||||
/* nan */
|
||||
return (x, rv2);
|
||||
}
|
||||
None
|
||||
u &= 1 << 63;
|
||||
return (f64::from_bits(u), rv2);
|
||||
}
|
||||
|
||||
if bytes.is_empty() || (!bytes[0].is_ascii_digit() && bytes[0] != b'-') {
|
||||
/* no integral part*/
|
||||
if e < 0 {
|
||||
u &= 1 << 63;
|
||||
rv2 = f64::from_bits(u);
|
||||
return (x, rv2);
|
||||
}
|
||||
|
||||
let mask = ((!0) >> 12) >> e;
|
||||
if (u & mask) == 0 {
|
||||
rv2 = x;
|
||||
u &= 1 << 63;
|
||||
return (f64::from_bits(u), rv2);
|
||||
}
|
||||
u &= !mask;
|
||||
rv2 = f64::from_bits(u);
|
||||
(x - rv2, rv2)
|
||||
}
|
||||
|
||||
pub fn parse_duration_bytes(num_with_unit_bytes: &[u8], span: Span) -> Option<Expression> {
|
||||
if num_with_unit_bytes.is_empty()
|
||||
|| (!num_with_unit_bytes[0].is_ascii_digit() && num_with_unit_bytes[0] != b'-')
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let token = String::from_utf8_lossy(bytes).to_string();
|
||||
|
||||
let upper = token.to_uppercase();
|
||||
|
||||
let num_with_unit = String::from_utf8_lossy(num_with_unit_bytes).to_string();
|
||||
let uppercase_num_with_unit = num_with_unit.to_uppercase();
|
||||
let unit_groups = [
|
||||
(Unit::Nanosecond, "NS", None),
|
||||
(Unit::Microsecond, "US", Some((Unit::Nanosecond, 1000))),
|
||||
@ -2202,34 +2229,33 @@ pub fn parse_duration_bytes(bytes: &[u8], span: Span) -> Option<Expression> {
|
||||
(Unit::Day, "DAY", Some((Unit::Minute, 1440))),
|
||||
(Unit::Week, "WK", Some((Unit::Day, 7))),
|
||||
];
|
||||
if let Some(unit) = unit_groups.iter().find(|&x| upper.ends_with(x.1)) {
|
||||
let mut lhs = token;
|
||||
|
||||
if let Some(unit) = unit_groups
|
||||
.iter()
|
||||
.find(|&x| uppercase_num_with_unit.ends_with(x.1))
|
||||
{
|
||||
let mut lhs = num_with_unit;
|
||||
for _ in 0..unit.1.len() {
|
||||
lhs.pop();
|
||||
}
|
||||
|
||||
let input: Vec<&str> = lhs.split('.').collect();
|
||||
let (value, unit_to_use) = match &input[..] {
|
||||
[number_str] => (number_str.parse::<i64>().ok(), unit.0),
|
||||
[number_str, decimal_part_str] => match unit.2 {
|
||||
Some(unit_to_convert_to) => match (
|
||||
number_str.parse::<i64>(),
|
||||
parse_decimal_str_to_number(decimal_part_str),
|
||||
) {
|
||||
(Ok(number), Some(decimal_part)) => (
|
||||
Some(
|
||||
(number * unit_to_convert_to.1) + (unit_to_convert_to.1 / decimal_part),
|
||||
),
|
||||
unit_to_convert_to.0,
|
||||
),
|
||||
_ => (None, unit.0),
|
||||
},
|
||||
None => (None, unit.0),
|
||||
},
|
||||
_ => (None, unit.0),
|
||||
let (decimal_part, number_part) = modf(match lhs.parse::<f64>() {
|
||||
Ok(x) => x,
|
||||
Err(_) => return None,
|
||||
});
|
||||
|
||||
let (num, unit_to_use) = match unit.2 {
|
||||
Some(unit_to_convert_to) => (
|
||||
Some(
|
||||
((number_part * unit_to_convert_to.1 as f64)
|
||||
+ (decimal_part * unit_to_convert_to.1 as f64)) as i64,
|
||||
),
|
||||
unit_to_convert_to.0,
|
||||
),
|
||||
None => (Some(number_part as i64), unit.0),
|
||||
};
|
||||
|
||||
if let Some(x) = value {
|
||||
if let Some(x) = num {
|
||||
trace!("-- found {} {:?}", x, unit_to_use);
|
||||
|
||||
let lhs_span = Span::new(span.start, span.start + lhs.len());
|
||||
@ -2262,33 +2288,32 @@ pub fn parse_filesize(
|
||||
working_set: &StateWorkingSet,
|
||||
span: Span,
|
||||
) -> (Expression, Option<ParseError>) {
|
||||
trace!("parsing: duration");
|
||||
|
||||
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
|
||||
}
|
||||
trace!("parsing: filesize");
|
||||
|
||||
let bytes = working_set.get_span_contents(span);
|
||||
|
||||
if bytes.is_empty() || (!bytes[0].is_ascii_digit() && bytes[0] != b'-') {
|
||||
return (
|
||||
match parse_filesize_bytes(bytes, span) {
|
||||
Some(expression) => (expression, None),
|
||||
None => (
|
||||
garbage(span),
|
||||
Some(ParseError::Mismatch(
|
||||
"filesize".into(),
|
||||
"non-filesize unit".into(),
|
||||
span,
|
||||
)),
|
||||
);
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_filesize_bytes(num_with_unit_bytes: &[u8], span: Span) -> Option<Expression> {
|
||||
if num_with_unit_bytes.is_empty()
|
||||
|| (!num_with_unit_bytes[0].is_ascii_digit() && num_with_unit_bytes[0] != b'-')
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let token = String::from_utf8_lossy(bytes).to_string();
|
||||
|
||||
let upper = token.to_uppercase();
|
||||
|
||||
let num_with_unit = String::from_utf8_lossy(num_with_unit_bytes).to_string();
|
||||
let uppercase_num_with_unit = num_with_unit.to_uppercase();
|
||||
let unit_groups = [
|
||||
(Unit::Kilobyte, "KB", Some((Unit::Byte, 1000))),
|
||||
(Unit::Megabyte, "MB", Some((Unit::Kilobyte, 1000))),
|
||||
@ -2306,69 +2331,58 @@ pub fn parse_filesize(
|
||||
(Unit::Zebibyte, "ZIB", Some((Unit::Exbibyte, 1024))),
|
||||
(Unit::Byte, "B", None),
|
||||
];
|
||||
if let Some(unit) = unit_groups.iter().find(|&x| upper.ends_with(x.1)) {
|
||||
let mut lhs = token;
|
||||
|
||||
if let Some(unit) = unit_groups
|
||||
.iter()
|
||||
.find(|&x| uppercase_num_with_unit.ends_with(x.1))
|
||||
{
|
||||
let mut lhs = num_with_unit;
|
||||
for _ in 0..unit.1.len() {
|
||||
lhs.pop();
|
||||
}
|
||||
|
||||
let input: Vec<&str> = lhs.split('.').collect();
|
||||
let (value, unit_to_use) = match &input[..] {
|
||||
[number_str] => (number_str.parse::<i64>().ok(), unit.0),
|
||||
[number_str, decimal_part_str] => match unit.2 {
|
||||
Some(unit_to_convert_to) => match (
|
||||
number_str.parse::<i64>(),
|
||||
parse_decimal_str_to_number(decimal_part_str),
|
||||
) {
|
||||
(Ok(number), Some(decimal_part)) => (
|
||||
Some(
|
||||
(number * unit_to_convert_to.1) + (unit_to_convert_to.1 / decimal_part),
|
||||
),
|
||||
unit_to_convert_to.0,
|
||||
),
|
||||
_ => (None, unit.0),
|
||||
},
|
||||
None => (None, unit.0),
|
||||
},
|
||||
_ => (None, unit.0),
|
||||
let (decimal_part, number_part) = modf(match lhs.parse::<f64>() {
|
||||
Ok(x) => x,
|
||||
Err(_) => return None,
|
||||
});
|
||||
|
||||
let (num, unit_to_use) = match unit.2 {
|
||||
Some(unit_to_convert_to) => (
|
||||
Some(
|
||||
((number_part * unit_to_convert_to.1 as f64)
|
||||
+ (decimal_part * unit_to_convert_to.1 as f64)) as i64,
|
||||
),
|
||||
unit_to_convert_to.0,
|
||||
),
|
||||
None => (Some(number_part as i64), unit.0),
|
||||
};
|
||||
|
||||
if let Some(x) = value {
|
||||
if let Some(x) = num {
|
||||
trace!("-- found {} {:?}", x, unit_to_use);
|
||||
|
||||
let lhs_span = Span::new(span.start, span.start + lhs.len());
|
||||
let unit_span = Span::new(span.start + lhs.len(), span.end);
|
||||
return (
|
||||
Expression {
|
||||
expr: Expr::ValueWithUnit(
|
||||
Box::new(Expression {
|
||||
expr: Expr::Int(x),
|
||||
span: lhs_span,
|
||||
ty: Type::Number,
|
||||
custom_completion: None,
|
||||
}),
|
||||
Spanned {
|
||||
item: unit_to_use,
|
||||
span: unit_span,
|
||||
},
|
||||
),
|
||||
span,
|
||||
ty: Type::Filesize,
|
||||
custom_completion: None,
|
||||
},
|
||||
None,
|
||||
);
|
||||
return Some(Expression {
|
||||
expr: Expr::ValueWithUnit(
|
||||
Box::new(Expression {
|
||||
expr: Expr::Int(x),
|
||||
span: lhs_span,
|
||||
ty: Type::Number,
|
||||
custom_completion: None,
|
||||
}),
|
||||
Spanned {
|
||||
item: unit_to_use,
|
||||
span: unit_span,
|
||||
},
|
||||
),
|
||||
span,
|
||||
ty: Type::Filesize,
|
||||
custom_completion: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
(
|
||||
garbage(span),
|
||||
Some(ParseError::Mismatch(
|
||||
"filesize".into(),
|
||||
"non-filesize unit".into(),
|
||||
span,
|
||||
)),
|
||||
)
|
||||
None
|
||||
}
|
||||
|
||||
pub fn parse_glob_pattern(
|
||||
|
Loading…
Reference in New Issue
Block a user