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 {
217 return Err(ShellError::AccessBeyondEnd {
218 max_idx: pre_elems.len() - 1,
219 span: path_span,
220 });
221 }
222
223 Ok(pre_elems
224 .into_iter()
225 .chain(stream)
226 .into_pipeline_data_with_metadata(
227 head_span,
228 engine_state.signals().clone(),
229 metadata,
230 ))
231 } else if let Some(new_cell_paths) = Value::try_put_int_path_member_on_top(cell_paths) {
232 insert_recursive(
233 engine_state,
234 stack,
235 head_span,
236 replacement,
237 PipelineData::ListStream(stream, metadata),
238 &new_cell_paths,
239 )
240 } else if let Value::Closure { val, .. } = replacement {
241 let mut closure = ClosureEval::new(engine_state, stack, *val);
242 let cell_paths = cell_paths.to_vec();
243 let stream = stream.map(move |mut value| {
244 let err =
245 insert_value_by_closure(&mut value, &mut closure, head_span, &cell_paths);
246
247 if let Err(e) = err {
248 Value::error(e, head_span)
249 } else {
250 value
251 }
252 });
253 Ok(PipelineData::list_stream(stream, metadata))
254 } else {
255 let cell_paths = cell_paths.to_vec();
256 let stream = stream.map(move |mut value| {
257 if let Err(e) =
258 value.insert_data_at_cell_path(&cell_paths, replacement.clone(), head_span)
259 {
260 Value::error(e, head_span)
261 } else {
262 value
263 }
264 });
265
266 Ok(PipelineData::list_stream(stream, metadata))
267 }
268 }
269 PipelineData::Empty => Err(ShellError::IncompatiblePathAccess {
270 type_name: "empty pipeline".to_string(),
271 span: head_span,
272 }),
273 PipelineData::ByteStream(stream, ..) => Err(ShellError::IncompatiblePathAccess {
274 type_name: stream.type_().describe().into(),
275 span: head_span,
276 }),
277 }
278}
279
280fn insert(
281 engine_state: &EngineState,
282 stack: &mut Stack,
283 call: &Call,
284 input: PipelineData,
285) -> Result<PipelineData, ShellError> {
286 let head = call.head;
287 let cell_path: CellPath = call.req(engine_state, stack, 0)?;
288 let replacement: Value = call.req(engine_state, stack, 1)?;
289 let input = input.into_stream_or_original(engine_state);
290
291 insert_recursive(
292 engine_state,
293 stack,
294 head,
295 replacement,
296 input,
297 &cell_path.members,
298 )
299}
300
301fn insert_value_by_closure(
302 value: &mut Value,
303 closure: &mut ClosureEval,
304 span: Span,
305 cell_path: &[PathMember],
306) -> Result<(), ShellError> {
307 let new_value = closure.run_with_value(value.clone())?.into_value(span)?;
308 value.insert_data_at_cell_path(cell_path, new_value, span)
309}
310
311fn insert_single_value_by_closure(
312 value: &mut Value,
313 closure: ClosureEvalOnce,
314 span: Span,
315 cell_path: &[PathMember],
316 cell_value_as_arg: bool,
317) -> Result<(), ShellError> {
318 let arg = if cell_value_as_arg {
322 value
323 .follow_cell_path(cell_path)
324 .map(Cow::into_owned)
325 .unwrap_or(Value::nothing(span))
326 } else {
327 value.clone()
328 };
329 let new_value = closure.run_with_value(arg)?.into_value(span)?;
330 value.insert_data_at_cell_path(cell_path, new_value, span)
331}
332
333#[cfg(test)]
334mod test {
335 use super::*;
336
337 #[test]
338 fn test_examples() -> nu_test_support::Result {
339 nu_test_support::test().examples(Insert)
340 }
341}