nu_command/filters/
columns.rs

1use nu_engine::{column::get_columns, command_prelude::*};
2
3#[derive(Clone)]
4pub struct Columns;
5
6impl Command for Columns {
7    fn name(&self) -> &str {
8        "columns"
9    }
10
11    fn signature(&self) -> Signature {
12        Signature::build(self.name())
13            .input_output_types(vec![
14                (Type::table(), Type::List(Box::new(Type::String))),
15                (Type::record(), Type::List(Box::new(Type::String))),
16            ])
17            .category(Category::Filters)
18    }
19
20    fn description(&self) -> &str {
21        "Given a record or table, produce a list of its columns' names."
22    }
23
24    fn extra_description(&self) -> &str {
25        "This is a counterpart to `values`, which produces a list of columns' values."
26    }
27
28    fn examples(&self) -> Vec<Example<'_>> {
29        vec![
30            Example {
31                example: "{ acronym:PWD, meaning:'Print Working Directory' } | columns",
32                description: "Get the columns from the record",
33                result: Some(Value::list(
34                    vec![Value::test_string("acronym"), Value::test_string("meaning")],
35                    Span::test_data(),
36                )),
37            },
38            Example {
39                example: "[[name,age,grade]; [bill,20,a]] | columns",
40                description: "Get the columns from the table",
41                result: Some(Value::list(
42                    vec![
43                        Value::test_string("name"),
44                        Value::test_string("age"),
45                        Value::test_string("grade"),
46                    ],
47                    Span::test_data(),
48                )),
49            },
50            Example {
51                example: "[[name,age,grade]; [bill,20,a]] | columns | first",
52                description: "Get the first column from the table",
53                result: None,
54            },
55            Example {
56                example: "[[name,age,grade]; [bill,20,a]] | columns | select 1",
57                description: "Get the second column from the table",
58                result: None,
59            },
60        ]
61    }
62
63    fn run(
64        &self,
65        _engine_state: &EngineState,
66        _stack: &mut Stack,
67        call: &Call,
68        input: PipelineData,
69    ) -> Result<PipelineData, ShellError> {
70        getcol(call.head, input)
71    }
72}
73
74fn getcol(head: Span, input: PipelineData) -> Result<PipelineData, ShellError> {
75    let metadata = input.metadata();
76    match input {
77        PipelineData::Empty => Ok(PipelineData::empty()),
78        PipelineData::Value(v, ..) => {
79            let span = v.span();
80            let cols = match v {
81                Value::List {
82                    vals: input_vals, ..
83                } => get_columns(&input_vals)
84                    .into_iter()
85                    .map(move |x| Value::string(x, span))
86                    .collect(),
87                Value::Custom { val, .. } => {
88                    // TODO: should we get CustomValue to expose columns in a more efficient way?
89                    // Would be nice to be able to get columns without generating the whole value
90                    let input_as_base_value = val.to_base_value(span)?;
91                    get_columns(&[input_as_base_value])
92                        .into_iter()
93                        .map(move |x| Value::string(x, span))
94                        .collect()
95                }
96                Value::Record { val, .. } => val
97                    .into_owned()
98                    .into_iter()
99                    .map(move |(x, _)| Value::string(x, head))
100                    .collect(),
101                // Propagate errors
102                Value::Error { error, .. } => return Err(*error),
103                other => {
104                    return Err(ShellError::OnlySupportsThisInputType {
105                        exp_input_type: "record or table".into(),
106                        wrong_type: other.get_type().to_string(),
107                        dst_span: head,
108                        src_span: other.span(),
109                    });
110                }
111            };
112
113            Ok(Value::list(cols, head)
114                .into_pipeline_data()
115                .set_metadata(metadata))
116        }
117        PipelineData::ListStream(stream, ..) => {
118            let values = stream.into_iter().collect::<Vec<_>>();
119            let cols = get_columns(&values)
120                .into_iter()
121                .map(|s| Value::string(s, head))
122                .collect();
123
124            Ok(Value::list(cols, head)
125                .into_pipeline_data()
126                .set_metadata(metadata))
127        }
128        PipelineData::ByteStream(stream, ..) => Err(ShellError::OnlySupportsThisInputType {
129            exp_input_type: "record or table".into(),
130            wrong_type: "byte stream".into(),
131            dst_span: head,
132            src_span: stream.span(),
133        }),
134    }
135}
136
137#[cfg(test)]
138mod test {
139    use super::*;
140
141    #[test]
142    fn test_examples() {
143        use crate::test_examples;
144
145        test_examples(Columns {})
146    }
147}