1use std::borrow::Cow;
2
3use nu_engine::{ClosureEval, ClosureEvalOnce, command_prelude::*};
4use nu_protocol::ast::PathMember;
5
6#[derive(Clone)]
7pub struct Insert;
8
9impl Command for Insert {
10 fn name(&self) -> &str {
11 "insert"
12 }
13
14 fn signature(&self) -> Signature {
15 Signature::build("insert")
16 .input_output_types(vec![
17 (Type::record(), Type::record()),
18 (Type::table(), Type::table()),
19 (
20 Type::List(Box::new(Type::Any)),
21 Type::List(Box::new(Type::Any)),
22 ),
23 ])
24 .required(
25 "field",
26 SyntaxShape::CellPath,
27 "The name of the column to insert.",
28 )
29 .required(
30 "new value",
31 SyntaxShape::Any,
32 "The new value to give the cell(s).",
33 )
34 .allow_variants_without_examples(true)
35 .category(Category::Filters)
36 }
37
38 fn description(&self) -> &str {
39 "Insert a new column, using an expression or closure to create each row's values."
40 }
41
42 fn extra_description(&self) -> &str {
43 "When inserting a column, the closure will be run for each row, and the current row will be passed as the first argument.
44When inserting into a specific index, the closure will instead get the current value at the index or null if inserting at the end of a list/table."
45 }
46
47 fn search_terms(&self) -> Vec<&str> {
48 vec!["add"]
49 }
50
51 fn run(
52 &self,
53 engine_state: &EngineState,
54 stack: &mut Stack,
55 call: &Call,
56 input: PipelineData,
57 ) -> Result<PipelineData, ShellError> {
58 insert(engine_state, stack, call, input)
59 }
60
61 fn examples(&self) -> Vec<Example<'_>> {
62 vec![
63 Example {
64 description: "Insert a new entry into a single record",
65 example: "{'name': 'nu', 'stars': 5} | insert alias 'Nushell'",
66 result: Some(Value::test_record(record! {
67 "name" => Value::test_string("nu"),
68 "stars" => Value::test_int(5),
69 "alias" => Value::test_string("Nushell"),
70 })),
71 },
72 Example {
73 description: "Insert a new column into a table, populating all rows",
74 example: "[[project, lang]; ['Nushell', 'Rust']] | insert type 'shell'",
75 result: Some(Value::test_list(vec![Value::test_record(record! {
76 "project" => Value::test_string("Nushell"),
77 "lang" => Value::test_string("Rust"),
78 "type" => Value::test_string("shell"),
79 })])),
80 },
81 Example {
82 description: "Insert a new column with values computed based off the other columns",
83 example: "[[foo]; [7] [8] [9]] | insert bar {|row| $row.foo * 2 }",
84 result: Some(Value::test_list(vec![
85 Value::test_record(record! {
86 "foo" => Value::test_int(7),
87 "bar" => Value::test_int(14),
88 }),
89 Value::test_record(record! {
90 "foo" => Value::test_int(8),
91 "bar" => Value::test_int(16),
92 }),
93 Value::test_record(record! {
94 "foo" => Value::test_int(9),
95 "bar" => Value::test_int(18),
96 }),
97 ])),
98 },
99 Example {
100 description: "Insert a new value into a list at an index",
101 example: "[1 2 4] | insert 2 3",
102 result: Some(Value::test_list(vec![
103 Value::test_int(1),
104 Value::test_int(2),
105 Value::test_int(3),
106 Value::test_int(4),
107 ])),
108 },
109 Example {
110 description: "Insert a new value at the end of a list",
111 example: "[1 2 3] | insert 3 4",
112 result: Some(Value::test_list(vec![
113 Value::test_int(1),
114 Value::test_int(2),
115 Value::test_int(3),
116 Value::test_int(4),
117 ])),
118 },
119 Example {
120 description: "Insert into a nested path, creating new values as needed",
121 example: "[{} {a: [{}]}] | insert a.0.b \"value\"",
122 result: Some(Value::test_list(vec![
123 Value::test_record(record!(
124 "a" => Value::test_record(record!(
125 "b" => Value::test_string("value"),
126 )),
127 )),
128 Value::test_record(record!(
129 "a" => Value::test_list(vec![Value::test_record(record!(
130 ))]),
131 )),
132 ])),
133 },
134 ]
135 }
136}
137
138fn insert_recursive(
139 engine_state: &EngineState,
140 stack: &mut Stack,
141 head_span: Span,
142 replacement: Value,
143 input: PipelineData,
144 cell_paths: &[PathMember],
145) -> Result<PipelineData, ShellError> {
146 match input {
147 PipelineData::Value(Value::Error { error, .. }, ..) => Err(*error),
149 PipelineData::Value(mut value, metadata) => {
150 if let Value::Closure { val, .. } = replacement {
151 insert_single_value_by_closure(
152 &mut value,
153 ClosureEvalOnce::new(engine_state, stack, *val),
154 head_span,
155 cell_paths,
156 false,
157 )?;
158 } else {
159 value.insert_data_at_cell_path(cell_paths, replacement, head_span)?;
160 }
161 Ok(value.into_pipeline_data_with_metadata(metadata))
162 }
163 PipelineData::ListStream(stream, metadata) => {
164 if let Some((
165 &PathMember::Int {
166 val,
167 span: path_span,
168 ..
169 },
170 path,
171 )) = cell_paths.split_first()
172 {
173 let mut stream = stream.into_iter();
174 let mut pre_elems = vec![];
175
176 for idx in 0..val {
177 if let Some(v) = stream.next() {
178 pre_elems.push(v);
179 } else {
180 return Err(ShellError::InsertAfterNextFreeIndex {
181 available_idx: idx,
182 span: path_span,
183 });
184 }
185 }
186
187 if path.is_empty() {
188 if let Value::Closure { val, .. } = replacement {
189 let value = stream.next();
190 let end_of_stream = value.is_none();
191 let value = value.unwrap_or(Value::nothing(head_span));
192 let new_value = ClosureEvalOnce::new(engine_state, stack, *val)
193 .run_with_value(value.clone())?
194 .into_value(head_span)?;
195
196 pre_elems.push(new_value);
197 if !end_of_stream {
198 pre_elems.push(value);
199 }
200 } else {
201 pre_elems.push(replacement);
202 }
203 } else if let Some(mut value) = stream.next() {
204 if let Value::Closure { val, .. } = replacement {
205 insert_single_value_by_closure(
206 &mut value,
207 ClosureEvalOnce::new(engine_state, stack, *val),
208 head_span,
209 path,
210 true,
211 )?;
212 } else {
213 value.insert_data_at_cell_path(path, replacement, head_span)?;
214 }
215 pre_elems.push(value)
216 } else if pre_elems.is_empty() {
217 return Err(ShellError::AccessEmptyContent { span: path_span });
218 } else {
219 return Err(ShellError::AccessBeyondEnd {
220 max_idx: pre_elems.len() - 1,
221 span: path_span,
222 });
223 }
224
225 Ok(pre_elems
226 .into_iter()
227 .chain(stream)
228 .into_pipeline_data_with_metadata(
229 head_span,
230 engine_state.signals().clone(),
231 metadata,
232 ))
233 } else if let Some(new_cell_paths) = Value::try_put_int_path_member_on_top(cell_paths) {
234 insert_recursive(
235 engine_state,
236 stack,
237 head_span,
238 replacement,
239 PipelineData::ListStream(stream, metadata),
240 &new_cell_paths,
241 )
242 } else if let Value::Closure { val, .. } = replacement {
243 let mut closure = ClosureEval::new(engine_state, stack, *val);
244 let cell_paths = cell_paths.to_vec();
245 let stream = stream.map(move |mut value| {
246 let err =
247 insert_value_by_closure(&mut value, &mut closure, head_span, &cell_paths);
248
249 if let Err(e) = err {
250 Value::error(e, head_span)
251 } else {
252 value
253 }
254 });
255 Ok(PipelineData::list_stream(stream, metadata))
256 } else {
257 let cell_paths = cell_paths.to_vec();
258 let stream = stream.map(move |mut value| {
259 if let Err(e) =
260 value.insert_data_at_cell_path(&cell_paths, replacement.clone(), head_span)
261 {
262 Value::error(e, head_span)
263 } else {
264 value
265 }
266 });
267
268 Ok(PipelineData::list_stream(stream, metadata))
269 }
270 }
271 PipelineData::Empty => Err(ShellError::IncompatiblePathAccess {
272 type_name: "empty pipeline".to_string(),
273 span: head_span,
274 }),
275 PipelineData::ByteStream(stream, ..) => Err(ShellError::IncompatiblePathAccess {
276 type_name: stream.type_().describe().into(),
277 span: head_span,
278 }),
279 }
280}
281
282fn insert(
283 engine_state: &EngineState,
284 stack: &mut Stack,
285 call: &Call,
286 input: PipelineData,
287) -> Result<PipelineData, ShellError> {
288 let head = call.head;
289 let cell_path: CellPath = call.req(engine_state, stack, 0)?;
290 let replacement: Value = call.req(engine_state, stack, 1)?;
291 let input = input.into_stream_or_original(engine_state);
292
293 insert_recursive(
294 engine_state,
295 stack,
296 head,
297 replacement,
298 input,
299 &cell_path.members,
300 )
301}
302
303fn insert_value_by_closure(
304 value: &mut Value,
305 closure: &mut ClosureEval,
306 span: Span,
307 cell_path: &[PathMember],
308) -> Result<(), ShellError> {
309 let new_value = closure.run_with_value(value.clone())?.into_value(span)?;
310 value.insert_data_at_cell_path(cell_path, new_value, span)
311}
312
313fn insert_single_value_by_closure(
314 value: &mut Value,
315 closure: ClosureEvalOnce,
316 span: Span,
317 cell_path: &[PathMember],
318 cell_value_as_arg: bool,
319) -> Result<(), ShellError> {
320 let arg = if cell_value_as_arg {
324 value
325 .follow_cell_path(cell_path)
326 .map(Cow::into_owned)
327 .unwrap_or(Value::nothing(span))
328 } else {
329 value.clone()
330 };
331 let new_value = closure.run_with_value(arg)?.into_value(span)?;
332 value.insert_data_at_cell_path(cell_path, new_value, span)
333}
334
335#[cfg(test)]
336mod test {
337 use super::*;
338
339 #[test]
340 fn test_examples() -> nu_test_support::Result {
341 nu_test_support::test().examples(Insert)
342 }
343}