1use nu_color_config::TextStyle;
2use nu_engine::column::get_columns;
3use nu_protocol::{Config, Record, ShellError, Value};
4
5use crate::{
6 NuRecordsValue, NuTable, StringResult, TableOpts, TableOutput, TableResult, clean_charset,
7 colorize_space,
8 common::{
9 INDEX_COLUMN_NAME, NuText, check_value, configure_table, get_empty_style, get_header_style,
10 get_index_style, get_value_style, nu_value_to_string_colored,
11 },
12 types::has_index,
13};
14
15pub struct JustTable;
16
17impl JustTable {
18 pub fn table(input: Vec<Value>, opts: TableOpts<'_>) -> StringResult {
19 list_table(input, opts)
20 }
21
22 pub fn kv_table(record: Record, opts: TableOpts<'_>) -> StringResult {
23 kv_table(record, opts)
24 }
25}
26
27fn list_table(input: Vec<Value>, opts: TableOpts<'_>) -> Result<Option<String>, ShellError> {
28 let output = create_table(input, &opts)?;
29 let mut out = match output {
30 Some(out) => out,
31 None => return Ok(None),
32 };
33
34 colorize_space(out.table.get_records_mut(), &opts.style_computer);
36
37 configure_table(&mut out, opts.config, &opts.style_computer, opts.mode);
38 let table = out.table.draw(opts.width);
39
40 Ok(table)
41}
42
43fn get_key_style(topts: &TableOpts<'_>) -> TextStyle {
44 get_header_style(&topts.style_computer).alignment(nu_color_config::Alignment::Left)
45}
46
47fn kv_table(record: Record, opts: TableOpts<'_>) -> StringResult {
48 let mut table = NuTable::new(record.len(), 2);
49 table.set_index_style(get_key_style(&opts));
50 table.set_indent(opts.config.table.padding);
51
52 for (i, (key, value)) in record.into_iter().enumerate() {
53 opts.signals.check(&opts.span)?;
54
55 let value = nu_value_to_string_colored(&value, opts.config, &opts.style_computer);
56
57 table.insert((i, 0), key);
58 table.insert((i, 1), value);
59 }
60
61 let mut out = TableOutput::from_table(table, false, true);
62 configure_table(&mut out, opts.config, &opts.style_computer, opts.mode);
63 let table = out.table.draw(opts.width);
64
65 Ok(table)
66}
67
68fn create_table(input: Vec<Value>, opts: &TableOpts<'_>) -> TableResult {
69 if input.is_empty() {
70 return Ok(None);
71 }
72
73 let headers = get_columns(&input);
74 let with_index = has_index(opts, &headers);
75 let with_header = !headers.is_empty();
76 let row_offset = opts.index_offset;
77
78 let table = match (with_header, with_index) {
79 (true, true) => create_table_with_header_and_index(input, headers, row_offset, opts)?,
80 (true, false) => create_table_with_header(input, headers, opts)?,
81 (false, true) => create_table_with_no_header_and_index(input, row_offset, opts)?,
82 (false, false) => create_table_with_no_header(input, opts)?,
83 };
84
85 let table = table.map(|table| TableOutput::from_table(table, with_header, with_index));
86
87 Ok(table)
88}
89
90fn create_table_with_header(
91 input: Vec<Value>,
92 headers: Vec<String>,
93 opts: &TableOpts<'_>,
94) -> Result<Option<NuTable>, ShellError> {
95 let count_rows = input.len() + 1;
96 let count_columns = headers.len();
97 let mut table = NuTable::new(count_rows, count_columns);
98 table.set_header_style(get_header_style(&opts.style_computer));
99 table.set_index_style(get_index_style(&opts.style_computer));
100 table.set_indent(opts.config.table.padding);
101
102 for (row, item) in input.into_iter().enumerate() {
103 opts.signals.check(&opts.span)?;
104 check_value(&item)?;
105
106 for (col, header) in headers.iter().enumerate() {
107 let (text, style) = get_string_value_with_header(&item, header, opts);
108
109 let pos = (row + 1, col);
110 table.insert(pos, text);
111 table.insert_style(pos, style);
112 }
113 }
114
115 let headers = collect_headers(headers, false);
116 table.set_row(0, headers);
117
118 Ok(Some(table))
119}
120
121fn create_table_with_header_and_index(
122 input: Vec<Value>,
123 headers: Vec<String>,
124 row_offset: usize,
125 opts: &TableOpts<'_>,
126) -> Result<Option<NuTable>, ShellError> {
127 let head = collect_headers(headers, true);
128
129 let count_rows = input.len() + 1;
130 let count_columns = head.len();
131
132 let mut table = NuTable::new(count_rows, count_columns);
133 table.set_header_style(get_header_style(&opts.style_computer));
134 table.set_index_style(get_index_style(&opts.style_computer));
135 table.set_indent(opts.config.table.padding);
136
137 table.set_row(0, head.clone());
138
139 for (row, item) in input.into_iter().enumerate() {
140 opts.signals.check(&opts.span)?;
141 check_value(&item)?;
142
143 let text = get_table_row_index(&item, opts.config, row, row_offset);
144 table.insert((row + 1, 0), text);
145
146 for (col, head) in head.iter().enumerate().skip(1) {
147 let (text, style) = get_string_value_with_header(&item, head.as_ref(), opts);
148
149 let pos = (row + 1, col);
150 table.insert(pos, text);
151 table.insert_style(pos, style);
152 }
153 }
154
155 Ok(Some(table))
156}
157
158fn create_table_with_no_header(
159 input: Vec<Value>,
160 opts: &TableOpts<'_>,
161) -> Result<Option<NuTable>, ShellError> {
162 let mut table = NuTable::new(input.len(), 1);
163 table.set_index_style(get_index_style(&opts.style_computer));
164 table.set_indent(opts.config.table.padding);
165
166 for (row, item) in input.into_iter().enumerate() {
167 opts.signals.check(&opts.span)?;
168 check_value(&item)?;
169
170 let (text, style) = get_string_value(&item, opts);
171
172 table.insert((row, 0), text);
173 table.insert_style((row, 0), style);
174 }
175
176 Ok(Some(table))
177}
178
179fn create_table_with_no_header_and_index(
180 input: Vec<Value>,
181 row_offset: usize,
182 opts: &TableOpts<'_>,
183) -> Result<Option<NuTable>, ShellError> {
184 let mut table = NuTable::new(input.len(), 1 + 1);
185 table.set_index_style(get_index_style(&opts.style_computer));
186 table.set_indent(opts.config.table.padding);
187
188 for (row, item) in input.into_iter().enumerate() {
189 opts.signals.check(&opts.span)?;
190 check_value(&item)?;
191
192 let index = get_table_row_index(&item, opts.config, row, row_offset);
193 let (value, style) = get_string_value(&item, opts);
194
195 table.insert((row, 0), index);
196 table.insert((row, 1), value);
197 table.insert_style((row, 1), style);
198 }
199
200 Ok(Some(table))
201}
202
203fn get_string_value_with_header(item: &Value, header: &str, opts: &TableOpts) -> NuText {
204 match item {
205 Value::Record { val, .. } => match val.get(header) {
206 Some(value) => get_string_value(value, opts),
207 None => get_empty_style(
208 opts.config.table.missing_value_symbol.clone(),
209 &opts.style_computer,
210 ),
211 },
212 value => get_string_value(value, opts),
213 }
214}
215
216fn get_string_value(item: &Value, opts: &TableOpts) -> NuText {
217 let (mut text, style) = get_value_style(item, opts.config, &opts.style_computer);
218
219 let is_string = matches!(item, Value::String { .. });
220 if is_string {
221 text = clean_charset(&text);
222 }
223
224 (text, style)
225}
226
227fn get_table_row_index(item: &Value, config: &Config, row: usize, offset: usize) -> String {
228 match item {
229 Value::Record { val, .. } => val
230 .get(INDEX_COLUMN_NAME)
231 .map(|value| value.to_expanded_string("", config))
232 .unwrap_or_else(|| (row + offset).to_string()),
233 _ => (row + offset).to_string(),
234 }
235}
236
237fn collect_headers(headers: Vec<String>, index: bool) -> Vec<NuRecordsValue> {
238 let length = if index {
241 headers.len() + 1
242 } else {
243 headers.len()
244 };
245
246 let mut v = Vec::with_capacity(length);
247
248 if index {
249 v.insert(0, NuRecordsValue::new("#".into()));
250 }
251
252 for text in headers {
253 if text == INDEX_COLUMN_NAME {
254 continue;
255 }
256
257 v.push(NuRecordsValue::new(text));
258 }
259
260 v
261}