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 None,
40 )
41 .category(Category::Formats)
42 }
43
44 fn description(&self) -> &str {
45 "Converts table data into Nuon (Nushell Object Notation) text."
46 }
47
48 fn run(
49 &self,
50 engine_state: &EngineState,
51 stack: &mut Stack,
52 call: &Call,
53 input: PipelineData,
54 ) -> Result<PipelineData, ShellError> {
55 let metadata = input
56 .metadata()
57 .unwrap_or_default()
58 .with_content_type(Some("application/x-nuon".into()));
59
60 let serialize_types = call.has_flag(engine_state, stack, "serialize")?;
61 let raw_strings = call.has_flag(engine_state, stack, "raw-strings")?;
62 let style = if call.has_flag(engine_state, stack, "raw")? {
63 nuon::ToStyle::Raw
64 } else if let Some(t) = call.get_flag(engine_state, stack, "tabs")? {
65 nuon::ToStyle::Tabs(t)
66 } else if let Some(i) = call.get_flag(engine_state, stack, "indent")? {
67 nuon::ToStyle::Spaces(i)
68 } else {
69 nuon::ToStyle::Default
70 };
71
72 let span = call.head;
73 let value = input.into_value(span)?;
74
75 let config = nuon::ToNuonConfig::default()
76 .style(style)
77 .span(Some(span))
78 .serialize_types(serialize_types)
79 .raw_strings(raw_strings);
80
81 match nuon::to_nuon(engine_state, &value, config) {
82 Ok(serde_nuon_string) => Ok(Value::string(serde_nuon_string, span)
83 .into_pipeline_data_with_metadata(Some(metadata))),
84 Err(error) => {
85 Ok(Value::error(error, span).into_pipeline_data_with_metadata(Some(metadata)))
86 }
87 }
88 }
89
90 fn examples(&self) -> Vec<Example<'_>> {
91 vec![
92 Example {
93 description: "Outputs a NUON string representing the contents of this list, compact by default",
94 example: "[1 2 3] | to nuon",
95 result: Some(Value::test_string("[1, 2, 3]")),
96 },
97 Example {
98 description: "Outputs a NUON array of ints, with pretty indentation",
99 example: "[1 2 3] | to nuon --indent 2",
100 result: Some(Value::test_string("[\n 1,\n 2,\n 3\n]")),
101 },
102 Example {
103 description: "Overwrite any set option with --raw",
104 example: "[1 2 3] | to nuon --indent 2 --raw",
105 result: Some(Value::test_string("[1,2,3]")),
106 },
107 Example {
108 description: "A more complex record with multiple data types",
109 example: "{date: 2000-01-01, data: [1 [2 3] 4.56]} | to nuon --indent 2",
110 result: Some(Value::test_string(
111 "{\n date: 2000-01-01T00:00:00+00:00,\n data: [\n 1,\n [\n 2,\n 3\n ],\n 4.56\n ]\n}",
112 )),
113 },
114 Example {
115 description: "A more complex record with --raw",
116 example: "{date: 2000-01-01, data: [1 [2 3] 4.56]} | to nuon --raw",
117 result: Some(Value::test_string(
118 "{date:2000-01-01T00:00:00+00:00,data:[1,[2,3],4.56]}",
119 )),
120 },
121 Example {
122 description: "Use raw string syntax for strings with quotes or backslashes",
123 example: r#"'hello "world"' | to nuon --raw-strings"#,
124 result: Some(Value::test_string(r#"r#'hello "world"'#"#)),
125 },
126 ]
127 }
128}
129
130#[cfg(test)]
131mod test {
132 use super::*;
133 use nu_cmd_lang::eval_pipeline_without_terminal_expression;
134
135 use crate::{Get, Metadata};
136
137 #[test]
138 fn test_examples() {
139 use super::ToNuon;
140 use crate::test_examples;
141 test_examples(ToNuon {})
142 }
143
144 #[test]
145 fn test_content_type_metadata() {
146 let mut engine_state = Box::new(EngineState::new());
147 let delta = {
148 let mut working_set = StateWorkingSet::new(&engine_state);
151
152 working_set.add_decl(Box::new(ToNuon {}));
153 working_set.add_decl(Box::new(Metadata {}));
154 working_set.add_decl(Box::new(Get {}));
155
156 working_set.render()
157 };
158
159 engine_state
160 .merge_delta(delta)
161 .expect("Error merging delta");
162
163 let cmd = "{a: 1 b: 2} | to nuon | metadata | get content_type | $in";
164 let result = eval_pipeline_without_terminal_expression(
165 cmd,
166 std::env::temp_dir().as_ref(),
167 &mut engine_state,
168 );
169 assert_eq!(
170 Value::test_string("application/x-nuon"),
171 result.expect("There should be a result")
172 );
173 }
174}