diff --git a/crates/nu-command/src/formats/to/json.rs b/crates/nu-command/src/formats/to/json.rs index f598de4a6a..46cd0b92a2 100644 --- a/crates/nu-command/src/formats/to/json.rs +++ b/crates/nu-command/src/formats/to/json.rs @@ -48,6 +48,8 @@ impl Command for ToJson { let use_tabs = call.has_flag("tabs"); let span = call.head; + // allow ranges to expand and turn into array + let input = input.try_expand_range()?; let value = input.into_value(span); let json_value = value_to_json_value(&value)?; diff --git a/crates/nu-command/src/formats/to/nuon.rs b/crates/nu-command/src/formats/to/nuon.rs index abe34c0548..1b4334196f 100644 --- a/crates/nu-command/src/formats/to/nuon.rs +++ b/crates/nu-command/src/formats/to/nuon.rs @@ -34,6 +34,7 @@ impl Command for ToNuon { call: &Call, input: PipelineData, ) -> Result { + let input = input.try_expand_range()?; Ok(Value::String { val: to_nuon(call, input)?, span: call.head, diff --git a/crates/nu-command/src/formats/to/text.rs b/crates/nu-command/src/formats/to/text.rs index 22085780b1..e6b6c3aaf6 100644 --- a/crates/nu-command/src/formats/to/text.rs +++ b/crates/nu-command/src/formats/to/text.rs @@ -39,6 +39,7 @@ impl Command for ToText { } else { "\n" }; + let input = input.try_expand_range()?; if let PipelineData::ListStream(stream, _) = input { Ok(PipelineData::ExternalStream { diff --git a/crates/nu-command/src/formats/to/xml.rs b/crates/nu-command/src/formats/to/xml.rs index cd20b15bdb..7b9669a53b 100644 --- a/crates/nu-command/src/formats/to/xml.rs +++ b/crates/nu-command/src/formats/to/xml.rs @@ -64,6 +64,7 @@ impl Command for ToXml { let head = call.head; let config = engine_state.get_config(); let pretty: Option> = call.get_flag(engine_state, stack, "pretty")?; + let input = input.try_expand_range()?; to_xml(input, head, pretty, config) } } diff --git a/crates/nu-command/src/formats/to/yaml.rs b/crates/nu-command/src/formats/to/yaml.rs index a4b9242378..b83dadf8ae 100644 --- a/crates/nu-command/src/formats/to/yaml.rs +++ b/crates/nu-command/src/formats/to/yaml.rs @@ -38,6 +38,7 @@ impl Command for ToYaml { input: PipelineData, ) -> Result { let head = call.head; + let input = input.try_expand_range()?; to_yaml(input, head) } } diff --git a/crates/nu-command/tests/commands/save.rs b/crates/nu-command/tests/commands/save.rs index 0e9fa486de..b28caee570 100644 --- a/crates/nu-command/tests/commands/save.rs +++ b/crates/nu-command/tests/commands/save.rs @@ -283,3 +283,21 @@ fn save_list_stream() { assert_eq!(actual, "a\nb\nc\nd\n") }) } + +#[test] +fn writes_out_range() { + Playground::setup("save_test_14", |dirs, sandbox| { + sandbox.with_files(vec![]); + + let expected_file = dirs.test().join("list_sample.json"); + + nu!( + cwd: dirs.root(), + r#"1..3 | save save_test_14/list_sample.json"#, + ); + + let actual = file_contents(expected_file); + println!("{actual}"); + assert_eq!(actual, "[\n 1,\n 2,\n 3\n]") + }) +} diff --git a/crates/nu-command/tests/format_conversions/json.rs b/crates/nu-command/tests/format_conversions/json.rs index 75a6ced766..1f0fb81198 100644 --- a/crates/nu-command/tests/format_conversions/json.rs +++ b/crates/nu-command/tests/format_conversions/json.rs @@ -108,3 +108,26 @@ fn top_level_values_from_json() { assert_eq!(actual.out, type_name); } } + +#[test] +fn ranges_to_json_as_array() { + let value = r#"[ 1, 2, 3]"#; + let actual = nu!(r#"1..3 | to json"#); + assert_eq!(actual.out, value); +} + +#[test] +fn unbounded_from_in_range_fails() { + let actual = nu!(r#"1.. | to json"#); + assert!(actual.err.contains("Cannot create range")); +} + +#[test] +fn inf_in_range_fails() { + let actual = nu!(r#"inf..5 | to json"#); + assert!(actual.err.contains("Cannot create range")); + let actual = nu!(r#"5..inf | to json"#); + assert!(actual.err.contains("Cannot create range")); + let actual = nu!(r#"-inf..inf | to json"#); + assert!(actual.err.contains("Cannot create range")); +} diff --git a/crates/nu-protocol/src/pipeline_data.rs b/crates/nu-protocol/src/pipeline_data.rs index d70433d1b7..506baa0b5e 100644 --- a/crates/nu-protocol/src/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline_data.rs @@ -604,6 +604,53 @@ impl PipelineData { (self, false) } } + /// Try to convert Value from Value::Range to Value::List. + /// This is useful to expand Value::Range into array notation, specifically when + /// converting `to json` or `to nuon`. + /// `1..3 | to XX -> [1,2,3]` + pub fn try_expand_range(self) -> Result { + let input = match self { + PipelineData::Value(Value::Range { val, span }, ..) => { + match (&val.to, &val.from) { + (Value::Float { val, .. }, _) | (_, Value::Float { val, .. }) => { + if *val == f64::INFINITY || *val == f64::NEG_INFINITY { + return Err(ShellError::GenericError( + "Cannot create range".into(), + "Infinity is not allowed when converting to json".into(), + Some(span), + Some("Consider removing infinity".into()), + vec![], + )); + } + } + (Value::Int { val, span }, _) => { + if *val == i64::MAX || *val == i64::MIN { + return Err(ShellError::GenericError( + "Cannot create range".into(), + "Unbounded ranges are not allowed when converting to json".into(), + Some(*span), + Some( + "Consider using ranges with valid start and end point.".into(), + ), + vec![], + )); + } + } + _ => (), + } + let range_values: Vec = val.into_range_iter(None)?.collect(); + PipelineData::Value( + Value::List { + vals: range_values, + span, + }, + None, + ) + } + _ => self, + }; + Ok(input) + } /// Consume and print self data immediately. ///