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 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}