diff options
Diffstat (limited to 'src/elements/table.rs')
-rw-r--r-- | src/elements/table.rs | 96 |
1 files changed, 96 insertions, 0 deletions
diff --git a/src/elements/table.rs b/src/elements/table.rs new file mode 100644 index 0000000..5b354c1 --- /dev/null +++ b/src/elements/table.rs @@ -0,0 +1,96 @@ +use crate::*; + +pub struct Table { + /// The column definitions for this table. + pub column_definitions: Vec<ColumnDefinition>, + /// The content contained in the rows of the table. An individual [Line] is + /// the contents of a single table cell, a group of cells forms a table row, + /// a group of rows forms a vertical section of the table, with a separator + /// intending to be drawn between each section, and a group of sections forms + /// the table itself. + /// Each row in the table is guaranteed to have the same number of columns + /// as the table header. + pub sections: Vec<Vec<Vec<Line>>>, +} + +impl Table { + pub fn try_from_strs(lines: &[&str]) -> Option<Self> { + let mut lines = lines.into_iter(); + let column_definitions: Vec<ColumnDefinition> = { + let names = split_trimmed_columns(lines.next()?)? + .into_iter().map(|l| Line::from_str(l)); + let alignments = parse_alignments(lines.next()?)?; + if names.len() != alignments.len() { return None } + std::iter::zip(names, alignments).map( + |(name, alignment)| ColumnDefinition { name, alignment } ).collect() + }; + + let mut sections = Vec::new(); + let mut current_section = Vec::new(); + + for line in lines { + if let Some(alignments) = parse_alignments(line) { + if alignments.len() != column_definitions.len() { return None } + sections.push(std::mem::take(&mut current_section)) + } else { + let row: Vec<Line> = split_trimmed_columns(line)? + .into_iter().map(|c| Line::from_str(c)).collect(); + if row.len() != column_definitions.len() { return None } + current_section.push(row); + } + } + + if !current_section.is_empty() { + sections.push(std::mem::take(&mut current_section)); } + Some( Self { column_definitions, sections }) + } +} + +pub struct ColumnDefinition { + /// The name of this column, shown in the header row of the table. + pub name: Line, + /// The alignment of the content in this column. + pub alignment: ColumnAlignment, +} + +pub enum ColumnAlignment { + Left, + Center, + Right, +} + +impl ColumnAlignment { + pub fn from_str(cell: &str) -> Option<Self> { + if !cell.chars().all(|c| c == ':' || c == '-') { + return None } + match (cell.starts_with(':'), cell.ends_with(':')) { + (false, false) => Some(ColumnAlignment::Left), + (false, true) => Some(ColumnAlignment::Right), + (true, false) => Some(ColumnAlignment::Left), + (true, true) => Some(ColumnAlignment::Center), + } + } +} + + +fn split_trimmed_columns(line: &str) -> Option<Vec<&str>> { + Some(split_columns(line)?.into_iter().map(|s| s.trim()).collect()) +} + +fn split_columns(line: &str) -> Option<Vec<&str>> { + if let Some(("", tail)) = line.split_once('|') { + if let Some((head, "")) = tail.rsplit_once('|') { + return Some(head.split('|').collect()); + } + } + return None; +} + +fn parse_alignments(line: &str) -> Option<Vec<ColumnAlignment>> { + let mut alignments = Vec::new(); + for cell in split_columns(line)? { + alignments.push(ColumnAlignment::from_str(cell)?); + } + Some(alignments) +} + |