mirror of
https://github.com/nushell/nushell.git
synced 2025-01-08 23:40:17 +01:00
fix nuon conversions of range values (#14687)
# Description Currently the step size of range values are discarded when converting to nuon. This PR fixes that and makes `to nuon | from nuon` round trips work. # User-Facing Changes `to nuon` conversion of `range` values now include the step size # Tests + Formatting Added some additional tests to cover inclusive/exclusive integer/float and step size cases.
This commit is contained in:
parent
8e41a308cd
commit
16e174be7e
@ -179,27 +179,56 @@ fn to_nuon_records() {
|
||||
|
||||
#[test]
|
||||
fn to_nuon_range() {
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
1..42
|
||||
| to nuon
|
||||
"#
|
||||
));
|
||||
|
||||
let actual = nu!(r#"1..42 | to nuon"#);
|
||||
assert_eq!(actual.out, "1..42");
|
||||
|
||||
let actual = nu!(r#"1..<42 | to nuon"#);
|
||||
assert_eq!(actual.out, "1..<42");
|
||||
|
||||
let actual = nu!(r#"1..4..42 | to nuon"#);
|
||||
assert_eq!(actual.out, "1..4..42");
|
||||
|
||||
let actual = nu!(r#"1..4..<42 | to nuon"#);
|
||||
assert_eq!(actual.out, "1..4..<42");
|
||||
|
||||
let actual = nu!(r#"1.0..42.0 | to nuon"#);
|
||||
assert_eq!(actual.out, "1.0..42.0");
|
||||
|
||||
let actual = nu!(r#"1.0..<42.0 | to nuon"#);
|
||||
assert_eq!(actual.out, "1.0..<42.0");
|
||||
|
||||
let actual = nu!(r#"1.0..4.0..42.0 | to nuon"#);
|
||||
assert_eq!(actual.out, "1.0..4.0..42.0");
|
||||
|
||||
let actual = nu!(r#"1.0..4.0..<42.0 | to nuon"#);
|
||||
assert_eq!(actual.out, "1.0..4.0..<42.0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_nuon_range() {
|
||||
let actual = nu!(pipeline(
|
||||
r#"
|
||||
"1..42"
|
||||
| from nuon
|
||||
| describe
|
||||
"#
|
||||
));
|
||||
let actual = nu!(r#"'1..42' | from nuon | to nuon"#);
|
||||
assert_eq!(actual.out, "1..42");
|
||||
|
||||
assert_eq!(actual.out, "range");
|
||||
let actual = nu!(r#"'1..<42' | from nuon | to nuon"#);
|
||||
assert_eq!(actual.out, "1..<42");
|
||||
|
||||
let actual = nu!(r#"'1..4..42' | from nuon | to nuon"#);
|
||||
assert_eq!(actual.out, "1..4..42");
|
||||
|
||||
let actual = nu!(r#"'1..4..<42' | from nuon | to nuon"#);
|
||||
assert_eq!(actual.out, "1..4..<42");
|
||||
|
||||
let actual = nu!(r#"'1.0..42.0' | from nuon | to nuon"#);
|
||||
assert_eq!(actual.out, "1.0..42.0");
|
||||
|
||||
let actual = nu!(r#"'1.0..<42.0' | from nuon | to nuon"#);
|
||||
assert_eq!(actual.out, "1.0..<42.0");
|
||||
|
||||
let actual = nu!(r#"'1.0..4.0..42.0' | from nuon | to nuon"#);
|
||||
assert_eq!(actual.out, "1.0..4.0..42.0");
|
||||
|
||||
let actual = nu!(r#"'1.0..4.0..<42.0' | from nuon | to nuon"#);
|
||||
assert_eq!(actual.out, "1.0..4.0..<42.0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
16
crates/nu-protocol/src/value/format.rs
Normal file
16
crates/nu-protocol/src/value/format.rs
Normal file
@ -0,0 +1,16 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
/// A f64 wrapper that formats whole numbers with a decimal point.
|
||||
pub struct ObviousFloat(pub f64);
|
||||
|
||||
impl Display for ObviousFloat {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
let val = self.0;
|
||||
// This serialises these as 'nan', 'inf' and '-inf', respectively.
|
||||
if val.round() == val && val.is_finite() {
|
||||
write!(f, "{}.0", val)
|
||||
} else {
|
||||
write!(f, "{}", val)
|
||||
}
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ mod range;
|
||||
#[cfg(test)]
|
||||
mod test_derive;
|
||||
|
||||
pub mod format;
|
||||
pub mod record;
|
||||
pub use custom_value::CustomValue;
|
||||
pub use duration::*;
|
||||
|
@ -183,12 +183,14 @@ mod int_range {
|
||||
|
||||
impl Display for IntRange {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
// what about self.step?
|
||||
let start = self.start;
|
||||
write!(f, "{}..", self.start)?;
|
||||
if self.step != 1 {
|
||||
write!(f, "{}..", self.start + self.step)?;
|
||||
}
|
||||
match self.end {
|
||||
Bound::Included(end) => write!(f, "{start}..{end}"),
|
||||
Bound::Excluded(end) => write!(f, "{start}..<{end}"),
|
||||
Bound::Unbounded => write!(f, "{start}.."),
|
||||
Bound::Included(end) => write!(f, "{end}"),
|
||||
Bound::Excluded(end) => write!(f, "<{end}"),
|
||||
Bound::Unbounded => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -228,7 +230,10 @@ mod int_range {
|
||||
}
|
||||
|
||||
mod float_range {
|
||||
use crate::{ast::RangeInclusion, IntRange, Range, ShellError, Signals, Span, Value};
|
||||
use crate::{
|
||||
ast::RangeInclusion, format::ObviousFloat, IntRange, Range, ShellError, Signals, Span,
|
||||
Value,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{cmp::Ordering, fmt::Display, ops::Bound};
|
||||
|
||||
@ -434,12 +439,14 @@ mod float_range {
|
||||
|
||||
impl Display for FloatRange {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
// what about self.step?
|
||||
let start = self.start;
|
||||
write!(f, "{}..", ObviousFloat(self.start))?;
|
||||
if self.step != 1f64 {
|
||||
write!(f, "{}..", ObviousFloat(self.start + self.step))?;
|
||||
}
|
||||
match self.end {
|
||||
Bound::Included(end) => write!(f, "{start}..{end}"),
|
||||
Bound::Excluded(end) => write!(f, "{start}..<{end}"),
|
||||
Bound::Unbounded => write!(f, "{start}.."),
|
||||
Bound::Included(end) => write!(f, "{}", ObviousFloat(end)),
|
||||
Bound::Excluded(end) => write!(f, "<{}", ObviousFloat(end)),
|
||||
Bound::Unbounded => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
use core::fmt::Write;
|
||||
use nu_engine::get_columns;
|
||||
use nu_protocol::format::ObviousFloat;
|
||||
use nu_protocol::{engine::EngineState, Range, ShellError, Span, Value};
|
||||
use nu_utils::{escape_quote_string, needs_quoting};
|
||||
|
||||
use std::ops::Bound;
|
||||
|
||||
/// control the way Nushell [`Value`] is converted to NUON data
|
||||
pub enum ToStyle {
|
||||
/// no indentation at all
|
||||
@ -138,14 +137,7 @@ fn value_to_string(
|
||||
Value::Error { error, .. } => Err(*error.clone()),
|
||||
// FIXME: make filesizes use the shortest lossless representation.
|
||||
Value::Filesize { val, .. } => Ok(format!("{}b", val.get())),
|
||||
Value::Float { val, .. } => {
|
||||
// This serialises these as 'nan', 'inf' and '-inf', respectively.
|
||||
if &val.round() == val && val.is_finite() {
|
||||
Ok(format!("{}.0", *val))
|
||||
} else {
|
||||
Ok(val.to_string())
|
||||
}
|
||||
}
|
||||
Value::Float { val, .. } => Ok(ObviousFloat(*val).to_string()),
|
||||
Value::Int { val, .. } => Ok(val.to_string()),
|
||||
Value::List { vals, .. } => {
|
||||
let headers = get_columns(vals);
|
||||
@ -213,43 +205,7 @@ fn value_to_string(
|
||||
Value::Nothing { .. } => Ok("null".to_string()),
|
||||
Value::Range { val, .. } => match **val {
|
||||
Range::IntRange(range) => Ok(range.to_string()),
|
||||
Range::FloatRange(range) => {
|
||||
let start = value_to_string(
|
||||
engine_state,
|
||||
&Value::float(range.start(), span),
|
||||
span,
|
||||
depth + 1,
|
||||
indent,
|
||||
serialize_types,
|
||||
)?;
|
||||
match range.end() {
|
||||
Bound::Included(end) => Ok(format!(
|
||||
"{}..{}",
|
||||
start,
|
||||
value_to_string(
|
||||
engine_state,
|
||||
&Value::float(end, span),
|
||||
span,
|
||||
depth + 1,
|
||||
indent,
|
||||
serialize_types,
|
||||
)?
|
||||
)),
|
||||
Bound::Excluded(end) => Ok(format!(
|
||||
"{}..<{}",
|
||||
start,
|
||||
value_to_string(
|
||||
engine_state,
|
||||
&Value::float(end, span),
|
||||
span,
|
||||
depth + 1,
|
||||
indent,
|
||||
serialize_types,
|
||||
)?
|
||||
)),
|
||||
Bound::Unbounded => Ok(format!("{start}..",)),
|
||||
}
|
||||
}
|
||||
Range::FloatRange(range) => Ok(range.to_string()),
|
||||
},
|
||||
Value::Record { val, .. } => {
|
||||
let mut collection = vec![];
|
||||
|
Loading…
Reference in New Issue
Block a user