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