diff --git a/crates/nu-command/tests/format_conversions/nuon.rs b/crates/nu-command/tests/format_conversions/nuon.rs index a7a423a0d6..ca23e6823b 100644 --- a/crates/nu-command/tests/format_conversions/nuon.rs +++ b/crates/nu-command/tests/format_conversions/nuon.rs @@ -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] diff --git a/crates/nu-protocol/src/value/format.rs b/crates/nu-protocol/src/value/format.rs new file mode 100644 index 0000000000..a4f07ffdf6 --- /dev/null +++ b/crates/nu-protocol/src/value/format.rs @@ -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) + } + } +} diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 09b9a5f60a..b9c942d3b0 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -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::*; diff --git a/crates/nu-protocol/src/value/range.rs b/crates/nu-protocol/src/value/range.rs index 77aa5a60ef..c4d6d4655e 100644 --- a/crates/nu-protocol/src/value/range.rs +++ b/crates/nu-protocol/src/value/range.rs @@ -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(()), } } } diff --git a/crates/nuon/src/to.rs b/crates/nuon/src/to.rs index 13244bfdcd..332dcaf98e 100644 --- a/crates/nuon/src/to.rs +++ b/crates/nuon/src/to.rs @@ -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![];