1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
use crate::prelude::*; use nu_engine::WholeStreamCommand; use nu_errors::ShellError; use nu_protocol::{ Dictionary, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value, }; use nu_source::Tagged; pub struct Command; impl WholeStreamCommand for Command { fn name(&self) -> &str { "flatten" } fn signature(&self) -> Signature { Signature::build("flatten").rest(SyntaxShape::String, "optionally flatten data by column") } fn usage(&self) -> &str { "Flatten the table." } fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> { flatten(args) } fn examples(&self) -> Vec<Example> { vec![ Example { description: "flatten a table", example: "echo [[N, u, s, h, e, l, l]] | flatten | first", result: Some(vec![Value::from("N")]), }, Example { description: "flatten a column having a nested table", example: "echo [[origin, people]; [Ecuador, (echo [[name, meal]; ['Andres', 'arepa']])]] | flatten | get meal", result: Some(vec![Value::from("arepa")]), }, Example { description: "restrict the flattening by passing column names", example: "echo [[origin, crate, versions]; [World, (echo [[name]; ['nu-cli']]), ['0.21', '0.22']]] | flatten versions | last | get versions", result: Some(vec![Value::from("0.22")]), } ] } } fn flatten(args: CommandArgs) -> Result<ActionStream, ShellError> { let tag = args.call_info.name_tag.clone(); let columns: Vec<Tagged<String>> = args.rest(0)?; let input = args.input; Ok(input .map(move |item| flat_value(&columns, &item, &tag).into_iter()) .flatten() .into_action_stream()) } enum TableInside<'a> { Entries(&'a str, &'a Tag, Vec<&'a Value>), } fn flat_value( columns: &[Tagged<String>], item: &Value, name_tag: impl Into<Tag>, ) -> Vec<Result<ReturnSuccess, ShellError>> { let tag = item.tag.clone(); let name_tag = name_tag.into(); let res = { if item.is_row() { let mut out = TaggedDictBuilder::new(tag); let mut a_table = None; let mut tables_explicitly_flattened = 0; for (column, value) in item.row_entries() { let column_requested = columns.iter().find(|c| c.item == *column); if let Value { value: UntaggedValue::Row(Dictionary { entries: mapa }), .. } = value { if column_requested.is_none() && !columns.is_empty() { if out.contains_key(column) { out.insert_value(format!("{}_{}", column, column), value.clone()); } else { out.insert_value(column, value.clone()); } continue; } for (k, v) in mapa.into_iter() { if out.contains_key(k) { out.insert_value(format!("{}_{}", column, k), v.clone()); } else { out.insert_value(k, v.clone()); } } } else if value.is_table() { if tables_explicitly_flattened >= 1 && column_requested.is_some() { let attempted = if let Some(name) = column_requested { name.span() } else { name_tag.span }; let already_flattened = if let Some(TableInside::Entries(_, column_tag, _)) = a_table { column_tag.span } else { name_tag.span }; return vec![ReturnSuccess::value( UntaggedValue::Error(ShellError::labeled_error_with_secondary( "can only flatten one inner table at the same time", "tried flattening more than one column with inner tables", attempted, "...but is flattened already", already_flattened, )) .into_value(name_tag), )]; } if !columns.is_empty() { if let Some(requested) = column_requested { a_table = Some(TableInside::Entries( &requested.item, &requested.tag, value.table_entries().collect(), )); tables_explicitly_flattened += 1; } else { out.insert_value(column, value.clone()); } } else if a_table.is_none() { a_table = Some(TableInside::Entries( column, &value.tag, value.table_entries().collect(), )) } else { out.insert_value(column, value.clone()); } } else { out.insert_value(column, value.clone()); } } let mut expanded = vec![]; if let Some(TableInside::Entries(column, _, entries)) = a_table { for entry in entries.into_iter() { let mut base = out.clone(); base.insert_value(column, entry.clone()); expanded.push(base.into_value()); } } else { expanded.push(out.into_value()); } expanded } else if item.is_table() { item.table_entries().map(Clone::clone).collect() } else { vec![item.clone()] } }; res.into_iter().map(ReturnSuccess::value).collect() }