1use nu_engine::command_prelude::*;
2use nu_protocol::{PipelineMetadata, casing::WrapCased};
3
4#[derive(Clone)]
5pub struct Peek;
6
7impl Command for Peek {
8 fn name(&self) -> &str {
9 "peek"
10 }
11
12 fn description(&self) -> &str {
13 "Peek the first <n> elements of a stream and store them in the metadata."
14 }
15
16 fn search_terms(&self) -> Vec<&str> {
17 vec!["stream", "inspect"]
18 }
19
20 fn signature(&self) -> Signature {
21 Signature::build("peek")
22 .input_output_types(vec![
23 (
24 Type::List(Box::new(Type::Any)),
25 Type::List(Box::new(Type::Any)),
26 ),
27 (Type::table(), Type::table()),
28 (Type::Any, Type::Any),
29 ])
30 .optional(
31 "n",
32 SyntaxShape::Int,
33 "Number of elements to peek, if the input is a stream or list.",
34 )
35 }
36
37 fn run(
38 &self,
39 engine_state: &EngineState,
40 stack: &mut Stack,
41 call: &Call,
42 input: PipelineData,
43 ) -> Result<PipelineData, ShellError> {
44 let n: Option<usize> = call.opt(engine_state, stack, 0)?;
45
46 match input {
47 PipelineData::Empty => {
48 let metadata = add_peek_metadata(None, "empty", false, None, call.head);
49 Ok(Value::nothing(call.head).into_pipeline_data_with_metadata(metadata))
50 }
51 PipelineData::Value(val, metadata) => match &val {
52 Value::List { vals, .. } => {
53 let peeked = n.map(|n| {
54 vals.iter()
55 .take(n)
56 .cloned()
57 .collect::<Vec<_>>()
58 .into_value(call.head)
59 });
60 let metadata = add_peek_metadata(metadata, "list", false, peeked, call.head);
61 Ok(PipelineData::value(val, metadata))
62 }
63 _ => {
64 let r#type = val.get_type_shallow().get_non_specified_string();
65 let metadata = add_peek_metadata(metadata, r#type, false, None, call.head);
66 Ok(PipelineData::value(val, metadata))
67 }
68 },
69 PipelineData::ListStream(stream, metadata) => {
70 let mut elems = None;
71 let stream = match n {
72 Some(n) => stream.modify(|mut it| {
73 let collect = it.as_mut().take(n).collect::<Vec<_>>();
74 elems = Some(collect.clone());
75 collect.into_iter().chain(it)
76 }),
77 None => stream,
78 };
79
80 let metadata = add_peek_metadata(
81 metadata,
82 "list",
83 true,
84 elems.map(|x| x.into_value(call.head)),
85 call.head,
86 );
87
88 Ok(PipelineData::list_stream(stream, metadata))
89 }
90 PipelineData::ByteStream(byte_stream, pipeline_metadata) => {
91 let metadata = add_peek_metadata(
92 pipeline_metadata,
93 match byte_stream.type_() {
94 ByteStreamType::Binary => "binary",
95 ByteStreamType::String => "string",
96 ByteStreamType::Unknown => "byte stream",
97 },
98 true,
99 None,
100 call.head,
101 );
102 Ok(PipelineData::byte_stream(byte_stream, metadata))
103 }
104 }
105 }
106
107 fn examples(&self) -> Vec<Example<'_>> {
108 vec![
109 Example {
110 description: "Peek the first 2 elements of a stream.",
111 example: "seq 1 5 | peek 2 | metadata | $in.peek",
112 result: Some(test_record! {
113 "type" => "list",
114 "stream" => true,
115 "value" => [1, 2],
116 }),
117 },
118 Example {
119 description: "Lists can also be peeked.",
120 example: "[1, 2, 3] | peek 1 | metadata | $in.peek",
121 result: Some(test_record! {
122 "type" => "list",
123 "stream" => false,
124 "value" => [1],
125 }),
126 },
127 Example {
128 description: "Peeking non-list values won't return any values.",
129 example: "'hello' | peek 1 | metadata | $in.peek",
130 result: Some(test_record! {
131 "type" => "string",
132 "stream" => false,
133 }),
134 },
135 Example {
136 description: "Peeking non-list streams (text streams, binary streams, external byte streams) won't return any values.",
137 example: "[0x[11] 0x[13 15]] | bytes collect | peek | metadata | $in.peek",
138 result: Some(test_record! {
139 "type" => "binary",
140 "stream" => true,
141 }),
142 },
143 ]
144 }
145}
146
147fn add_peek_metadata(
148 mut metadata: Option<PipelineMetadata>,
149 r#type: impl Into<String>,
150 stream: bool,
151 value: Option<Value>,
152 span: Span,
153) -> Option<PipelineMetadata> {
154 let mut record = Record::new();
155 let record_handle = record.as_mut().case_sensitive();
156
157 record_handle.insert("type", r#type.into().into_value(span));
158 record_handle.insert("stream", stream.into_value(span));
159 if let Some(value) = value {
160 record_handle.insert("value", value);
161 }
162
163 metadata
164 .get_or_insert_default()
165 .custom
166 .insert("peek", record.into_value(span));
167
168 metadata
169}
170
171#[cfg(test)]
172mod test {
173 use super::*;
174
175 #[test]
176 fn test_examples() -> nu_test_support::Result {
177 nu_test_support::test().examples(Peek)
178 }
179}