nu_command/formats/to/
tsv.rs1use std::sync::Arc;
2
3use crate::formats::to::delimited::to_delimited_data;
4use nu_engine::command_prelude::*;
5use nu_protocol::Config;
6
7use super::delimited::ToDelimitedDataArgs;
8
9#[derive(Clone)]
10pub struct ToTsv;
11
12impl Command for ToTsv {
13 fn name(&self) -> &str {
14 "to tsv"
15 }
16
17 fn signature(&self) -> Signature {
18 Signature::build("to tsv")
19 .input_output_types(vec![
20 (Type::record(), Type::String),
21 (Type::table(), Type::String),
22 ])
23 .switch(
24 "noheaders",
25 "do not output the column names as the first row",
26 Some('n'),
27 )
28 .named(
29 "columns",
30 SyntaxShape::List(SyntaxShape::String.into()),
31 "the names (in order) of the columns to use",
32 None,
33 )
34 .category(Category::Formats)
35 }
36
37 fn description(&self) -> &str {
38 "Convert table into .tsv text."
39 }
40
41 fn examples(&self) -> Vec<Example<'_>> {
42 vec![
43 Example {
44 description: "Outputs a TSV string representing the contents of this table",
45 example: "[[foo bar]; [1 2]] | to tsv",
46 result: Some(Value::test_string("foo\tbar\n1\t2\n")),
47 },
48 Example {
49 description: "Outputs a TSV string representing the contents of this record",
50 example: "{a: 1 b: 2} | to tsv",
51 result: Some(Value::test_string("a\tb\n1\t2\n")),
52 },
53 Example {
54 description: "Outputs a TSV stream with column names pre-determined",
55 example: "[[foo bar baz]; [1 2 3]] | to tsv --columns [baz foo]",
56 result: Some(Value::test_string("baz\tfoo\n3\t1\n")),
57 },
58 ]
59 }
60
61 fn run(
62 &self,
63 engine_state: &EngineState,
64 stack: &mut Stack,
65 call: &Call,
66 input: PipelineData,
67 ) -> Result<PipelineData, ShellError> {
68 let head = call.head;
69 let noheaders = call.has_flag(engine_state, stack, "noheaders")?;
70 let columns: Option<Vec<String>> = call.get_flag(engine_state, stack, "columns")?;
71 let config = engine_state.config.clone();
72 to_tsv(input, noheaders, columns, head, config)
73 }
74}
75
76fn to_tsv(
77 input: PipelineData,
78 noheaders: bool,
79 columns: Option<Vec<String>>,
80 head: Span,
81 config: Arc<Config>,
82) -> Result<PipelineData, ShellError> {
83 let sep = Spanned {
84 item: '\t',
85 span: head,
86 };
87 to_delimited_data(
88 ToDelimitedDataArgs {
89 noheaders,
90 separator: sep,
91 columns,
92 format_name: "TSV",
93 input,
94 head,
95 content_type: Some(mime::TEXT_TAB_SEPARATED_VALUES.to_string()),
96 },
97 config,
98 )
99}
100
101#[cfg(test)]
102mod test {
103 use nu_cmd_lang::eval_pipeline_without_terminal_expression;
104
105 use crate::{Get, Metadata};
106
107 use super::*;
108
109 #[test]
110 fn test_examples() {
111 use crate::test_examples;
112
113 test_examples(ToTsv {})
114 }
115
116 #[test]
117 fn test_content_type_metadata() {
118 let mut engine_state = Box::new(EngineState::new());
119 let delta = {
120 let mut working_set = StateWorkingSet::new(&engine_state);
123
124 working_set.add_decl(Box::new(ToTsv {}));
125 working_set.add_decl(Box::new(Metadata {}));
126 working_set.add_decl(Box::new(Get {}));
127
128 working_set.render()
129 };
130
131 engine_state
132 .merge_delta(delta)
133 .expect("Error merging delta");
134
135 let cmd = "{a: 1 b: 2} | to tsv | metadata | get content_type | $in";
136 let result = eval_pipeline_without_terminal_expression(
137 cmd,
138 std::env::temp_dir().as_ref(),
139 &mut engine_state,
140 );
141 assert_eq!(
142 Value::test_string("text/tab-separated-values"),
143 result.expect("There should be a result")
144 );
145 }
146}