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 and -t).",
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            .category(Category::Formats)
52    }
53
54    fn description(&self) -> &str {
55        "Converts table data into Nuon (Nushell Object Notation) text."
56    }
57
58    fn run(
59        &self,
60        engine_state: &EngineState,
61        stack: &mut Stack,
62        call: &Call,
63        mut input: PipelineData,
64    ) -> Result<PipelineData, ShellError> {
65        let serialize_types = call.has_flag(engine_state, stack, "serialize")?;
66        let raw_strings = call.has_flag(engine_state, stack, "raw-strings")?;
67        let list_of_records = call.has_flag(engine_state, stack, "list-of-records")?;
68        let no_commas = call.has_flag(engine_state, stack, "no-commas")?;
69        let style = if call.has_flag(engine_state, stack, "raw")? {
70            nuon::ToStyle::Raw
71        } else if let Some(t) = call.get_flag(engine_state, stack, "tabs")? {
72            nuon::ToStyle::Tabs(t)
73        } else if let Some(i) = call.get_flag(engine_state, stack, "indent")? {
74            nuon::ToStyle::Spaces(i)
75        } else {
76            nuon::ToStyle::Default
77        };
78
79        let span = call.head;
80        let metadata = input
81            .take_metadata()
82            .unwrap_or_default()
83            .with_content_type(Some("application/x-nuon".into()));
84
85        let value = input.into_value(span)?;
86
87        let config = nuon::ToNuonConfig::default()
88            .style(style)
89            .span(Some(span))
90            .serialize_types(serialize_types)
91            .raw_strings(raw_strings)
92            .list_of_records(list_of_records)
93            .use_commas(!no_commas);
94
95        match nuon::to_nuon(engine_state, &value, config) {
96            Ok(serde_nuon_string) => Ok(Value::string(serde_nuon_string, span)
97                .into_pipeline_data_with_metadata(Some(metadata))),
98            Err(error) => {
99                Ok(Value::error(error, span).into_pipeline_data_with_metadata(Some(metadata)))
100            }
101        }
102    }
103
104    fn examples(&self) -> Vec<Example<'_>> {
105        vec![
106            Example {
107                description: "Outputs a NUON string representing the contents of this list, compact by default.",
108                example: "[1 2 3] | to nuon",
109                result: Some(Value::test_string("[1, 2, 3]")),
110            },
111            Example {
112                description: "Outputs a NUON array of ints, with pretty indentation.",
113                example: "[1 2 3] | to nuon --indent 2",
114                result: Some(Value::test_string("[\n  1,\n  2,\n  3\n]")),
115            },
116            Example {
117                description: "Overwrite any set option with --raw.",
118                example: "[1 2 3] | to nuon --indent 2 --raw",
119                result: Some(Value::test_string("[1,2,3]")),
120            },
121            Example {
122                description: "A more complex record with multiple data types.",
123                example: "{date: 2000-01-01, data: [1 [2 3] 4.56]} | to nuon --indent 2",
124                result: Some(Value::test_string(
125                    "{\n  date: 2000-01-01T00:00:00+00:00,\n  data: [\n    1,\n    [\n      2,\n      3\n    ],\n    4.56\n  ]\n}",
126                )),
127            },
128            Example {
129                description: "A more complex record with --raw.",
130                example: "{date: 2000-01-01, data: [1 [2 3] 4.56]} | to nuon --raw",
131                result: Some(Value::test_string(
132                    "{date:2000-01-01T00:00:00+00:00,data:[1,[2,3],4.56]}",
133                )),
134            },
135            Example {
136                description: "Use raw string syntax for strings with quotes or backslashes.",
137                example: r#"'hello "world"' | to nuon --raw-strings"#,
138                result: Some(Value::test_string(r#"r#'hello "world"'#"#)),
139            },
140            Example {
141                description: "Serialize table values as a list of records instead of table syntax.",
142                example: "[[a, b]; [1, 2], [3, 4]] | to nuon --list-of-records",
143                result: Some(Value::test_string("[{a: 1, b: 2}, {a: 3, b: 4}]")),
144            },
145            Example {
146                description: "Serialize table values as list of records with pretty indentation.",
147                example: "[[a, b]; [1, 2], [3, 4]] | to nuon --list-of-records --indent 2",
148                result: Some(Value::test_string("[\n  {a: 1, b: 2},\n  {a: 3, b: 4}\n]")),
149            },
150            Example {
151                description: "Output a list without commas between items.",
152                example: "[1 2 3] | to nuon --no-commas",
153                result: Some(Value::test_string("[1 2 3]")),
154            },
155            Example {
156                description: "Output a record without commas between fields.",
157                example: "{a: 1, b: 2} | to nuon --no-commas",
158                result: Some(Value::test_string("{a: 1 b: 2}")),
159            },
160        ]
161    }
162}
163
164#[cfg(test)]
165mod test {
166    use super::*;
167    use nu_cmd_lang::eval_pipeline_without_terminal_expression;
168
169    use crate::{Get, Metadata};
170
171    #[test]
172    fn test_examples() -> nu_test_support::Result {
173        use super::ToNuon;
174        nu_test_support::test().examples(ToNuon)
175    }
176
177    #[test]
178    fn test_content_type_metadata() {
179        let mut engine_state = Box::new(EngineState::new());
180        let delta = {
181            // Base functions that are needed for testing
182            // Try to keep this working set small to keep tests running as fast as possible
183            let mut working_set = StateWorkingSet::new(&engine_state);
184
185            working_set.add_decl(Box::new(ToNuon {}));
186            working_set.add_decl(Box::new(Metadata {}));
187            working_set.add_decl(Box::new(Get {}));
188
189            working_set.render()
190        };
191
192        engine_state
193            .merge_delta(delta)
194            .expect("Error merging delta");
195
196        let cmd = "{a: 1 b: 2} | to nuon | metadata | get content_type | $in";
197        let result = eval_pipeline_without_terminal_expression(
198            cmd,
199            std::env::temp_dir().as_ref(),
200            &mut engine_state,
201        );
202        assert_eq!(
203            Value::test_string("application/x-nuon"),
204            result.expect("There should be a result")
205        );
206    }
207}