nu_table/types/
general.rs

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