nu_value_ext/
lib.rs

1use indexmap::indexmap;
2use indexmap::set::IndexSet;
3use itertools::Itertools;
4use nu_errors::{ExpectedRange, ShellError};
5use nu_protocol::{
6    ColumnPath, MaybeOwned, PathMember, Primitive, ShellTypeName, SpannedTypeName,
7    UnspannedPathMember, UntaggedValue, Value,
8};
9use nu_source::{
10    HasFallibleSpan, HasSpan, PrettyDebug, Span, Spanned, SpannedItem, Tag, Tagged, TaggedItem,
11};
12use num_traits::cast::ToPrimitive;
13
14#[cfg(feature = "dataframe")]
15use nu_protocol::dataframe::NuDataFrame;
16
17pub trait ValueExt {
18    fn into_parts(self) -> (UntaggedValue, Tag);
19    fn get_data(&self, desc: &str) -> MaybeOwned<'_, Value>;
20    fn get_data_by_key(&self, name: Spanned<&str>) -> Option<Value>;
21    fn get_data_by_member(&self, name: &PathMember) -> Result<Value, ShellError>;
22    fn get_data_by_column_path(
23        &self,
24        path: &ColumnPath,
25        callback: Box<dyn FnOnce(&Value, &PathMember, ShellError) -> ShellError>,
26    ) -> Result<Value, ShellError>;
27    fn swap_data_by_column_path(
28        &self,
29        path: &ColumnPath,
30        callback: Box<dyn FnOnce(&Value) -> Result<Value, ShellError>>,
31    ) -> Result<Value, ShellError>;
32    fn insert_data_at_path(&self, path: &str, new_value: Value) -> Option<Value>;
33    fn insert_data_at_member(
34        &mut self,
35        member: &PathMember,
36        new_value: Value,
37    ) -> Result<(), ShellError>;
38    fn forgiving_insert_data_at_column_path(
39        &self,
40        split_path: &ColumnPath,
41        new_value: Value,
42    ) -> Result<Value, ShellError>;
43    fn insert_data_at_column_path(
44        &self,
45        split_path: &ColumnPath,
46        new_value: Value,
47    ) -> Result<Value, ShellError>;
48    fn replace_data_at_column_path(
49        &self,
50        split_path: &ColumnPath,
51        replaced_value: Value,
52    ) -> Option<Value>;
53    fn as_column_path(&self) -> Result<Tagged<ColumnPath>, ShellError>;
54    fn as_path_member(&self) -> Result<PathMember, ShellError>;
55    fn as_string(&self) -> Result<String, ShellError>;
56}
57
58impl ValueExt for Value {
59    fn into_parts(self) -> (UntaggedValue, Tag) {
60        (self.value, self.tag)
61    }
62
63    fn get_data(&self, desc: &str) -> MaybeOwned<'_, Value> {
64        get_data(self, desc)
65    }
66
67    fn get_data_by_key(&self, name: Spanned<&str>) -> Option<Value> {
68        get_data_by_key(self, name)
69    }
70
71    fn get_data_by_member(&self, name: &PathMember) -> Result<Value, ShellError> {
72        get_data_by_member(self, name)
73    }
74
75    fn get_data_by_column_path(
76        &self,
77        path: &ColumnPath,
78        get_error: Box<dyn FnOnce(&Value, &PathMember, ShellError) -> ShellError>,
79    ) -> Result<Value, ShellError> {
80        get_data_by_column_path(self, path, get_error)
81    }
82
83    fn swap_data_by_column_path(
84        &self,
85        path: &ColumnPath,
86        callback: Box<dyn FnOnce(&Value) -> Result<Value, ShellError>>,
87    ) -> Result<Value, ShellError> {
88        swap_data_by_column_path(self, path, callback)
89    }
90
91    fn insert_data_at_path(&self, path: &str, new_value: Value) -> Option<Value> {
92        insert_data_at_path(self, path, new_value)
93    }
94
95    fn insert_data_at_member(
96        &mut self,
97        member: &PathMember,
98        new_value: Value,
99    ) -> Result<(), ShellError> {
100        insert_data_at_member(self, member, new_value)
101    }
102
103    fn forgiving_insert_data_at_column_path(
104        &self,
105        split_path: &ColumnPath,
106        new_value: Value,
107    ) -> Result<Value, ShellError> {
108        forgiving_insert_data_at_column_path(self, split_path, new_value)
109    }
110
111    fn insert_data_at_column_path(
112        &self,
113        split_path: &ColumnPath,
114        new_value: Value,
115    ) -> Result<Value, ShellError> {
116        insert_data_at_column_path(self, split_path, new_value)
117    }
118
119    fn replace_data_at_column_path(
120        &self,
121        split_path: &ColumnPath,
122        replaced_value: Value,
123    ) -> Option<Value> {
124        replace_data_at_column_path(self, split_path, replaced_value)
125    }
126
127    fn as_column_path(&self) -> Result<Tagged<ColumnPath>, ShellError> {
128        as_column_path(self)
129    }
130
131    fn as_path_member(&self) -> Result<PathMember, ShellError> {
132        as_path_member(self)
133    }
134
135    fn as_string(&self) -> Result<String, ShellError> {
136        as_string(self)
137    }
138}
139
140pub fn get_data_by_member(value: &Value, name: &PathMember) -> Result<Value, ShellError> {
141    match &value.value {
142        // If the value is a row, the member is a column name
143        UntaggedValue::Row(o) => match &name.unspanned {
144            // If the member is a string, get the data
145            UnspannedPathMember::String(string) => o
146                .get_data_by_key(string[..].spanned(name.span))
147                .ok_or_else(|| {
148                    ShellError::missing_property(
149                        "row".spanned(value.tag.span),
150                        string.spanned(name.span),
151                    )
152                }),
153
154            // If the member is a number, it's an error
155            UnspannedPathMember::Int(_) => Err(ShellError::invalid_integer_index(
156                "row".spanned(value.tag.span),
157                name.span,
158            )),
159        },
160
161        // If the value is a table
162        UntaggedValue::Table(l) => {
163            match &name.unspanned {
164                // If the member is a string, map over the member
165                UnspannedPathMember::String(string) => {
166                    let mut out = vec![];
167
168                    for item in l {
169                        if let Value {
170                            value: UntaggedValue::Row(o),
171                            ..
172                        } = item
173                        {
174                            if let Some(v) = o.get_data_by_key(string[..].spanned(name.span)) {
175                                out.push(v)
176                            }
177                        }
178                    }
179
180                    if out.is_empty() {
181                        Err(ShellError::missing_property(
182                            "table".spanned(value.tag.span),
183                            string.spanned(name.span),
184                        ))
185                    } else {
186                        Ok(UntaggedValue::Table(out)
187                            .into_value(Tag::new(value.anchor(), name.span)))
188                    }
189                }
190                UnspannedPathMember::Int(int) => {
191                    let index = int.to_usize().ok_or_else(|| {
192                        ShellError::range_error(
193                            ExpectedRange::Usize,
194                            &"massive integer".spanned(name.span),
195                            "indexing",
196                        )
197                    })?;
198
199                    get_data_by_index(value, index.spanned(value.tag.span)).ok_or_else(|| {
200                        ShellError::range_error(0..(l.len()), &int.spanned(name.span), "indexing")
201                    })
202                }
203            }
204        }
205        #[cfg(feature = "dataframe")]
206        UntaggedValue::DataFrame(df) => match &name.unspanned {
207            UnspannedPathMember::String(string) => {
208                let column = df.as_ref().select(string.as_str()).map_err(|e| {
209                    ShellError::labeled_error("Dataframe error", e.to_string(), &name.span)
210                })?;
211
212                Ok(NuDataFrame::dataframe_to_value(
213                    column,
214                    Tag::new(value.anchor(), name.span),
215                ))
216            }
217            UnspannedPathMember::Int(int) => {
218                if df.is_series() {
219                    df.get_value(*int as usize, name.span)
220                } else {
221                    Err(ShellError::labeled_error(
222                        "Column not found",
223                        "Column name not found in the dataframe",
224                        name.span,
225                    ))
226                }
227            }
228        },
229        other => Err(ShellError::type_error(
230            "row or table",
231            other.type_name().spanned(value.tag.span),
232        )),
233    }
234}
235
236pub fn get_data_by_column_path<F>(
237    value: &Value,
238    path: &ColumnPath,
239    get_error: F,
240) -> Result<Value, ShellError>
241where
242    F: FnOnce(&Value, &PathMember, ShellError) -> ShellError,
243{
244    let mut current = value.clone();
245
246    for p in path {
247        let value = get_data_by_member(&current, p);
248
249        match value {
250            Ok(v) => current = v.clone(),
251            Err(e) => return Err(get_error(&current, p, e)),
252        }
253    }
254
255    Ok(current)
256}
257
258pub fn swap_data_by_column_path<F>(
259    value: &Value,
260    path: &ColumnPath,
261    callback: F,
262) -> Result<Value, ShellError>
263where
264    F: FnOnce(&Value) -> Result<Value, ShellError>,
265{
266    let fields = path.clone();
267
268    let to_replace =
269        get_data_by_column_path(value, path, move |obj_source, column_path_tried, error| {
270            let path_members_span = fields.maybe_span().unwrap_or_else(Span::unknown);
271
272            match &obj_source.value {
273                UntaggedValue::Table(rows) => match column_path_tried {
274                    PathMember {
275                        unspanned: UnspannedPathMember::String(column),
276                        ..
277                    } => {
278                        let primary_label = format!("There isn't a column named '{}'", &column);
279
280                        let suggestions: IndexSet<_> = rows
281                            .iter()
282                            .filter_map(|r| {
283                                nu_protocol::did_you_mean(r, column_path_tried.as_string())
284                            })
285                            .map(|s| s[0].to_owned())
286                            .collect();
287                        let mut existing_columns: IndexSet<_> = IndexSet::default();
288                        let mut names: Vec<String> = vec![];
289
290                        for row in rows {
291                            for field in row.data_descriptors() {
292                                if !existing_columns.contains(&field[..]) {
293                                    existing_columns.insert(field.clone());
294                                    names.push(field);
295                                }
296                            }
297                        }
298
299                        if names.is_empty() {
300                            return ShellError::labeled_error_with_secondary(
301                                "Unknown column",
302                                primary_label,
303                                column_path_tried.span,
304                                "Appears to contain rows. Try indexing instead.",
305                                column_path_tried.span.since(path_members_span),
306                            );
307                        } else {
308                            return ShellError::labeled_error_with_secondary(
309                                "Unknown column",
310                                primary_label,
311                                column_path_tried.span,
312                                format!(
313                                    "Perhaps you meant '{}'? Columns available: {}",
314                                    suggestions
315                                        .iter()
316                                        .map(|x| x.to_owned())
317                                        .collect::<Vec<String>>()
318                                        .join(","),
319                                    names.join(", ")
320                                ),
321                                column_path_tried.span.since(path_members_span),
322                            );
323                        };
324                    }
325                    PathMember {
326                        unspanned: UnspannedPathMember::Int(idx),
327                        ..
328                    } => {
329                        let total = rows.len();
330
331                        let secondary_label = if total == 1 {
332                            "The table only has 1 row".to_owned()
333                        } else {
334                            format!("The table only has {} rows (0 to {})", total, total - 1)
335                        };
336
337                        return ShellError::labeled_error_with_secondary(
338                            "Row not found",
339                            format!("There isn't a row indexed at {}", idx),
340                            column_path_tried.span,
341                            secondary_label,
342                            column_path_tried.span.since(path_members_span),
343                        );
344                    }
345                },
346                UntaggedValue::Row(columns) => match column_path_tried {
347                    PathMember {
348                        unspanned: UnspannedPathMember::String(column),
349                        ..
350                    } => {
351                        let primary_label = format!("There isn't a column named '{}'", &column);
352
353                        if let Some(suggestions) =
354                            nu_protocol::did_you_mean(obj_source, column_path_tried.as_string())
355                        {
356                            return ShellError::labeled_error_with_secondary(
357                                "Unknown column",
358                                primary_label,
359                                column_path_tried.span,
360                                format!(
361                                    "Perhaps you meant '{}'? Columns available: {}",
362                                    suggestions[0],
363                                    &obj_source.data_descriptors().join(",")
364                                ),
365                                column_path_tried.span.since(path_members_span),
366                            );
367                        }
368                    }
369                    PathMember {
370                        unspanned: UnspannedPathMember::Int(idx),
371                        ..
372                    } => {
373                        return ShellError::labeled_error_with_secondary(
374                            "No rows available",
375                            format!("A row at '{}' can't be indexed.", &idx),
376                            column_path_tried.span,
377                            format!(
378                                "Appears to contain columns. Columns available: {}",
379                                columns.keys().join(",")
380                            ),
381                            column_path_tried.span.since(path_members_span),
382                        )
383                    }
384                },
385                _ => {}
386            }
387
388            if let Some(suggestions) =
389                nu_protocol::did_you_mean(obj_source, column_path_tried.as_string())
390            {
391                return ShellError::labeled_error(
392                    "Unknown column",
393                    format!("did you mean '{}'?", suggestions[0]),
394                    column_path_tried.span.since(path_members_span),
395                );
396            }
397
398            error
399        });
400
401    let old_value = to_replace?;
402    let replacement = callback(&old_value)?;
403
404    value
405        .replace_data_at_column_path(path, replacement)
406        .ok_or_else(|| {
407            ShellError::labeled_error("missing column-path", "missing column-path", value.tag.span)
408        })
409}
410
411pub fn insert_data_at_path(value: &Value, path: &str, new_value: Value) -> Option<Value> {
412    let mut new_obj = value.clone();
413
414    let split_path: Vec<_> = path.split('.').collect();
415
416    if let UntaggedValue::Row(ref mut o) = new_obj.value {
417        let mut current = o;
418
419        if split_path.len() == 1 {
420            // Special case for inserting at the top level
421            current
422                .entries
423                .insert(path.to_string(), new_value.value.into_value(&value.tag));
424            return Some(new_obj);
425        }
426
427        for idx in 0..split_path.len() {
428            match current.entries.get_mut(split_path[idx]) {
429                Some(next) => {
430                    if idx == (split_path.len() - 2) {
431                        if let UntaggedValue::Row(o) = &mut next.value {
432                            o.entries.insert(
433                                split_path[idx + 1].to_string(),
434                                new_value.value.into_value(&value.tag),
435                            );
436                        }
437                        return Some(new_obj.clone());
438                    } else {
439                        match next.value {
440                            UntaggedValue::Row(ref mut o) => {
441                                current = o;
442                            }
443                            _ => return None,
444                        }
445                    }
446                }
447                _ => return None,
448            }
449        }
450    }
451
452    None
453}
454
455pub fn insert_data_at_member(
456    value: &mut Value,
457    member: &PathMember,
458    new_value: Value,
459) -> Result<(), ShellError> {
460    match &mut value.value {
461        UntaggedValue::Row(dict) => match &member.unspanned {
462            UnspannedPathMember::String(key) => {
463                dict.insert_data_at_key(key, new_value);
464                Ok(())
465            }
466            UnspannedPathMember::Int(_) => Err(ShellError::type_error(
467                "column name",
468                "integer".spanned(member.span),
469            )),
470        },
471        UntaggedValue::Table(array) => match &member.unspanned {
472            UnspannedPathMember::String(_) => Err(ShellError::type_error(
473                "list index",
474                "string".spanned(member.span),
475            )),
476            UnspannedPathMember::Int(int) => {
477                let int = int.to_usize().ok_or_else(|| {
478                    ShellError::range_error(
479                        ExpectedRange::Usize,
480                        &"bigger number".spanned(member.span),
481                        "inserting into a list",
482                    )
483                })?;
484
485                insert_data_at_index(array, int.tagged(member.span), new_value)?;
486                Ok(())
487            }
488        },
489        other => match &member.unspanned {
490            UnspannedPathMember::String(_) => Err(ShellError::type_error(
491                "row",
492                other.type_name().spanned(value.span()),
493            )),
494            UnspannedPathMember::Int(_) => Err(ShellError::type_error(
495                "table",
496                other.type_name().spanned(value.span()),
497            )),
498        },
499    }
500}
501
502pub fn missing_path_members_by_column_path(value: &Value, path: &ColumnPath) -> Option<usize> {
503    let mut current = value.clone();
504
505    for (idx, p) in path.iter().enumerate() {
506        if let Ok(value) = get_data_by_member(&current, p) {
507            current = value;
508        } else {
509            return Some(idx);
510        }
511    }
512
513    None
514}
515
516pub fn forgiving_insert_data_at_column_path(
517    value: &Value,
518    split_path: &ColumnPath,
519    new_value: Value,
520) -> Result<Value, ShellError> {
521    let mut original = value.clone();
522
523    if let Some(missed_at) = missing_path_members_by_column_path(value, split_path) {
524        let mut paths = split_path.iter().skip(missed_at + 1).collect::<Vec<_>>();
525        paths.reverse();
526
527        let mut candidate = new_value;
528
529        for member in &paths {
530            match &member.unspanned {
531                UnspannedPathMember::String(column_name) => {
532                    candidate =
533                        UntaggedValue::row(indexmap! { column_name.into() => candidate.clone()})
534                            .into_value(&candidate.tag)
535                }
536                UnspannedPathMember::Int(int) => {
537                    let mut rows = vec![];
538                    let size = int.to_usize().unwrap_or(0);
539
540                    for _ in 0..=size {
541                        rows.push(
542                            UntaggedValue::Primitive(Primitive::Nothing).into_value(&candidate.tag),
543                        );
544                    }
545                    rows.push(candidate.clone());
546                    candidate = UntaggedValue::Table(rows).into_value(&candidate.tag);
547                }
548            }
549        }
550
551        let cp = ColumnPath::new(
552            split_path
553                .iter()
554                .cloned()
555                .take(split_path.members().len() - missed_at + 1)
556                .collect::<Vec<_>>(),
557        );
558
559        if missed_at == 0 {
560            let current: &mut Value = &mut original;
561            insert_data_at_member(current, &cp.members()[0], candidate)?;
562            return Ok(original);
563        }
564
565        if value
566            .get_data_by_column_path(&cp, Box::new(move |_, _, err| err))
567            .is_ok()
568        {
569            return insert_data_at_column_path(value, &cp, candidate);
570        } else if let Some((last, front)) = cp.split_last() {
571            let mut current: &mut Value = &mut original;
572
573            for member in front {
574                let type_name = current.spanned_type_name();
575
576                current = get_mut_data_by_member(current, member).ok_or_else(|| {
577                    ShellError::missing_property(
578                        member.plain_string(std::usize::MAX).spanned(member.span),
579                        type_name,
580                    )
581                })?
582            }
583
584            insert_data_at_member(current, last, candidate)?;
585
586            return Ok(original);
587        } else {
588            return Err(ShellError::untagged_runtime_error(
589                "Internal error: could not split column path correctly",
590            ));
591        }
592    }
593
594    insert_data_at_column_path(value, split_path, new_value)
595}
596
597pub fn insert_data_at_column_path(
598    value: &Value,
599    split_path: &ColumnPath,
600    new_value: Value,
601) -> Result<Value, ShellError> {
602    if let Some((last, front)) = split_path.split_last() {
603        let mut original = value.clone();
604
605        let mut current: &mut Value = &mut original;
606
607        for member in front {
608            let type_name = current.spanned_type_name();
609
610            current = get_mut_data_by_member(current, member).ok_or_else(|| {
611                ShellError::missing_property(
612                    member.plain_string(std::usize::MAX).spanned(member.span),
613                    type_name,
614                )
615            })?
616        }
617
618        insert_data_at_member(current, last, new_value)?;
619
620        Ok(original)
621    } else {
622        Err(ShellError::untagged_runtime_error(
623            "Internal error: could not split column path correctly",
624        ))
625    }
626}
627
628pub fn replace_data_at_column_path(
629    value: &Value,
630    split_path: &ColumnPath,
631    replaced_value: Value,
632) -> Option<Value> {
633    let mut new_obj: Value = value.clone();
634    let mut current = &mut new_obj;
635    let split_path = split_path.members();
636
637    for idx in 0..split_path.len() {
638        match get_mut_data_by_member(current, &split_path[idx]) {
639            Some(next) => {
640                if idx == (split_path.len() - 1) {
641                    *next = replaced_value.value.into_value(&value.tag);
642                    return Some(new_obj);
643                } else {
644                    current = next;
645                }
646            }
647            None => {
648                return None;
649            }
650        }
651    }
652
653    None
654}
655
656pub fn as_column_path(value: &Value) -> Result<Tagged<ColumnPath>, ShellError> {
657    match &value.value {
658        UntaggedValue::Primitive(Primitive::String(s)) => {
659            let s = s.to_string().spanned(value.tag.span);
660
661            Ok(ColumnPath::build(&s).tagged(&value.tag))
662        }
663
664        UntaggedValue::Primitive(Primitive::ColumnPath(path)) => {
665            Ok(path.clone().tagged(value.tag.clone()))
666        }
667
668        other => Err(ShellError::type_error(
669            "column path",
670            other.type_name().spanned(value.span()),
671        )),
672    }
673}
674
675pub fn as_path_member(value: &Value) -> Result<PathMember, ShellError> {
676    match &value.value {
677        UntaggedValue::Primitive(primitive) => match primitive {
678            Primitive::Int(int) => Ok(PathMember::int(*int, value.tag.span)),
679            Primitive::String(string) => Ok(PathMember::string(string, value.tag.span)),
680            other => Err(ShellError::type_error(
681                "path member",
682                other.type_name().spanned(value.span()),
683            )),
684        },
685        other => Err(ShellError::type_error(
686            "path member",
687            other.type_name().spanned(value.span()),
688        )),
689    }
690}
691
692pub fn as_string(value: &Value) -> Result<String, ShellError> {
693    match &value.value {
694        UntaggedValue::Primitive(Primitive::String(s)) => Ok(s.clone()),
695        UntaggedValue::Primitive(Primitive::Date(dt)) => Ok(dt.format("%Y-%m-%d").to_string()),
696        UntaggedValue::Primitive(Primitive::Boolean(x)) => Ok(x.to_string()),
697        UntaggedValue::Primitive(Primitive::Decimal(x)) => Ok(x.to_string()),
698        UntaggedValue::Primitive(Primitive::Int(x)) => Ok(x.to_string()),
699        UntaggedValue::Primitive(Primitive::Filesize(x)) => Ok(x.to_string()),
700        UntaggedValue::Primitive(Primitive::FilePath(x)) => Ok(x.display().to_string()),
701        UntaggedValue::Primitive(Primitive::BigInt(x)) => Ok(x.to_string()),
702        UntaggedValue::Primitive(Primitive::ColumnPath(path)) => Ok(path
703            .iter()
704            .map(|member| match &member.unspanned {
705                UnspannedPathMember::String(name) => name.to_string(),
706                UnspannedPathMember::Int(n) => n.to_string(),
707            })
708            .join(".")),
709
710        // TODO: this should definitely be more general with better errors
711        other => Err(ShellError::labeled_error(
712            "Expected string",
713            other.type_name(),
714            &value.tag,
715        )),
716    }
717}
718
719fn insert_data_at_index(
720    list: &mut Vec<Value>,
721    index: Tagged<usize>,
722    new_value: Value,
723) -> Result<(), ShellError> {
724    if index.item >= list.len() {
725        if index.item == list.len() {
726            list.push(new_value);
727            return Ok(());
728        }
729
730        let mut idx = list.len();
731
732        loop {
733            list.push(UntaggedValue::Primitive(Primitive::Nothing).into_value(&new_value.tag));
734
735            idx += 1;
736
737            if idx == index.item {
738                list.push(new_value);
739                return Ok(());
740            }
741        }
742    } else {
743        list[index.item] = new_value;
744        Ok(())
745    }
746}
747
748pub fn get_data<'value>(value: &'value Value, desc: &str) -> MaybeOwned<'value, Value> {
749    match &value.value {
750        UntaggedValue::Primitive(_) => MaybeOwned::Borrowed(value),
751        UntaggedValue::Row(o) => o.get_data(desc),
752        UntaggedValue::Block(_) | UntaggedValue::Table(_) | UntaggedValue::Error(_) => {
753            MaybeOwned::Owned(UntaggedValue::nothing().into_untagged_value())
754        }
755        #[cfg(feature = "dataframe")]
756        UntaggedValue::DataFrame(_) | UntaggedValue::FrameStruct(_) => {
757            MaybeOwned::Owned(UntaggedValue::nothing().into_untagged_value())
758        }
759    }
760}
761
762pub(crate) fn get_data_by_index(value: &Value, idx: Spanned<usize>) -> Option<Value> {
763    match &value.value {
764        UntaggedValue::Table(value_set) => {
765            let value = value_set.get(idx.item)?;
766            Some(
767                value
768                    .value
769                    .clone()
770                    .into_value(Tag::new(value.anchor(), idx.span)),
771            )
772        }
773        _ => None,
774    }
775}
776
777pub fn get_data_by_key(value: &Value, name: Spanned<&str>) -> Option<Value> {
778    match &value.value {
779        UntaggedValue::Row(o) => o.get_data_by_key(name),
780        UntaggedValue::Table(l) => {
781            let mut out = vec![];
782            for item in l {
783                match item {
784                    Value {
785                        value: UntaggedValue::Row(o),
786                        ..
787                    } => match o.get_data_by_key(name) {
788                        Some(v) => out.push(v),
789                        None => out.push(UntaggedValue::nothing().into_untagged_value()),
790                    },
791                    _ => out.push(UntaggedValue::nothing().into_untagged_value()),
792                }
793            }
794
795            if !out.is_empty() {
796                Some(UntaggedValue::Table(out).into_value(name.span))
797            } else {
798                None
799            }
800        }
801        _ => None,
802    }
803}
804
805pub(crate) fn get_mut_data_by_member<'value>(
806    value: &'value mut Value,
807    name: &PathMember,
808) -> Option<&'value mut Value> {
809    match &mut value.value {
810        UntaggedValue::Row(o) => match &name.unspanned {
811            UnspannedPathMember::String(string) => o.get_mut_data_by_key(string),
812            UnspannedPathMember::Int(_) => None,
813        },
814        UntaggedValue::Table(l) => match &name.unspanned {
815            UnspannedPathMember::String(string) => {
816                for item in l {
817                    if let Value {
818                        value: UntaggedValue::Row(o),
819                        ..
820                    } = item
821                    {
822                        if let Some(v) = o.get_mut_data_by_key(string) {
823                            return Some(v);
824                        }
825                    }
826                }
827                None
828            }
829            UnspannedPathMember::Int(int) => {
830                let index = int.to_usize()?;
831                l.get_mut(index)
832            }
833        },
834        _ => None,
835    }
836}