allow Range to expand to array-like when converting to json (#8047)

# Description
Fixes #8002, which expands ranges `1..3` to expand to array-like when
saving and converting to json. Now,

```
> 1..3 | save foo.json
# foo.json
[
    1,
    2,
    3
]


> 1..3 | to json
[
    1,
    2,
    3
]
```
# User-Facing Changes

_(List of all changes that impact the user experience here. This helps
us keep track of breaking changes.)_

# Tests + Formatting

Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- [X] `cargo fmt --all -- --check` to check standard code formatting
(`cargo fmt --all` applies these changes)
- [X] `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- [X] `cargo test --workspace` to check that all tests pass

# After Submitting

If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
This commit is contained in:
David Matos 2023-02-24 22:31:33 +01:00 committed by GitHub
parent 253b223e65
commit 42f0b55de0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 94 additions and 0 deletions

View File

@ -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)?;

View File

@ -34,6 +34,7 @@ impl Command for ToNuon {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let input = input.try_expand_range()?;
Ok(Value::String {
val: to_nuon(call, input)?,
span: call.head,

View File

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

View File

@ -64,6 +64,7 @@ impl Command for ToXml {
let head = call.head;
let config = engine_state.get_config();
let pretty: Option<Spanned<i64>> = call.get_flag(engine_state, stack, "pretty")?;
let input = input.try_expand_range()?;
to_xml(input, head, pretty, config)
}
}

View File

@ -38,6 +38,7 @@ impl Command for ToYaml {
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let input = input.try_expand_range()?;
to_yaml(input, head)
}
}

View File

@ -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]")
})
}

View File

@ -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"));
}

View File

@ -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<PipelineData, ShellError> {
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<Value> = val.into_range_iter(None)?.collect();
PipelineData::Value(
Value::List {
vals: range_values,
span,
},
None,
)
}
_ => self,
};
Ok(input)
}
/// Consume and print self data immediately.
///