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:
Bahex 2025-01-07 23:29:39 +03:00 committed by GitHub
parent 8e41a308cd
commit 16e174be7e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 82 additions and 73 deletions

View File

@ -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]

View 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)
}
}
}

View File

@ -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::*;

View File

@ -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(()),
}
}
}

View File

@ -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![];