Skip to main content

nu_command/formats/to/
nuon.rs

1use nu_engine::command_prelude::*;
2
3#[derive(Clone)]
4pub struct ToNuon;
5
6impl Command for ToNuon {
7    fn name(&self) -> &str {
8        "to nuon"
9    }
10
11    fn signature(&self) -> Signature {
12        Signature::build("to nuon")
13            .input_output_types(vec![(Type::Any, Type::String)])
14            .switch(
15                "raw",
16                "Remove all of the whitespace (overwrites -i, -t, and -p).",
17                Some('r'),
18            )
19            .named(
20                "indent",
21                SyntaxShape::Number,
22                "Specify indentation width.",
23                Some('i'),
24            )
25            .named(
26                "tabs",
27                SyntaxShape::Number,
28                "Specify indentation tab quantity.",
29                Some('t'),
30            )
31            .switch(
32                "serialize",
33                "Serialize nushell types that cannot be deserialized.",
34                Some('s'),
35            )
36            .switch(
37                "raw-strings",
38                "Use raw string syntax (r#'...'#) for strings with quotes or backslashes.",
39                Some('R'),
40            )
41            .switch(
42                "list-of-records",
43                "Serialize table values as list-of-records instead of table syntax.",
44                Some('l'),
45            )
46            .switch(
47                "no-commas",
48                "Do not use commas between items in tables and lists.",
49                Some('c'),
50            )
51            .switch(
52                "pretty",
53                "Format output with indentation and aligned table columns.",
54                Some('p'),
55            )
56            .category(Category::Formats)
57    }
58
59    fn description(&self) -> &str {
60        "Converts table data into Nuon (Nushell Object Notation) text."
61    }
62
63    fn run(
64        &self,
65        engine_state: &EngineState,
66        stack: &mut Stack,
67        call: &Call,
68        mut input: PipelineData,
69    ) -> Result<PipelineData, ShellError> {
70        let serialize_types = call.has_flag(engine_state, stack, "serialize")?;
71        let raw_strings = call.has_flag(engine_state, stack, "raw-strings")?;
72        let list_of_records = call.has_flag(engine_state, stack, "list-of-records")?;
73        let no_commas = call.has_flag(engine_state, stack, "no-commas")?;
74        let pretty = call.has_flag(engine_state, stack, "pretty")?;
75        let style = if call.has_flag(engine_state, stack, "raw")? {
76            nuon::ToStyle::Raw
77        } else if let Some(t) = call.get_flag(engine_state, stack, "tabs")? {
78            nuon::ToStyle::Tabs(t)
79        } else if let Some(i) = call.get_flag(engine_state, stack, "indent")? {
80            nuon::ToStyle::Spaces(i)
81        } else if pretty {
82            nuon::ToStyle::Spaces(2)
83        } else {
84            nuon::ToStyle::Default
85        };
86
87        let span = call.head;
88        let metadata = input
89            .take_metadata()
90            .unwrap_or_default()
91            .with_content_type(Some("application/x-nuon".into()));
92
93        let value = input.into_value(span)?;
94
95        let config = nuon::ToNuonConfig::default()
96            .style(style)
97            .span(Some(span))
98            .serialize_types(serialize_types)
99            .raw_strings(raw_strings)
100            .list_of_records(list_of_records)
101            .use_commas(!no_commas);
102
103        match nuon::to_nuon(engine_state, &value, config) {
104            Ok(serde_nuon_string) => Ok(Value::string(serde_nuon_string, span)
105                .into_pipeline_data_with_metadata(Some(metadata))),
106            Err(error) => {
107                Ok(Value::error(error, span).into_pipeline_data_with_metadata(Some(metadata)))
108            }
109        }
110    }
111
112    fn examples(&self) -> Vec<Example<'_>> {
113        vec![
114            Example {
115                description: "Outputs a NUON string representing the contents of this list, compact by default.",
116                example: "[1 2 3] | to nuon",
117                result: Some(Value::test_string("[1, 2, 3]")),
118            },
119            Example {
120                description: "Outputs a NUON array of ints, with pretty indentation.",
121                example: "[1 2 3] | to nuon --indent 2",
122                result: Some(Value::test_string("[\n  1,\n  2,\n  3\n]")),
123            },
124            Example {
125                description: "Overwrite any set option with --raw.",
126                example: "[1 2 3] | to nuon --indent 2 --raw",
127                result: Some(Value::test_string("[1,2,3]")),
128            },
129            Example {
130                description: "A more complex record with multiple data types.",
131                example: "{date: 2000-01-01, data: [1 [2 3] 4.56]} | to nuon --indent 2",
132                result: Some(Value::test_string(
133                    "{\n  date: 2000-01-01T00:00:00+00:00,\n  data: [\n    1,\n    [\n      2,\n      3\n    ],\n    4.56\n  ]\n}",
134                )),
135            },
136            Example {
137                description: "A more complex record with --raw.",
138                example: "{date: 2000-01-01, data: [1 [2 3] 4.56]} | to nuon --raw",
139                result: Some(Value::test_string(
140                    "{date:2000-01-01T00:00:00+00:00,data:[1,[2,3],4.56]}",
141                )),
142            },
143            Example {
144                description: "Use raw string syntax for strings with quotes or backslashes.",
145                example: r#"'hello "world"' | to nuon --raw-strings"#,
146                result: Some(Value::test_string(r#"r#'hello "world"'#"#)),
147            },
148            Example {
149                description: "Serialize table values as a list of records instead of table syntax.",
150                example: "[[a, b]; [1, 2], [3, 4]] | to nuon --list-of-records",
151                result: Some(Value::test_string("[{a: 1, b: 2}, {a: 3, b: 4}]")),
152            },
153            Example {
154                description: "Serialize table values as list of records with pretty indentation.",
155                example: "[[a, b]; [1, 2], [3, 4]] | to nuon --list-of-records --indent 2",
156                result: Some(Value::test_string("[\n  {a: 1, b: 2},\n  {a: 3, b: 4}\n]")),
157            },
158            Example {
159                description: "Output a list without commas between items.",
160                example: "[1 2 3] | to nuon --no-commas",
161                result: Some(Value::test_string("[1 2 3]")),
162            },
163            Example {
164                description: "Output a record without commas between fields.",
165                example: "{a: 1, b: 2} | to nuon --no-commas",
166                result: Some(Value::test_string("{a: 1 b: 2}")),
167            },
168            Example {
169                description: "Format output with pretty indentation and aligned table columns.",
170                example: "[[name, age]; [Alice, 30], [Bob, 25]] | to nuon --pretty",
171                result: Some(Value::test_string(
172                    "[\n  [name,  age];\n  [Alice, 30],\n  [Bob,   25]\n]",
173                )),
174            },
175        ]
176    }
177}
178
179#[cfg(test)]
180mod test {
181    use super::*;
182    use nu_cmd_lang::eval_pipeline_without_terminal_expression;
183
184    use crate::{Get, Metadata};
185
186    #[test]
187    fn test_examples() -> nu_test_support::Result {
188        use super::ToNuon;
189        nu_test_support::test().examples(ToNuon)
190    }
191
192    #[test]
193    fn test_content_type_metadata() {
194        let mut engine_state = Box::new(EngineState::new());
195        let delta = {
196            // Base functions that are needed for testing
197            // Try to keep this working set small to keep tests running as fast as possible
198            let mut working_set = StateWorkingSet::new(&engine_state);
199
200            working_set.add_decl(Box::new(ToNuon {}));
201            working_set.add_decl(Box::new(Metadata {}));
202            working_set.add_decl(Box::new(Get {}));
203
204            working_set.render()
205        };
206
207        engine_state
208            .merge_delta(delta)
209            .expect("Error merging delta");
210
211        let cmd = "{a: 1 b: 2} | to nuon | metadata | get content_type | $in";
212        let result = eval_pipeline_without_terminal_expression(
213            cmd,
214            std::env::temp_dir().as_ref(),
215            &mut engine_state,
216        );
217        assert_eq!(
218            Value::test_string("application/x-nuon"),
219            result.expect("There should be a result")
220        );
221    }
222}