tabulate_rs/
table.rs

1use std::{
2    cmp::max,
3    collections::{HashMap, HashSet},
4};
5
6use serde::Serialize;
7use serde_json::{Map, Value};
8use textwrap::{Options as WrapOptions, WordSplitter, wrap};
9
10use crate::{
11    alignment::{Alignment, DecimalLayout, align_cell},
12    constants::{MIN_PADDING, MULTILINE_FORMATS, SEPARATING_LINE},
13    format::{LineFormat, RowFormat, TableFormat, table_format},
14    options::{
15        FormatSpec, HeaderAlignment, Headers, MissingValues, RowAlignment, ShowIndex,
16        TableFormatChoice, TabulateOptions,
17    },
18    width::visible_width,
19};
20
21/// Errors emitted while attempting to render a table.
22#[derive(thiserror::Error, Debug)]
23pub enum TabulateError {
24    /// The requested table format is unknown.
25    #[error("unknown table format: {0}")]
26    UnknownFormat(String),
27    /// Attempted to tabulate data that does not contain any rows.
28    #[error("no rows to tabulate")]
29    EmptyData,
30    /// Provided explicit headers for a list of dict-like rows.
31    #[error(
32        "headers for a list of dicts must be a mapping or keyword such as 'keys' or 'firstrow'"
33    )]
34    InvalidHeadersForObjects,
35    /// Attempted to use a headers variant that is not yet supported.
36    #[error("header mode '{0}' is not yet supported in the Rust port")]
37    UnsupportedHeaders(String),
38    /// Provided index values length does not match the number of data rows.
39    #[error("index length {found} does not match number of rows {expected}")]
40    IndexLengthMismatch {
41        /// Number of data rows expected to match the index values length.
42        expected: usize,
43        /// Number of index values provided by the caller.
44        found: usize,
45    },
46    /// Converting user data into a tabular representation failed.
47    #[error("failed to serialise row: {0}")]
48    Serialization(String),
49}
50
51/// Render `tabular_data` according to the provided `options`.
52pub fn tabulate<Data, Row>(
53    tabular_data: Data,
54    options: TabulateOptions,
55) -> Result<String, TabulateError>
56where
57    Data: IntoIterator<Item = Row>,
58    Row: Serialize,
59{
60    Tabulator::new(options).tabulate(tabular_data)
61}
62
63/// Stateful renderer used to build textual tables.
64pub struct Tabulator {
65    options: TabulateOptions,
66}
67
68impl Tabulator {
69    /// Create a new renderer.
70    pub fn new(options: TabulateOptions) -> Self {
71        Self { options }
72    }
73
74    /// Render the given iterator of rows into a table string.
75    pub fn tabulate<Data, Row>(&self, tabular_data: Data) -> Result<String, TabulateError>
76    where
77        Data: IntoIterator<Item = Row>,
78        Row: Serialize,
79    {
80        let (format, format_name_opt) = self.resolve_format()?;
81        let mut options = self.options.clone();
82        let format_name_lower = format_name_opt.map(|name| name.to_ascii_lowercase());
83        let format_name_key = format_name_lower.as_deref();
84
85        if matches!(format_name_key, Some("pretty")) {
86            options.disable_numparse = true;
87            if options.num_align.is_none() {
88                options.num_align = Some(Alignment::Center);
89            }
90            if options.str_align.is_none() {
91                options.str_align = Some(Alignment::Center);
92            }
93        }
94        if matches!(format_name_key, Some("colon_grid")) && options.col_global_align.is_none() {
95            options.col_global_align = Some(Alignment::Left);
96        }
97
98        let mut normalized = normalize_tabular_data(tabular_data, &options)?;
99
100        let default_index = normalized.default_index.clone();
101        let default_index_header = normalized.default_index_header.clone();
102        apply_show_index(
103            &mut normalized.rows,
104            &mut normalized.headers,
105            &options.show_index,
106            options.disable_numparse,
107            default_index,
108            default_index_header,
109        )?;
110
111        apply_wrapping(&mut normalized.rows, &mut normalized.headers, &options);
112
113        let render_plan = RenderPlan::build(
114            &normalized.rows,
115            &normalized.headers,
116            format,
117            &options,
118            options.table_format_name(),
119        );
120        Ok(render_plan.render())
121    }
122
123    fn resolve_format(&self) -> Result<(&TableFormat, Option<&str>), TabulateError> {
124        match &self.options.table_format {
125            TableFormatChoice::Name(name) => table_format(name)
126                .map(|format| (format as &TableFormat, Some(name.as_str())))
127                .ok_or_else(|| TabulateError::UnknownFormat(name.clone())),
128            TableFormatChoice::Custom(format) => Ok((&**format, None)),
129        }
130    }
131}
132
133#[derive(Clone, Debug)]
134struct CellData {
135    text: String,
136    value: ParsedValue,
137}
138
139impl CellData {
140    fn new(text: String, value: ParsedValue) -> Self {
141        Self { text, value }
142    }
143
144    fn empty() -> Self {
145        Self::new(String::new(), ParsedValue::String)
146    }
147}
148
149#[derive(Clone, Debug)]
150enum ParsedValue {
151    Int(i128),
152    Float(f64),
153    Bool(bool),
154    String,
155}
156
157fn parse_value(text: &str, disable_numparse: bool) -> ParsedValue {
158    if disable_numparse {
159        return ParsedValue::String;
160    }
161    let trimmed = text.trim();
162    if trimmed.is_empty() {
163        return ParsedValue::String;
164    }
165
166    let normalized = normalize_numeric_candidate(trimmed);
167    if let Ok(value) = normalized.parse::<i128>() {
168        return ParsedValue::Int(value);
169    }
170
171    if let Ok(value) = normalized.parse::<f64>() {
172        return ParsedValue::Float(value);
173    }
174
175    match trimmed {
176        "true" | "True" => ParsedValue::Bool(true),
177        "false" | "False" => ParsedValue::Bool(false),
178        _ => ParsedValue::String,
179    }
180}
181
182#[derive(Clone, Debug)]
183enum RowKind {
184    Data(Vec<CellData>),
185    Separator,
186}
187
188struct NormalizedTable {
189    headers: Vec<String>,
190    rows: Vec<RowKind>,
191    default_index: Option<Vec<String>>,
192    default_index_header: Option<String>,
193}
194
195#[derive(Clone, Debug)]
196enum RowValue {
197    Array(Vec<Value>),
198    Object(Vec<(String, Value)>),
199    Separator,
200}
201
202fn process_item(value: Value, output: &mut Vec<Value>) {
203    output.push(value);
204}
205
206fn expand_columnar_object(row_values: &mut Vec<RowValue>) -> Option<Vec<String>> {
207    if row_values.len() != 1 {
208        return None;
209    }
210
211    let original_row = row_values.remove(0);
212    let entries = match original_row {
213        RowValue::Object(entries) => entries,
214        other => {
215            row_values.insert(0, other);
216            return None;
217        }
218    };
219
220    let mut columns = Vec::with_capacity(entries.len());
221    let mut keys = Vec::with_capacity(entries.len());
222    let mut max_len = 0usize;
223
224    for (key, value) in entries.iter() {
225        match value {
226            Value::Array(items) => {
227                max_len = max(max_len, items.len());
228                columns.push(items.clone());
229                keys.push(key.clone());
230            }
231            _ => {
232                row_values.insert(0, RowValue::Object(entries));
233                return None;
234            }
235        }
236    }
237
238    if max_len == 0 {
239        row_values.clear();
240        return Some(keys);
241    }
242
243    for column in columns.iter_mut() {
244        while column.len() < max_len {
245            column.push(Value::Null);
246        }
247    }
248
249    let mut expanded = Vec::with_capacity(max_len);
250    for idx in 0..max_len {
251        let mut row = Vec::with_capacity(columns.len());
252        for column in &columns {
253            row.push(column[idx].clone());
254        }
255        expanded.push(RowValue::Array(row));
256    }
257
258    *row_values = expanded;
259    Some(keys)
260}
261
262struct DataFrameData {
263    columns: Vec<String>,
264    rows: Vec<Vec<Value>>,
265    index: Option<Vec<Value>>,
266    index_label: Option<String>,
267}
268
269struct NumpyRecArrayData {
270    columns: Vec<String>,
271    rows: Vec<Vec<Value>>,
272}
273
274fn parse_dataframe(value: Value) -> Option<DataFrameData> {
275    let mut map = match value {
276        Value::Object(map) => map,
277        _ => return None,
278    };
279
280    match map.remove("__tabulate_dataframe__") {
281        Some(Value::Bool(true)) => {}
282        _ => return None,
283    }
284
285    let columns = match map.remove("columns")? {
286        Value::Array(values) => values.into_iter().map(value_to_plain_string).collect(),
287        _ => return None,
288    };
289
290    let rows = match map.remove("data")? {
291        Value::Array(rows) => {
292            let mut result = Vec::with_capacity(rows.len());
293            for row in rows {
294                match row {
295                    Value::Array(values) => result.push(values),
296                    _ => return None,
297                }
298            }
299            result
300        }
301        _ => return None,
302    };
303
304    let index = match map.remove("index") {
305        Some(Value::Array(values)) => Some(values),
306        Some(Value::Null) | None => None,
307        _ => return None,
308    };
309
310    let mut index_label = match map.remove("index_label") {
311        Some(Value::String(label)) => Some(label),
312        Some(Value::Null) => None,
313        Some(Value::Array(labels)) => {
314            let combined = labels
315                .into_iter()
316                .map(value_to_plain_string)
317                .collect::<Vec<_>>()
318                .join(" ");
319            if combined.is_empty() {
320                None
321            } else {
322                Some(combined)
323            }
324        }
325        Some(_) => return None,
326        None => None,
327    };
328
329    if index_label.is_none() {
330        if let Some(Value::String(label)) = map.remove("index_name") {
331            index_label = Some(label);
332        } else if let Some(Value::Array(labels)) = map.remove("index_names") {
333            let combined = labels
334                .into_iter()
335                .map(value_to_plain_string)
336                .collect::<Vec<_>>()
337                .join(" ");
338            if !combined.is_empty() {
339                index_label = Some(combined);
340            }
341        }
342    }
343
344    Some(DataFrameData {
345        columns,
346        rows,
347        index,
348        index_label,
349    })
350}
351
352fn parse_numpy_recarray(value: &Value) -> Option<NumpyRecArrayData> {
353    let map = match value {
354        Value::Object(map) => map,
355        _ => return None,
356    };
357    match map.get("__tabulate_numpy_recarray__") {
358        Some(Value::Bool(true)) => {}
359        _ => return None,
360    }
361    let dtype = match map.get("dtype") {
362        Some(Value::Array(items)) => items
363            .iter()
364            .filter_map(|entry| match entry {
365                Value::Array(parts) if !parts.is_empty() => parts
366                    .first()
367                    .and_then(|name| name.as_str())
368                    .map(str::to_string),
369                _ => None,
370            })
371            .collect::<Vec<_>>(),
372        _ => return None,
373    };
374    if dtype.is_empty() {
375        return None;
376    }
377    let rows = match map.get("rows") {
378        Some(Value::Array(items)) => items
379            .iter()
380            .map(|row| match row {
381                Value::Array(values) => values.clone(),
382                _ => Vec::new(),
383            })
384            .collect::<Vec<_>>(),
385        _ => return None,
386    };
387    if rows.iter().any(|row| row.len() != dtype.len()) {
388        return None;
389    }
390    Some(NumpyRecArrayData {
391        columns: dtype,
392        rows,
393    })
394}
395
396fn value_to_plain_string(value: Value) -> String {
397    match value {
398        Value::String(s) => s,
399        Value::Number(n) => n.to_string(),
400        Value::Bool(flag) => flag.to_string(),
401        Value::Null => String::new(),
402        other => other.to_string(),
403    }
404}
405
406fn normalize_tabular_data<Data, Row>(
407    tabular_data: Data,
408    options: &TabulateOptions,
409) -> Result<NormalizedTable, TabulateError>
410where
411    Data: IntoIterator<Item = Row>,
412    Row: Serialize,
413{
414    let mut raw_rows = Vec::new();
415    for item in tabular_data {
416        process_item(
417            serde_json::to_value(item)
418                .map_err(|err| TabulateError::Serialization(err.to_string()))?,
419            &mut raw_rows,
420        );
421    }
422
423    let mut keys_order = Vec::new();
424    let mut seen_keys = HashSet::new();
425
426    let mut row_values = Vec::new();
427    let mut default_index: Option<Vec<String>> = None;
428    let mut default_index_header: Option<String> = None;
429    let mut has_dataframe = false;
430    let mut had_object_row = false;
431    for value in raw_rows {
432        if let Some(recarray) = parse_numpy_recarray(&value) {
433            for column in &recarray.columns {
434                if seen_keys.insert(column.clone()) {
435                    keys_order.push(column.clone());
436                }
437            }
438            for row in recarray.rows {
439                row_values.push(RowValue::Array(row));
440            }
441            continue;
442        }
443
444        if let Some(dataframe) = parse_dataframe(value.clone()) {
445            has_dataframe = true;
446            let DataFrameData {
447                columns,
448                rows,
449                index,
450                index_label,
451            } = dataframe;
452
453            let row_count = rows.len();
454
455            if default_index.is_none()
456                && let Some(index_values) = index
457                && index_values.len() == row_count
458            {
459                let mut converted = Vec::with_capacity(index_values.len());
460                for value in index_values {
461                    let cell = cell_from_value(value, options, None);
462                    converted.push(cell.text);
463                }
464                default_index = Some(converted);
465            }
466            if default_index_header.is_none() {
467                default_index_header = index_label;
468            }
469
470            for value in columns.iter() {
471                if seen_keys.insert(value.clone()) {
472                    keys_order.push(value.clone());
473                }
474            }
475
476            for mut row in rows.into_iter() {
477                if row.len() < columns.len() {
478                    row.resize(columns.len(), Value::Null);
479                }
480                let entries = columns
481                    .iter()
482                    .cloned()
483                    .zip(row.into_iter())
484                    .collect::<Vec<_>>();
485                row_values.push(RowValue::Object(entries));
486                had_object_row = true;
487            }
488            continue;
489        }
490
491        if is_separator_value(&value) {
492            row_values.push(RowValue::Separator);
493            continue;
494        }
495        match value {
496            Value::Array(values) => row_values.push(RowValue::Array(values)),
497            Value::Object(map) => {
498                had_object_row = true;
499                row_values.push(RowValue::Object(map.into_iter().collect()));
500            }
501            other => row_values.push(RowValue::Array(vec![other])),
502        }
503    }
504
505    let columnar_keys = expand_columnar_object(&mut row_values);
506
507    if had_object_row
508        && columnar_keys.is_none()
509        && !has_dataframe
510        && let Headers::Explicit(values) = &options.headers
511        && !values.is_empty()
512    {
513        return Err(TabulateError::InvalidHeadersForObjects);
514    }
515
516    let mut headers = Vec::new();
517    if let Some(keys) = &columnar_keys {
518        for key in keys {
519            if seen_keys.insert(key.clone()) {
520                keys_order.push(key.clone());
521            }
522        }
523    }
524    let mut first_header_map: Option<Vec<(String, Value)>> = None;
525    let header_mapping = match &options.headers {
526        Headers::Mapping(entries) => Some(entries.clone()),
527        _ => None,
528    };
529    let mapping_keys: Option<Vec<String>> = header_mapping
530        .as_ref()
531        .map(|entries| entries.iter().map(|(key, _)| key.clone()).collect());
532
533    if matches!(options.headers, Headers::FirstRow)
534        && let Some(index) = row_values
535            .iter()
536            .position(|row| matches!(row, RowValue::Array(_) | RowValue::Object(_)))
537    {
538        match row_values.remove(index) {
539            RowValue::Array(values) => {
540                headers = values
541                    .into_iter()
542                    .map(|value| value_to_header(&value, options.preserve_whitespace))
543                    .collect();
544            }
545            RowValue::Object(entries) => {
546                for (key, _) in &entries {
547                    if seen_keys.insert(key.clone()) {
548                        keys_order.push(key.clone());
549                    }
550                }
551                first_header_map = Some(entries);
552            }
553            RowValue::Separator => {}
554        }
555    }
556
557    for row in &row_values {
558        if let RowValue::Object(entries) = row {
559            for (key, _) in entries {
560                if seen_keys.insert(key.clone()) {
561                    keys_order.push(key.clone());
562                }
563            }
564        }
565    }
566
567    if let Some(mapping_keys) = &mapping_keys {
568        for key in mapping_keys {
569            if seen_keys.insert(key.clone()) {
570                keys_order.push(key.clone());
571            }
572        }
573    }
574
575    if let Some(entries) = &first_header_map {
576        let mut header_lookup = HashMap::new();
577        for (key, value) in entries {
578            header_lookup.insert(key.clone(), value.clone());
579        }
580        headers = keys_order
581            .iter()
582            .map(|key| {
583                header_lookup
584                    .get(key)
585                    .map(|value| value_to_header(value, options.preserve_whitespace))
586                    .unwrap_or_else(|| key.clone())
587            })
588            .collect();
589    }
590
591    let mut column_count = 0usize;
592    for row in &row_values {
593        match row {
594            RowValue::Array(values) => column_count = column_count.max(values.len()),
595            RowValue::Object(_) => column_count = column_count.max(keys_order.len()),
596            RowValue::Separator => {}
597        }
598    }
599
600    if let Some(mapping) = &header_mapping {
601        column_count = column_count.max(mapping.len());
602    }
603
604    let candidate_headers = headers;
605    let headers = match &options.headers {
606        Headers::Explicit(values) => values.clone(),
607        Headers::Mapping(entries) => entries.iter().map(|(_, label)| label.clone()).collect(),
608        Headers::None => {
609            if candidate_headers.is_empty() {
610                vec![String::new(); column_count]
611            } else {
612                candidate_headers.clone()
613            }
614        }
615        Headers::FirstRow => candidate_headers.clone(),
616        Headers::Keys => {
617            if !keys_order.is_empty() {
618                keys_order.clone()
619            } else {
620                (0..column_count).map(|idx| idx.to_string()).collect()
621            }
622        }
623    };
624
625    let mut headers = headers;
626    column_count = column_count.max(headers.len());
627    if headers.len() < column_count {
628        let pad = column_count - headers.len();
629        let mut padded = Vec::with_capacity(column_count);
630        padded.extend((0..pad).map(|_| String::new()));
631        padded.extend(headers);
632        headers = padded;
633    }
634
635    let object_columns = if let Some(mapping_keys) = &mapping_keys {
636        mapping_keys.clone()
637    } else if !keys_order.is_empty() {
638        keys_order.clone()
639    } else {
640        headers.clone()
641    };
642
643    let mut rows = Vec::new();
644    for row in row_values {
645        match row {
646            RowValue::Separator => rows.push(RowKind::Separator),
647            RowValue::Array(values) => {
648                let mut cells = if let Some(keys) = &columnar_keys {
649                    let mut map = HashMap::new();
650                    for (idx, value) in values.into_iter().enumerate() {
651                        if let Some(key) = keys.get(idx) {
652                            map.insert(key.clone(), value);
653                        }
654                    }
655                    let mut ordered = Vec::with_capacity(column_count);
656                    for (col_idx, key) in object_columns.iter().enumerate() {
657                        let value = map.remove(key).unwrap_or(Value::Null);
658                        ordered.push(cell_from_value(value, options, Some(col_idx)));
659                    }
660                    ordered
661                } else {
662                    values
663                        .into_iter()
664                        .enumerate()
665                        .map(|(col_idx, value)| cell_from_value(value, options, Some(col_idx)))
666                        .collect::<Vec<_>>()
667                };
668                while cells.len() < column_count {
669                    cells.push(CellData::empty());
670                }
671                rows.push(RowKind::Data(cells));
672            }
673            RowValue::Object(entries) => {
674                let mut map = Map::new();
675                for (key, value) in entries {
676                    map.insert(key, value);
677                }
678                let mut cells = Vec::with_capacity(column_count);
679                for (col_idx, key) in object_columns.iter().enumerate() {
680                    let value = map.get(key).cloned().unwrap_or(Value::Null);
681                    cells.push(cell_from_value(value, options, Some(col_idx)));
682                }
683                while cells.len() < column_count {
684                    cells.push(CellData::empty());
685                }
686                rows.push(RowKind::Data(cells));
687            }
688        }
689    }
690
691    Ok(NormalizedTable {
692        headers,
693        rows,
694        default_index,
695        default_index_header,
696    })
697}
698
699fn cell_from_value(value: Value, options: &TabulateOptions, column: Option<usize>) -> CellData {
700    let text = match value {
701        Value::Null => String::new(),
702        Value::Bool(flag) => flag.to_string(),
703        Value::Number(number) => number.to_string(),
704        Value::String(s) => normalize_cell_text(&s, options.preserve_whitespace),
705        other => other.to_string(),
706    };
707    let parsed = parse_value(&text, options.is_numparse_disabled(column));
708    CellData::new(text, parsed)
709}
710
711fn value_to_header(value: &Value, preserve_whitespace: bool) -> String {
712    match value {
713        Value::String(s) => normalize_cell_text(s, preserve_whitespace),
714        Value::Null => String::new(),
715        Value::Bool(flag) => flag.to_string(),
716        Value::Number(number) => number.to_string(),
717        other => other.to_string(),
718    }
719}
720
721fn normalize_cell_text(text: &str, preserve_whitespace: bool) -> String {
722    if preserve_whitespace {
723        text.to_string()
724    } else {
725        text.trim().to_string()
726    }
727}
728
729fn is_separator_value(value: &Value) -> bool {
730    match value {
731        Value::String(text) => is_separator_str(text),
732        Value::Array(items) => items.iter().take(2).any(|item| match item {
733            Value::String(text) => is_separator_str(text),
734            _ => false,
735        }),
736        _ => false,
737    }
738}
739
740fn is_separator_str(value: &str) -> bool {
741    value.trim() == SEPARATING_LINE
742}
743
744fn apply_show_index(
745    rows: &mut [RowKind],
746    headers: &mut Vec<String>,
747    show_index: &ShowIndex,
748    disable_numparse: bool,
749    default_index: Option<Vec<String>>,
750    default_index_header: Option<String>,
751) -> Result<(), TabulateError> {
752    let data_row_count = rows
753        .iter()
754        .filter(|row| matches!(row, RowKind::Data(_)))
755        .count();
756
757    match show_index {
758        ShowIndex::Default => {
759            if let Some(values) = default_index {
760                if values.len() != data_row_count {
761                    return Err(TabulateError::IndexLengthMismatch {
762                        expected: data_row_count,
763                        found: values.len(),
764                    });
765                }
766                prepend_index_column(
767                    rows,
768                    headers,
769                    values,
770                    disable_numparse,
771                    default_index_header,
772                );
773            }
774            Ok(())
775        }
776        ShowIndex::Never => Ok(()),
777        ShowIndex::Always => {
778            let values = match default_index {
779                Some(values) => values,
780                None => (0..data_row_count).map(|idx| idx.to_string()).collect(),
781            };
782            if values.len() != data_row_count {
783                return Err(TabulateError::IndexLengthMismatch {
784                    expected: data_row_count,
785                    found: values.len(),
786                });
787            }
788            prepend_index_column(
789                rows,
790                headers,
791                values,
792                disable_numparse,
793                default_index_header,
794            );
795            Ok(())
796        }
797        ShowIndex::Values(values) => {
798            if values.len() != data_row_count {
799                return Err(TabulateError::IndexLengthMismatch {
800                    expected: data_row_count,
801                    found: values.len(),
802                });
803            }
804            prepend_index_column(rows, headers, values.clone(), disable_numparse, None);
805            Ok(())
806        }
807    }
808}
809
810fn prepend_index_column(
811    rows: &mut [RowKind],
812    headers: &mut Vec<String>,
813    values: Vec<String>,
814    disable_numparse: bool,
815    header_label: Option<String>,
816) {
817    let mut iter = values.into_iter();
818    let mut inserted = false;
819    for row in rows.iter_mut() {
820        if let RowKind::Data(cells) = row
821            && let Some(value) = iter.next()
822        {
823            let parsed = parse_value(&value, disable_numparse);
824            cells.insert(0, CellData::new(value, parsed));
825            inserted = true;
826        }
827    }
828    if inserted {
829        headers.insert(0, header_label.unwrap_or_default());
830    }
831}
832
833fn apply_wrapping(rows: &mut [RowKind], headers: &mut [String], options: &TabulateOptions) {
834    let column_count = headers.len();
835    if column_count == 0 {
836        return;
837    }
838
839    let col_widths = expand_widths(&options.max_col_widths, column_count);
840    let header_widths = expand_widths(&options.max_header_col_widths, column_count);
841
842    for (idx, width_opt) in header_widths.iter().copied().enumerate() {
843        if let Some(width) = width_opt
844            && width > 0
845            && max_line_width(&headers[idx], options.enable_widechars) > width
846        {
847            let wrapped = wrap_text(
848                &headers[idx],
849                width,
850                options.break_long_words,
851                options.break_on_hyphens,
852            );
853            headers[idx] = wrapped.join("\n");
854        }
855    }
856
857    for row in rows.iter_mut() {
858        if let RowKind::Data(cells) = row {
859            for (idx, cell) in cells.iter_mut().enumerate() {
860                if let Some(width) = col_widths.get(idx).copied().flatten() {
861                    if width == 0 {
862                        continue;
863                    }
864                    if max_line_width(&cell.text, options.enable_widechars) <= width {
865                        continue;
866                    }
867                    if !options.is_numparse_disabled(Some(idx))
868                        && matches!(cell.value, ParsedValue::Int(_) | ParsedValue::Float(_))
869                    {
870                        continue;
871                    }
872                    let wrapped = wrap_text(
873                        &cell.text,
874                        width,
875                        options.break_long_words,
876                        options.break_on_hyphens,
877                    );
878                    if !wrapped.is_empty() {
879                        cell.text = wrapped.join("\n");
880                        cell.value = ParsedValue::String;
881                    }
882                }
883            }
884        }
885    }
886}
887
888fn expand_widths(spec: &Option<Vec<Option<usize>>>, column_count: usize) -> Vec<Option<usize>> {
889    match spec {
890        None => vec![None; column_count],
891        Some(values) => {
892            if values.is_empty() {
893                return vec![None; column_count];
894            }
895            let mut result = Vec::with_capacity(column_count);
896            let mut iter = values.iter().cloned();
897            let mut last = iter.next().unwrap_or(None);
898            result.push(last);
899            for _idx in 1..column_count {
900                if let Some(value) = iter.next() {
901                    last = value;
902                }
903                result.push(last);
904            }
905            result
906        }
907    }
908}
909
910fn wrap_text(
911    text: &str,
912    width: usize,
913    break_long_words: bool,
914    break_on_hyphens: bool,
915) -> Vec<String> {
916    if width == 0 {
917        return vec![text.to_string()];
918    }
919
920    let base = WrapOptions::new(width).break_words(break_long_words);
921    let options = if break_on_hyphens {
922        base.word_splitter(WordSplitter::HyphenSplitter)
923    } else {
924        base.word_splitter(WordSplitter::NoHyphenation)
925    };
926
927    let mut lines = Vec::new();
928    for raw_line in text.split('\n') {
929        if raw_line.is_empty() {
930            lines.push(String::new());
931            continue;
932        }
933        let wrapped = wrap(raw_line, &options);
934        if wrapped.is_empty() {
935            lines.push(raw_line.to_string());
936        } else {
937            lines.extend(wrapped.into_iter().map(|segment| segment.into_owned()));
938        }
939    }
940
941    if lines.is_empty() {
942        vec![text.to_string()]
943    } else {
944        lines
945    }
946}
947
948#[derive(Clone, Copy, Debug, PartialEq, Eq)]
949enum ColumnType {
950    Int,
951    Float,
952    Bool,
953    String,
954}
955
956impl ColumnType {
957    fn more_generic(self, other: ColumnType) -> ColumnType {
958        use ColumnType::*;
959        match (self, other) {
960            (String, _) | (_, String) => String,
961            (Float, _) | (_, Float) => Float,
962            (Int, Int) => Int,
963            (Int, Bool) | (Bool, Int) => Int,
964            (Bool, Bool) => Bool,
965        }
966    }
967}
968
969struct RenderPlan {
970    has_header: bool,
971    header_lines: Vec<String>,
972    data_lines: Vec<Vec<String>>,
973    sequence: Vec<RowMarker>,
974    line_above: Option<String>,
975    line_below_header: Option<String>,
976    line_between_rows: Option<String>,
977    line_below: Option<String>,
978    separator_fallback: Option<String>,
979}
980
981#[derive(Clone, Debug)]
982enum RowMarker {
983    Data(usize),
984    Separator,
985}
986
987impl RenderPlan {
988    fn build(
989        rows: &[RowKind],
990        headers: &[String],
991        format: &TableFormat,
992        options: &TabulateOptions,
993        format_name: Option<&str>,
994    ) -> Self {
995        let mut data_rows = Vec::new();
996        let mut sequence = Vec::new();
997        for row in rows {
998            match row {
999                RowKind::Data(cells) => {
1000                    let index = data_rows.len();
1001                    data_rows.push(cells.clone());
1002                    sequence.push(RowMarker::Data(index));
1003                }
1004                RowKind::Separator => sequence.push(RowMarker::Separator),
1005            }
1006        }
1007
1008        let row_count = data_rows.len();
1009        let column_count = headers.len();
1010        let mut formatted_rows = vec![vec![String::new(); column_count]; row_count];
1011        let mut column_types = vec![ColumnType::Bool; column_count];
1012
1013        for col in 0..column_count {
1014            let mut current_type = ColumnType::Bool;
1015            let mut seen_non_empty = false;
1016            for row in &data_rows {
1017                let cell = &row[col];
1018                if cell.text.is_empty() && matches!(cell.value, ParsedValue::String) {
1019                    continue;
1020                }
1021                seen_non_empty = true;
1022                let value_type = match cell.value {
1023                    ParsedValue::Int(_) => ColumnType::Int,
1024                    ParsedValue::Float(_) => ColumnType::Float,
1025                    ParsedValue::Bool(_) => ColumnType::Bool,
1026                    ParsedValue::String => ColumnType::String,
1027                };
1028                current_type = current_type.more_generic(value_type);
1029                if current_type == ColumnType::String {
1030                    break;
1031                }
1032            }
1033            if !seen_non_empty {
1034                current_type = ColumnType::String;
1035            }
1036            column_types[col] = if options.is_numparse_disabled(Some(col)) {
1037                ColumnType::String
1038            } else {
1039                current_type
1040            };
1041        }
1042
1043        let float_formats = resolve_formats(&options.float_format, column_count, "g");
1044        let int_formats = resolve_formats(&options.int_format, column_count, "");
1045        let missing_values = resolve_missing_values(&options.missing_values, column_count);
1046
1047        for (row_idx, row) in data_rows.iter().enumerate() {
1048            for col_idx in 0..column_count {
1049                formatted_rows[row_idx][col_idx] = format_cell(
1050                    &row[col_idx],
1051                    column_types[col_idx],
1052                    &float_formats[col_idx],
1053                    &int_formats[col_idx],
1054                    &missing_values[col_idx],
1055                );
1056            }
1057        }
1058
1059        let formatted_headers: Vec<String> = headers.to_vec();
1060        let format_name_lower = format_name.map(|name| name.to_ascii_lowercase());
1061        let format_name_key = format_name_lower.as_deref().unwrap_or("");
1062        let supports_multiline = format_name_lower
1063            .as_deref()
1064            .map(|name| MULTILINE_FORMATS.contains(&name))
1065            .unwrap_or(false);
1066
1067        let mut content_widths = vec![0usize; column_count];
1068        let mut decimal_widths: Vec<Option<DecimalLayout>> = vec![None; column_count];
1069
1070        let enable_widechars = options.enable_widechars;
1071
1072        for col_idx in 0..column_count {
1073            for row in &formatted_rows {
1074                content_widths[col_idx] = max(
1075                    content_widths[col_idx],
1076                    cell_width(&row[col_idx], supports_multiline, enable_widechars),
1077                );
1078            }
1079            content_widths[col_idx] = max(
1080                content_widths[col_idx],
1081                cell_width(
1082                    &formatted_headers[col_idx],
1083                    supports_multiline,
1084                    enable_widechars,
1085                ),
1086            );
1087        }
1088
1089        if format_name_key == "moinmoin" {
1090            for (idx, width) in content_widths.iter_mut().enumerate() {
1091                match column_types.get(idx).copied().unwrap_or(ColumnType::String) {
1092                    ColumnType::String => *width += 1,
1093                    _ => *width += 2,
1094                }
1095            }
1096        } else if format_name_key == "colon_grid" {
1097            for (idx, width) in content_widths.iter_mut().enumerate() {
1098                if matches!(column_types.get(idx), Some(ColumnType::String)) {
1099                    *width += 1;
1100                }
1101            }
1102        }
1103
1104        #[cfg(test)]
1105        if format_name_key == "moinmoin" {
1106            dbg!(&content_widths);
1107            dbg!(&formatted_headers);
1108            dbg!(&formatted_rows);
1109        }
1110
1111        let has_header = headers.iter().any(|h| !h.is_empty());
1112        let min_padding = if format_name_key == "pretty" {
1113            0
1114        } else {
1115            MIN_PADDING
1116        };
1117
1118        if has_header {
1119            let format_requires_padding = format_name_key.starts_with("latex");
1120            let apply_min_padding = matches!(format.header_row, RowFormat::Static(_))
1121                || format.padding == 0
1122                || format_requires_padding;
1123            for col_idx in 0..column_count {
1124                let header_width = cell_width(
1125                    &formatted_headers[col_idx],
1126                    supports_multiline,
1127                    enable_widechars,
1128                );
1129                if apply_min_padding {
1130                    content_widths[col_idx] =
1131                        max(content_widths[col_idx], header_width + min_padding);
1132                } else {
1133                    content_widths[col_idx] = max(content_widths[col_idx], header_width);
1134                }
1135            }
1136        }
1137
1138        let column_aligns = initialise_alignments(column_count, &column_types, options);
1139        let header_aligns = initialise_header_alignments(column_count, &column_aligns, options);
1140        for col_idx in 0..column_count {
1141            if column_aligns[col_idx] == Alignment::Decimal {
1142                let mut max_integer_width_value = 0usize;
1143                let mut max_fraction_width_value = 0usize;
1144                for row in &formatted_rows {
1145                    let (integer_width, fraction_width) =
1146                        max_integer_fraction_width(&row[col_idx], '.', enable_widechars);
1147                    max_integer_width_value = max(max_integer_width_value, integer_width);
1148                    max_fraction_width_value = max(max_fraction_width_value, fraction_width);
1149                }
1150                let (header_integer, header_fraction) =
1151                    max_integer_fraction_width(&formatted_headers[col_idx], '.', enable_widechars);
1152                max_integer_width_value = max(max_integer_width_value, header_integer);
1153                max_fraction_width_value = max(max_fraction_width_value, header_fraction);
1154                decimal_widths[col_idx] = Some(DecimalLayout {
1155                    integer: max_integer_width_value,
1156                    fraction: max_fraction_width_value,
1157                });
1158                let decimal_total = if max_fraction_width_value > 0 {
1159                    max_integer_width_value + 1 + max_fraction_width_value
1160                } else {
1161                    max_integer_width_value
1162                };
1163                content_widths[col_idx] = max(content_widths[col_idx], decimal_total);
1164            }
1165        }
1166        let padding = format.padding;
1167        let column_widths: Vec<usize> = content_widths.iter().map(|w| w + padding * 2).collect();
1168
1169        #[cfg(test)]
1170        if format_name_key == "moinmoin" {
1171            println!("moin widths {:?}", column_widths);
1172            println!("headers {:?}", formatted_headers);
1173            println!("rows {:?}", formatted_rows);
1174        }
1175
1176        let mut aligned_headers = Vec::with_capacity(column_count);
1177        for col_idx in 0..column_count {
1178            let alignment = match header_aligns[col_idx] {
1179                Alignment::Decimal => Alignment::Right,
1180                other => other,
1181            };
1182            let header_decimal_layout = match header_aligns[col_idx] {
1183                Alignment::Decimal => None,
1184                _ => decimal_widths[col_idx],
1185            };
1186            let aligned = align_cell(
1187                &formatted_headers[col_idx],
1188                content_widths[col_idx],
1189                alignment,
1190                '.',
1191                header_decimal_layout,
1192                supports_multiline,
1193                true,
1194                enable_widechars,
1195            );
1196            let padded = apply_padding(&aligned, padding, supports_multiline);
1197            aligned_headers.push(padded);
1198        }
1199
1200        #[cfg(test)]
1201        if format_name_key == "moinmoin" {
1202            dbg!(&aligned_headers);
1203        }
1204
1205        let mut aligned_rows = vec![Vec::with_capacity(column_count); row_count];
1206        for col_idx in 0..column_count {
1207            for row_idx in 0..row_count {
1208                let cell_text = &formatted_rows[row_idx][col_idx];
1209                let enforce_left_alignment = supports_multiline
1210                    || column_aligns[col_idx] != Alignment::Left
1211                    || !cell_text.contains('\n');
1212                let aligned = align_cell(
1213                    cell_text,
1214                    content_widths[col_idx],
1215                    column_aligns[col_idx],
1216                    '.',
1217                    decimal_widths[col_idx],
1218                    supports_multiline,
1219                    enforce_left_alignment,
1220                    enable_widechars,
1221                );
1222                aligned_rows[row_idx].push(apply_padding(&aligned, padding, supports_multiline));
1223            }
1224        }
1225
1226        let header_lines = if has_header {
1227            render_multiline_row(
1228                &format.header_row,
1229                &aligned_headers,
1230                &column_widths,
1231                &header_aligns,
1232                RowAlignment::Top,
1233                format_name_key,
1234                supports_multiline,
1235            )
1236        } else {
1237            Vec::new()
1238        };
1239
1240        let row_aligns = initialise_row_alignments(row_count, options);
1241
1242        let mut data_lines = Vec::with_capacity(row_count);
1243        for (row_idx, row) in aligned_rows.iter().enumerate() {
1244            let alignment = row_aligns
1245                .get(row_idx)
1246                .copied()
1247                .unwrap_or(RowAlignment::Top);
1248            data_lines.push(render_multiline_row(
1249                &format.data_row,
1250                row,
1251                &column_widths,
1252                &column_aligns,
1253                alignment,
1254                format_name_key,
1255                supports_multiline,
1256            ));
1257        }
1258
1259        let hide_line_above = has_header && format.hides("lineabove");
1260        let hide_line_below_header = has_header && format.hides("linebelowheader");
1261        let hide_line_between_rows = has_header && format.hides("linebetweenrows");
1262        let hide_line_below = has_header && format.hides("linebelow");
1263
1264        let line_above = render_line(
1265            &format.line_above,
1266            &column_widths,
1267            &column_aligns,
1268            hide_line_above,
1269        );
1270        let line_below_header = render_line(
1271            &format.line_below_header,
1272            &column_widths,
1273            &column_aligns,
1274            hide_line_below_header,
1275        );
1276        let line_between_rows = render_line(
1277            &format.line_between_rows,
1278            &column_widths,
1279            &column_aligns,
1280            hide_line_between_rows,
1281        );
1282        let line_below = render_line(
1283            &format.line_below,
1284            &column_widths,
1285            &column_aligns,
1286            hide_line_below,
1287        );
1288
1289        let separator_fallback = if line_between_rows.is_some() {
1290            None
1291        } else {
1292            line_below_header
1293                .clone()
1294                .or_else(|| line_below.clone())
1295                .or_else(|| line_above.clone())
1296        };
1297
1298        Self {
1299            has_header,
1300            header_lines,
1301            data_lines,
1302            sequence,
1303            line_above,
1304            line_below_header,
1305            line_between_rows,
1306            line_below,
1307            separator_fallback,
1308        }
1309    }
1310
1311    fn render(self) -> String {
1312        let RenderPlan {
1313            has_header,
1314            header_lines,
1315            data_lines,
1316            sequence,
1317            line_above,
1318            line_below_header,
1319            line_between_rows,
1320            line_below,
1321            separator_fallback,
1322        } = self;
1323
1324        let mut output = Vec::new();
1325        if let Some(line) = line_above {
1326            output.push(line);
1327        }
1328
1329        if has_header {
1330            output.extend(header_lines);
1331            if let Some(line) = line_below_header {
1332                output.push(line);
1333            }
1334        }
1335
1336        if !sequence.is_empty() {
1337            for (idx, marker) in sequence.iter().enumerate() {
1338                match marker {
1339                    RowMarker::Data(index) => {
1340                        if let Some(lines) = data_lines.get(*index) {
1341                            for line in lines {
1342                                output.push(line.clone());
1343                            }
1344                        }
1345                        if let Some(next_marker) = sequence.get(idx + 1)
1346                            && matches!(next_marker, RowMarker::Data(_))
1347                            && let Some(line) = &line_between_rows
1348                        {
1349                            output.push(line.clone());
1350                        }
1351                    }
1352                    RowMarker::Separator => {
1353                        if let Some(line) = &line_between_rows {
1354                            output.push(line.clone());
1355                        } else if let Some(line) = &separator_fallback {
1356                            if !line.is_empty() {
1357                                output.push(line.clone());
1358                            } else {
1359                                output.push(String::new());
1360                            }
1361                        } else {
1362                            output.push(String::new());
1363                        }
1364                    }
1365                }
1366            }
1367        }
1368
1369        if let Some(line) = line_below {
1370            output.push(line);
1371        }
1372
1373        output.join("\n")
1374    }
1375}
1376
1377fn resolve_formats(spec: &FormatSpec, column_count: usize, default: &str) -> Vec<String> {
1378    match spec {
1379        FormatSpec::Default => vec![default.to_string(); column_count],
1380        FormatSpec::Fixed(fmt) => vec![fmt.clone(); column_count],
1381        FormatSpec::PerColumn(list) => {
1382            let mut result = Vec::with_capacity(column_count);
1383            for idx in 0..column_count {
1384                result.push(
1385                    list.get(idx)
1386                        .cloned()
1387                        .unwrap_or_else(|| default.to_string()),
1388                );
1389            }
1390            result
1391        }
1392    }
1393}
1394
1395fn resolve_missing_values(spec: &MissingValues, column_count: usize) -> Vec<String> {
1396    match spec {
1397        MissingValues::Single(value) => vec![value.clone(); column_count],
1398        MissingValues::PerColumn(values) => {
1399            if values.is_empty() {
1400                return vec![String::new(); column_count];
1401            }
1402            let mut result = Vec::with_capacity(column_count);
1403            for idx in 0..column_count {
1404                result.push(
1405                    values
1406                        .get(idx)
1407                        .cloned()
1408                        .unwrap_or_else(|| values.last().cloned().unwrap()),
1409                );
1410            }
1411            result
1412        }
1413    }
1414}
1415
1416fn format_cell(
1417    cell: &CellData,
1418    column_type: ColumnType,
1419    float_format: &str,
1420    int_format: &str,
1421    missing_value: &str,
1422) -> String {
1423    if cell.text.is_empty() {
1424        return missing_value.to_string();
1425    }
1426
1427    match column_type {
1428        ColumnType::Int => match cell.value {
1429            ParsedValue::Int(value) => format_int_value(value, int_format),
1430            _ => cell.text.clone(),
1431        },
1432        ColumnType::Float => match cell.value {
1433            ParsedValue::Float(value) => format_float_value(value, float_format),
1434            ParsedValue::Int(value) => format_float_value(value as f64, float_format),
1435            _ => cell.text.clone(),
1436        },
1437        ColumnType::Bool => match cell.value {
1438            ParsedValue::Bool(flag) => format_bool_value(flag),
1439            _ => cell.text.clone(),
1440        },
1441        ColumnType::String => cell.text.clone(),
1442    }
1443}
1444
1445fn format_bool_value(flag: bool) -> String {
1446    if flag {
1447        "True".to_string()
1448    } else {
1449        "False".to_string()
1450    }
1451}
1452
1453fn format_int_value(value: i128, spec: &str) -> String {
1454    let mut body = spec.trim().to_string();
1455    if body.is_empty() {
1456        return value.to_string();
1457    }
1458
1459    let thousands = if body.contains(',') {
1460        body = body.replace(',', "");
1461        true
1462    } else {
1463        false
1464    };
1465
1466    let mut base = 10;
1467    let mut uppercase = false;
1468    if let Some(ch) = body.chars().last() {
1469        match ch {
1470            'x' => {
1471                base = 16;
1472                body.pop();
1473            }
1474            'X' => {
1475                base = 16;
1476                uppercase = true;
1477                body.pop();
1478            }
1479            'b' | 'B' => {
1480                base = 2;
1481                uppercase = ch.is_uppercase();
1482                body.pop();
1483            }
1484            'o' | 'O' => {
1485                base = 8;
1486                uppercase = ch.is_uppercase();
1487                body.pop();
1488            }
1489            _ => {}
1490        }
1491    }
1492
1493    let mut formatted = match base {
1494        16 => {
1495            if uppercase {
1496                format!("{:X}", value)
1497            } else {
1498                format!("{:x}", value)
1499            }
1500        }
1501        8 => {
1502            let text = format!("{:o}", value);
1503            if uppercase { text.to_uppercase() } else { text }
1504        }
1505        2 => {
1506            let sign = if value < 0 { "-" } else { "" };
1507            let digits = format!("{:b}", value.abs());
1508            format!("{sign}{digits}")
1509        }
1510        _ => value.to_string(),
1511    };
1512
1513    if thousands && base == 10 {
1514        formatted = apply_thousands_to_integer_string(&formatted);
1515    }
1516
1517    formatted
1518}
1519
1520fn format_float_value(value: f64, spec: &str) -> String {
1521    let fmt = parse_float_format(spec);
1522
1523    if value.is_nan() {
1524        return if fmt.uppercase {
1525            "NAN".to_string()
1526        } else {
1527            "nan".to_string()
1528        };
1529    }
1530    if value.is_infinite() {
1531        let inf = if value.is_sign_negative() {
1532            "-inf"
1533        } else {
1534            "inf"
1535        };
1536        return if fmt.uppercase {
1537            inf.to_uppercase()
1538        } else {
1539            inf.to_string()
1540        };
1541    }
1542
1543    let mut rendered = match fmt.style {
1544        FloatStyle::Fixed => {
1545            let precision = fmt.precision.unwrap_or(6);
1546            if fmt.percent {
1547                let mut text = format!("{:.*}", precision, value * 100.0);
1548                text = trim_trailing_zeros(text);
1549                if text.is_empty() {
1550                    text = "0".to_string();
1551                }
1552                text.push('%');
1553                if fmt.thousands {
1554                    apply_thousands_to_number(&text)
1555                } else {
1556                    text
1557                }
1558            } else {
1559                format!("{:.*}", precision, value)
1560            }
1561        }
1562        FloatStyle::Exponent => {
1563            let precision = fmt.precision.unwrap_or(6);
1564            let formatted = if fmt.uppercase {
1565                format!("{:.*E}", precision, value)
1566            } else {
1567                format!("{:.*e}", precision, value)
1568            };
1569            if let Some(pos) = formatted.find('e').or_else(|| formatted.find('E')) {
1570                let exp_char = formatted.as_bytes()[pos] as char;
1571                let mantissa = &formatted[..pos];
1572                let exponent = normalize_exponent(&formatted[pos + 1..]);
1573                format!("{mantissa}{exp_char}{exponent}")
1574            } else {
1575                formatted
1576            }
1577        }
1578        FloatStyle::General => format_float_general(value, fmt.precision, fmt.uppercase),
1579    };
1580
1581    if (fmt.style != FloatStyle::Fixed || !fmt.percent) && fmt.thousands {
1582        rendered = apply_thousands_to_number(&rendered);
1583    }
1584
1585    rendered
1586}
1587
1588#[derive(Clone, Copy, PartialEq, Eq)]
1589enum FloatStyle {
1590    General,
1591    Fixed,
1592    Exponent,
1593}
1594
1595#[derive(Clone, Copy)]
1596struct ParsedFloatFormat {
1597    style: FloatStyle,
1598    precision: Option<usize>,
1599    thousands: bool,
1600    uppercase: bool,
1601    percent: bool,
1602}
1603
1604impl Default for ParsedFloatFormat {
1605    fn default() -> Self {
1606        Self {
1607            style: FloatStyle::General,
1608            precision: None,
1609            thousands: false,
1610            uppercase: false,
1611            percent: false,
1612        }
1613    }
1614}
1615
1616fn parse_float_format(spec: &str) -> ParsedFloatFormat {
1617    let mut fmt = ParsedFloatFormat::default();
1618    if spec.is_empty() {
1619        return fmt;
1620    }
1621
1622    let mut body = spec.trim().to_string();
1623    if body.contains(',') {
1624        body = body.replace(',', "");
1625        fmt.thousands = true;
1626    }
1627
1628    if let Some(ch) = body.chars().last() {
1629        match ch {
1630            'f' | 'F' => {
1631                fmt.style = FloatStyle::Fixed;
1632                fmt.uppercase = ch.is_uppercase();
1633                body.pop();
1634            }
1635            'e' | 'E' => {
1636                fmt.style = FloatStyle::Exponent;
1637                fmt.uppercase = ch.is_uppercase();
1638                body.pop();
1639            }
1640            'g' | 'G' => {
1641                fmt.style = FloatStyle::General;
1642                fmt.uppercase = ch.is_uppercase();
1643                body.pop();
1644            }
1645            '%' => {
1646                fmt.style = FloatStyle::Fixed;
1647                fmt.percent = true;
1648                body.pop();
1649            }
1650            _ => {}
1651        }
1652    }
1653
1654    if let Some(dot) = body.find('.') {
1655        let precision_part = &body[dot + 1..];
1656        if !precision_part.is_empty() {
1657            fmt.precision = precision_part.parse::<usize>().ok();
1658        }
1659    }
1660
1661    fmt
1662}
1663
1664fn format_float_general(value: f64, precision: Option<usize>, uppercase: bool) -> String {
1665    let precision = precision.unwrap_or(6).max(1);
1666    if value == 0.0 {
1667        return "0".to_string();
1668    }
1669
1670    let abs = value.abs();
1671    let exponent = if abs == 0.0 {
1672        0
1673    } else {
1674        abs.log10().floor() as i32
1675    };
1676    let use_exponent = exponent < -4 || exponent >= precision as i32;
1677
1678    if use_exponent {
1679        let formatted = if uppercase {
1680            format!("{:.*E}", precision - 1, value)
1681        } else {
1682            format!("{:.*e}", precision - 1, value)
1683        };
1684        if let Some(pos) = formatted.find('e').or_else(|| formatted.find('E')) {
1685            let exp_char = formatted.as_bytes()[pos] as char;
1686            let mantissa = trim_trailing_zeros(formatted[..pos].to_string());
1687            let exponent = normalize_exponent(&formatted[pos + 1..]);
1688            format!("{mantissa}{exp_char}{exponent}")
1689        } else {
1690            formatted
1691        }
1692    } else {
1693        let decimals = (precision as i32 - exponent - 1).max(0) as usize;
1694        let text = format!("{:.*}", decimals, value);
1695        trim_trailing_zeros(text)
1696    }
1697}
1698
1699fn trim_trailing_zeros(mut value: String) -> String {
1700    if let Some(dot) = value.find('.') {
1701        let mut trim_len = value.len();
1702        while trim_len > dot && value.as_bytes()[trim_len - 1] == b'0' {
1703            trim_len -= 1;
1704        }
1705        if trim_len > dot && value.as_bytes()[trim_len - 1] == b'.' {
1706            trim_len -= 1;
1707        }
1708        value.truncate(trim_len);
1709    }
1710    value
1711}
1712
1713fn apply_thousands_to_integer_string(value: &str) -> String {
1714    let (sign, digits) = if let Some(rest) = value.strip_prefix('-') {
1715        ("-", rest)
1716    } else if let Some(rest) = value.strip_prefix('+') {
1717        ("+", rest)
1718    } else {
1719        ("", value)
1720    };
1721    let formatted = apply_thousands_to_integer(digits);
1722    format!("{sign}{formatted}")
1723}
1724
1725fn apply_thousands_to_integer(digits: &str) -> String {
1726    if digits.len() <= 3 {
1727        return digits.to_string();
1728    }
1729
1730    let mut result = String::new();
1731    let chars: Vec<char> = digits.chars().collect();
1732    let mut index = chars.len() % 3;
1733    if index == 0 {
1734        index = 3;
1735    }
1736    result.extend(&chars[..index]);
1737    while index < chars.len() {
1738        result.push(',');
1739        result.extend(&chars[index..index + 3]);
1740        index += 3;
1741    }
1742    result
1743}
1744
1745fn apply_thousands_to_number(text: &str) -> String {
1746    if text.contains('e') || text.contains('E') {
1747        return text.to_string();
1748    }
1749    let (body, suffix) = if let Some(stripped) = text.strip_suffix('%') {
1750        (stripped, "%")
1751    } else {
1752        (text, "")
1753    };
1754    let (sign, rest) = if let Some(stripped) = body.strip_prefix('-') {
1755        ("-", stripped)
1756    } else if let Some(stripped) = body.strip_prefix('+') {
1757        ("+", stripped)
1758    } else {
1759        ("", body)
1760    };
1761    let mut parts = rest.splitn(2, '.');
1762    let int_part = parts.next().unwrap_or("");
1763    let frac_part = parts.next();
1764    let formatted_int = apply_thousands_to_integer(int_part);
1765    let mut result = String::new();
1766    result.push_str(sign);
1767    result.push_str(&formatted_int);
1768    if let Some(frac) = frac_part
1769        && !frac.is_empty()
1770    {
1771        result.push('.');
1772        result.push_str(frac);
1773    }
1774    result.push_str(suffix);
1775    result
1776}
1777
1778fn normalize_numeric_candidate(input: &str) -> String {
1779    input
1780        .chars()
1781        .filter(|ch| *ch != ',' && *ch != '_' && *ch != ' ')
1782        .collect()
1783}
1784
1785fn normalize_exponent(exponent: &str) -> String {
1786    let (sign, digits) = if exponent.starts_with(['+', '-']) {
1787        exponent.split_at(1)
1788    } else {
1789        ("+", exponent)
1790    };
1791    let trimmed = digits.trim_start_matches('0');
1792    let core = if trimmed.is_empty() { "0" } else { trimmed };
1793    let padded = if core.len() < 2 {
1794        format!("{:0>2}", core)
1795    } else {
1796        core.to_string()
1797    };
1798    format!("{sign}{padded}")
1799}
1800
1801fn initialise_alignments(
1802    column_count: usize,
1803    column_types: &[ColumnType],
1804    options: &TabulateOptions,
1805) -> Vec<Alignment> {
1806    let mut aligns = vec![Alignment::Left; column_count];
1807    let num_align = options.num_align.unwrap_or(Alignment::Decimal);
1808    let str_align = options.str_align.unwrap_or(Alignment::Left);
1809
1810    for (idx, col_type) in column_types.iter().enumerate() {
1811        aligns[idx] = match col_type {
1812            ColumnType::Int | ColumnType::Float => num_align,
1813            _ => str_align,
1814        };
1815    }
1816
1817    if let Some(global) = options.col_global_align {
1818        aligns.iter_mut().for_each(|align| *align = global);
1819    }
1820
1821    for (idx, custom) in options.col_align.iter().enumerate() {
1822        if let Some(alignment) = custom
1823            && idx < aligns.len()
1824        {
1825            aligns[idx] = *alignment;
1826        }
1827    }
1828
1829    aligns
1830}
1831
1832fn initialise_header_alignments(
1833    column_count: usize,
1834    column_aligns: &[Alignment],
1835    options: &TabulateOptions,
1836) -> Vec<Alignment> {
1837    let mut aligns = if let Some(global) = options.headers_global_align {
1838        vec![global; column_count]
1839    } else {
1840        column_aligns.to_vec()
1841    };
1842
1843    for (idx, custom) in options.headers_align.iter().enumerate() {
1844        if idx >= aligns.len() {
1845            break;
1846        }
1847        if let Some(spec) = custom {
1848            match spec {
1849                HeaderAlignment::Align(alignment) => {
1850                    aligns[idx] = *alignment;
1851                }
1852                HeaderAlignment::SameAsColumn => {
1853                    if let Some(column_alignment) = column_aligns.get(idx) {
1854                        aligns[idx] = *column_alignment;
1855                    }
1856                }
1857            }
1858        }
1859    }
1860
1861    aligns
1862}
1863
1864fn initialise_row_alignments(row_count: usize, options: &TabulateOptions) -> Vec<RowAlignment> {
1865    let default = options.row_global_align.unwrap_or(RowAlignment::Top);
1866    let mut aligns = vec![default; row_count];
1867    for (idx, custom) in options.row_align.iter().enumerate() {
1868        if idx >= aligns.len() {
1869            break;
1870        }
1871        if let Some(alignment) = custom {
1872            aligns[idx] = *alignment;
1873        }
1874    }
1875    aligns
1876}
1877
1878fn apply_padding(text: &str, padding: usize, supports_multiline: bool) -> String {
1879    if padding == 0 {
1880        return text.to_string();
1881    }
1882    let pad = " ".repeat(padding);
1883    if supports_multiline {
1884        split_cell_lines(text)
1885            .into_iter()
1886            .map(|line| format!("{pad}{line}{pad}"))
1887            .collect::<Vec<_>>()
1888            .join("\n")
1889    } else {
1890        format!("{pad}{text}{pad}")
1891    }
1892}
1893
1894fn split_cell_lines(text: &str) -> Vec<&str> {
1895    text.split('\n').collect()
1896}
1897
1898fn cell_width(text: &str, supports_multiline: bool, enable_widechars: bool) -> usize {
1899    if supports_multiline {
1900        max_line_width(text, enable_widechars)
1901    } else {
1902        full_text_width(text, enable_widechars)
1903    }
1904}
1905
1906fn max_line_width(text: &str, enable_widechars: bool) -> usize {
1907    split_cell_lines(text)
1908        .into_iter()
1909        .map(|line| visible_width(line, enable_widechars))
1910        .max()
1911        .unwrap_or(0)
1912}
1913
1914fn full_text_width(text: &str, enable_widechars: bool) -> usize {
1915    let lines = split_cell_lines(text);
1916    if lines.is_empty() {
1917        return 0;
1918    }
1919    let mut total = 0usize;
1920    let last_index = lines.len().saturating_sub(1);
1921    for (idx, line) in lines.iter().enumerate() {
1922        total += visible_width(line, enable_widechars);
1923        if idx < last_index {
1924            total += 1; // account for the newline separating the lines
1925        }
1926    }
1927    total
1928}
1929
1930fn max_integer_fraction_width(
1931    text: &str,
1932    decimal_marker: char,
1933    enable_widechars: bool,
1934) -> (usize, usize) {
1935    split_cell_lines(text)
1936        .into_iter()
1937        .map(|line| integer_fraction_width_line(line, decimal_marker, enable_widechars))
1938        .fold((0, 0), |(max_int, max_frac), (int_w, frac_w)| {
1939            (max(max_int, int_w), max(max_frac, frac_w))
1940        })
1941}
1942
1943fn integer_fraction_width_line(
1944    value: &str,
1945    decimal_marker: char,
1946    enable_widechars: bool,
1947) -> (usize, usize) {
1948    match value.find(decimal_marker) {
1949        Some(pos) => {
1950            let integer_width = visible_width(&value[..pos], enable_widechars);
1951            let fraction_width =
1952                visible_width(&value[pos + decimal_marker.len_utf8()..], enable_widechars);
1953            (integer_width, fraction_width)
1954        }
1955        None => (visible_width(value, enable_widechars), 0),
1956    }
1957}
1958
1959fn render_line(
1960    spec: &LineFormat,
1961    col_widths: &[usize],
1962    col_aligns: &[Alignment],
1963    suppressed: bool,
1964) -> Option<String> {
1965    if suppressed {
1966        return None;
1967    }
1968    match spec {
1969        LineFormat::None => None,
1970        LineFormat::Static(line) => {
1971            let mut out = String::new();
1972            out.push_str(line.begin.as_ref());
1973            for (idx, width) in col_widths.iter().enumerate() {
1974                if idx > 0 {
1975                    out.push_str(line.separator.as_ref());
1976                }
1977                out.push_str(&line.fill.as_ref().repeat(*width));
1978            }
1979            out.push_str(line.end.as_ref());
1980            if out.is_empty() { None } else { Some(out) }
1981        }
1982        LineFormat::Text(text) => {
1983            let owned = text.clone().into_owned();
1984            if owned.is_empty() { None } else { Some(owned) }
1985        }
1986        LineFormat::Dynamic(func) => {
1987            let rendered = func(col_widths, col_aligns);
1988            if rendered.is_empty() {
1989                None
1990            } else {
1991                Some(rendered)
1992            }
1993        }
1994    }
1995}
1996
1997fn render_multiline_row(
1998    spec: &RowFormat,
1999    cells: &[String],
2000    col_widths: &[usize],
2001    col_aligns: &[Alignment],
2002    row_alignment: RowAlignment,
2003    format_name: &str,
2004    supports_multiline: bool,
2005) -> Vec<String> {
2006    match spec {
2007        RowFormat::None => Vec::new(),
2008        RowFormat::Dynamic(func) => vec![func(cells, col_widths, col_aligns)],
2009        RowFormat::Static(_) => {
2010            let split_cells: Vec<Vec<String>> = cells
2011                .iter()
2012                .map(|cell| {
2013                    split_cell_lines(cell)
2014                        .into_iter()
2015                        .map(|line| line.to_string())
2016                        .collect()
2017                })
2018                .collect();
2019            let line_count = split_cells
2020                .iter()
2021                .map(|lines| lines.len())
2022                .max()
2023                .unwrap_or(0);
2024            if line_count <= 1 || !supports_multiline {
2025                return vec![render_row(spec, cells, col_widths, col_aligns)];
2026            }
2027            let mut per_column_lines: Vec<Vec<String>> = Vec::with_capacity(cells.len());
2028            for (col_idx, lines) in split_cells.iter().enumerate() {
2029                let width = *col_widths.get(col_idx).unwrap_or(&0);
2030                per_column_lines.push(pad_cell_lines(
2031                    lines,
2032                    width,
2033                    line_count,
2034                    row_alignment,
2035                    format_name,
2036                ));
2037            }
2038            let mut output = Vec::with_capacity(line_count);
2039            for line_idx in 0..line_count {
2040                let line_cells = per_column_lines
2041                    .iter()
2042                    .map(|column_lines| column_lines[line_idx].clone())
2043                    .collect::<Vec<_>>();
2044                output.push(render_row(spec, &line_cells, col_widths, col_aligns));
2045            }
2046            output
2047        }
2048    }
2049}
2050
2051fn pad_cell_lines(
2052    lines: &[String],
2053    width: usize,
2054    total_lines: usize,
2055    alignment: RowAlignment,
2056    format_name: &str,
2057) -> Vec<String> {
2058    let mut result = Vec::with_capacity(total_lines);
2059    let blank = " ".repeat(width);
2060    let line_count = lines.len();
2061    if line_count >= total_lines {
2062        result.extend_from_slice(&lines[..total_lines]);
2063        return result;
2064    }
2065    let padding = total_lines - line_count;
2066    match alignment {
2067        RowAlignment::Top => {
2068            result.extend_from_slice(lines);
2069            result.extend((0..padding).map(|_| blank.clone()));
2070        }
2071        RowAlignment::Bottom => {
2072            let treat_as_top = lines.len() <= 1 && format_name.starts_with("pipe");
2073            if treat_as_top {
2074                result.extend_from_slice(lines);
2075                result.extend((0..padding).map(|_| blank.clone()));
2076            } else {
2077                result.extend((0..padding).map(|_| blank.clone()));
2078                result.extend_from_slice(lines);
2079            }
2080        }
2081        RowAlignment::Center => {
2082            let top = padding / 2;
2083            let bottom = padding - top;
2084            result.extend((0..top).map(|_| blank.clone()));
2085            result.extend_from_slice(lines);
2086            result.extend((0..bottom).map(|_| blank.clone()));
2087        }
2088    }
2089    result
2090}
2091
2092fn render_row(
2093    spec: &RowFormat,
2094    cells: &[String],
2095    col_widths: &[usize],
2096    col_aligns: &[Alignment],
2097) -> String {
2098    match spec {
2099        RowFormat::None => String::new(),
2100        RowFormat::Static(row) => {
2101            let mut out = String::new();
2102            out.push_str(row.begin.as_ref());
2103            for (idx, cell) in cells.iter().enumerate() {
2104                if idx > 0 {
2105                    out.push_str(row.separator.as_ref());
2106                }
2107                out.push_str(cell);
2108            }
2109            out.push_str(row.end.as_ref());
2110            out.trim_end().to_string()
2111        }
2112        RowFormat::Dynamic(func) => func(cells, col_widths, col_aligns),
2113    }
2114}