nu_command/filters/
items.rs1use super::utils::chain_error_with_input;
2use nu_engine::{ClosureEval, command_prelude::*};
3use nu_protocol::engine::Closure;
4
5#[derive(Clone)]
6pub struct Items;
7
8impl Command for Items {
9 fn name(&self) -> &str {
10 "items"
11 }
12
13 fn signature(&self) -> Signature {
14 Signature::build(self.name())
15 .input_output_types(vec![(Type::record(), Type::Any)])
16 .required(
17 "closure",
18 SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Any])),
19 "The closure to run.",
20 )
21 .allow_variants_without_examples(true)
22 .category(Category::Filters)
23 }
24
25 fn description(&self) -> &str {
26 "Given a record, iterate on each pair of column name and associated value."
27 }
28
29 fn extra_description(&self) -> &str {
30 "This is a fusion of `columns`, `values` and `each`."
31 }
32
33 fn run(
34 &self,
35 engine_state: &EngineState,
36 stack: &mut Stack,
37 call: &Call,
38 mut input: PipelineData,
39 ) -> Result<PipelineData, ShellError> {
40 let head = call.head;
41 let closure: Closure = call.req(engine_state, stack, 0)?;
42
43 let metadata = input.take_metadata();
44 match input {
45 PipelineData::Empty => Ok(PipelineData::empty()),
46 PipelineData::Value(value, ..) => {
47 let span = value.span();
48 match value {
49 Value::Record { val, .. } => {
50 let mut closure = ClosureEval::new(engine_state, stack, closure);
51 Ok(val
52 .into_owned()
53 .into_iter()
54 .map_while(move |(col, val)| {
55 let result = closure
56 .add_arg(Value::string(col, span))
57 .and_then(|closure| closure.add_arg(val))
58 .and_then(|closure| {
59 closure.run_with_input(PipelineData::empty())
60 })
61 .and_then(|data| data.into_value(head));
62
63 match result {
64 Ok(value) => Some(value),
65 Err(err) => {
66 let err = chain_error_with_input(err, false, span);
67 Some(Value::error(err, head))
68 }
69 }
70 })
71 .into_pipeline_data(head, engine_state.signals().clone()))
72 }
73 Value::Error { error, .. } => Err(*error),
74 other => Err(ShellError::OnlySupportsThisInputType {
75 exp_input_type: "record".into(),
76 wrong_type: other.get_type().to_string(),
77 dst_span: head,
78 src_span: other.span(),
79 }),
80 }
81 }
82 PipelineData::ListStream(stream, ..) => Err(ShellError::OnlySupportsThisInputType {
83 exp_input_type: "record".into(),
84 wrong_type: "stream".into(),
85 dst_span: call.head,
86 src_span: stream.span(),
87 }),
88 PipelineData::ByteStream(stream, ..) => Err(ShellError::OnlySupportsThisInputType {
89 exp_input_type: "record".into(),
90 wrong_type: stream.type_().describe().into(),
91 dst_span: call.head,
92 src_span: stream.span(),
93 }),
94 }
95 .map(|data| data.set_metadata(metadata))
96 }
97
98 fn examples(&self) -> Vec<Example<'_>> {
99 vec![Example {
100 example: "{ new: york, san: francisco } | items {|key, value| echo $'($key) ($value)' }",
101 description: "Iterate over each key-value pair of a record",
102 result: Some(Value::list(
103 vec![
104 Value::test_string("new york"),
105 Value::test_string("san francisco"),
106 ],
107 Span::test_data(),
108 )),
109 }]
110 }
111}
112
113#[cfg(test)]
114mod test {
115 use super::*;
116
117 #[test]
118 fn test_examples() -> nu_test_support::Result {
119 nu_test_support::test().examples(Items)
120 }
121}