summaryrefslogtreecommitdiff
path: root/src/elements/table.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/elements/table.rs')
-rw-r--r--src/elements/table.rs96
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)
+}
+