nu_command/formats/to/
tsv.rs

1use 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            // Base functions that are needed for testing
121            // Try to keep this working set small to keep tests running as fast as possible
122            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}