From a6f88a8b82ef3f0431e4b91135cfa394ab90423e Mon Sep 17 00:00:00 2001 From: Seo Sanghyeon Date: Thu, 28 Jan 2021 05:52:35 +0900 Subject: [PATCH] Prettier formatting when horizontal span is present --- examples/formatting.rs | 14 +------ src/format.rs | 88 +++++++++++++++++++++++++++++++----------- src/lib.rs | 72 ++++++++++++++++++++++++++++------ src/row.rs | 19 +++++++++ 4 files changed, 146 insertions(+), 47 deletions(-) diff --git a/examples/formatting.rs b/examples/formatting.rs index e6e9aa0afc..fa148ad6e7 100644 --- a/examples/formatting.rs +++ b/examples/formatting.rs @@ -54,7 +54,7 @@ fn main() { .borders('|') .separators(&[format::LinePosition::Top, format::LinePosition::Bottom], - format::LineSeparator::new('-', '+', '+', '+')) + format::LineSeparator::new('-', '+')) .padding(1, 1) .build()); table.printstd(); @@ -69,17 +69,7 @@ fn main() { // │ Value three │ Value four │ // └─────────────┴────────────┘ println!("With unicode:"); - table.set_format(format::FormatBuilder::new() - .column_separator('│') - .borders('│') - .separators(&[format::LinePosition::Top], - format::LineSeparator::new('─', '┬', '┌', '┐')) - .separators(&[format::LinePosition::Intern], - format::LineSeparator::new('─', '┼', '├', '┤')) - .separators(&[format::LinePosition::Bottom], - format::LineSeparator::new('─', '┴', '└', '┘')) - .padding(1, 1) - .build()); + table.set_format(*format::consts::FORMAT_BOX_CHARS); table.printstd(); // Customized format with unicode and different padding diff --git a/src/format.rs b/src/format.rs index 10bee2697d..8f6f88c6fb 100644 --- a/src/format.rs +++ b/src/format.rs @@ -50,20 +50,40 @@ pub struct LineSeparator { /// Internal junction separator junc: char, /// Left junction separator - ljunc: char, + ljunc: Option, /// Right junction separator - rjunc: char, + rjunc: Option, + /// Top junction separator + tjunc: Option, + /// Bottom junction separator + bjunc: Option, } impl LineSeparator { /// Create a new line separator instance where `line` is the character used to separate 2 lines /// and `junc` is the one used for junctions between columns and lines - pub fn new(line: char, junc: char, ljunc: char, rjunc: char) -> LineSeparator { + pub fn new(line: char, junc: char) -> LineSeparator { LineSeparator { line, junc, - ljunc, - rjunc, + ljunc: None, + rjunc: None, + tjunc: None, + bjunc: None, + } + } + + /// Create a new line separator instance where `line` is the character used to separate 2 lines + /// and `junc` is the one used for junctions between columns and lines and `ljunc`, `rjunc`, + /// `tjunc`, `bjunc` for left, right, top, bottom junctions respectively + pub fn new_box(line: char, junc: char, ljunc: char, rjunc: char, tjunc: char, bjunc: char) -> LineSeparator { + LineSeparator { + line, + junc, + ljunc: Some(ljunc), + rjunc: Some(rjunc), + tjunc: Some(tjunc), + bjunc: Some(bjunc), } } @@ -72,25 +92,37 @@ impl LineSeparator { fn print(&self, out: &mut T, col_width: &[usize], + current_spanning: &[bool], + next_spanning: &[bool], padding: (usize, usize), colsep: bool, lborder: bool, rborder: bool) -> Result { if lborder { - out.write_all(Utf8Char::from(self.ljunc).as_bytes())?; + let ljunc = self.ljunc.unwrap_or(self.junc); + out.write_all(Utf8Char::from(ljunc).as_bytes())?; } + let mut i = 0; let mut iter = col_width.iter().peekable(); while let Some(width) = iter.next() { for _ in 0..width + padding.0 + padding.1 { out.write_all(Utf8Char::from(self.line).as_bytes())?; } if colsep && iter.peek().is_some() { - out.write_all(Utf8Char::from(self.junc).as_bytes())?; + let junc = match (current_spanning[i], next_spanning[i]) { + (false, false) => self.junc, + (false, true) => self.bjunc.unwrap_or(self.junc), + (true, false) => self.tjunc.unwrap_or(self.junc), + (true, true) => self.line, + }; + out.write_all(Utf8Char::from(junc).as_bytes())?; } + i += 1; } if rborder { - out.write_all(Utf8Char::from(self.rjunc).as_bytes())?; + let rjunc = self.rjunc.unwrap_or(self.junc); + out.write_all(Utf8Char::from(rjunc).as_bytes())?; } out.write_all(NEWLINE)?; Ok(1) @@ -99,7 +131,7 @@ impl LineSeparator { impl Default for LineSeparator { fn default() -> Self { - LineSeparator::new('-', '+', '+', '+') + LineSeparator::new('-', '+') } } @@ -224,6 +256,8 @@ impl TableFormat { pub (crate) fn print_line_separator(&self, out: &mut T, col_width: &[usize], + current_spanning: &[bool], + next_spanning: &[bool], pos: LinePosition) -> Result { match *self.get_sep_for_line(pos) { @@ -232,6 +266,8 @@ impl TableFormat { out.write_all(&vec![b' '; self.get_indent()])?; l.print(out, col_width, + current_spanning, + next_spanning, self.get_padding(), self.csep.is_some(), self.lborder.is_some(), @@ -354,9 +390,9 @@ pub mod consts { lazy_static! { /// A line separator made of `-` and `+` - static ref MINUS_PLUS_SEP: LineSeparator = LineSeparator::new('-', '+', '+', '+'); + static ref MINUS_PLUS_SEP: LineSeparator = LineSeparator::new('-', '+'); /// A line separator made of `=` and `+` - static ref EQU_PLUS_SEP: LineSeparator = LineSeparator::new('=', '+', '+', '+'); + static ref EQU_PLUS_SEP: LineSeparator = LineSeparator::new('=', '+'); /// Default table format /// @@ -539,20 +575,26 @@ pub mod consts { .column_separator('│') .borders('│') .separators(&[LinePosition::Top], - LineSeparator::new('─', - '┬', - '┌', - '┐')) + LineSeparator::new_box('─', + '┬', + '┌', + '┐', + '┬', + '─')) .separators(&[LinePosition::Intern], - LineSeparator::new('─', - '┼', - '├', - '┤')) + LineSeparator::new_box('─', + '┼', + '├', + '┤', + '┬', + '┴')) .separators(&[LinePosition::Bottom], - LineSeparator::new('─', - '┴', - '└', - '┘')) + LineSeparator::new_box('─', + '┴', + '└', + '┘', + '─', + '┴')) .padding(1, 1) .build(); } diff --git a/src/lib.rs b/src/lib.rs index 977f58d306..99d181e9f5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -137,27 +137,52 @@ impl<'a> TableSlice<'a> { fn __print(&self, out: &mut T, f: F) -> Result where F: Fn(&Row, &mut T, &TableFormat, &[usize]) -> Result { + let colnum = self.get_column_num(); + let all_spanning = vec![true; colnum - 1]; + let title_ref = (*self.titles).as_ref(); + let top_spanning = title_ref.or(self.rows.first()).map_or_else( + || all_spanning.clone(), + |r| r.get_all_column_spanning()); + let title_spanning = title_ref.map_or_else( + || all_spanning.clone(), + |r| r.get_all_column_spanning()); + let first_spanning = self.rows.first().map_or_else( + || all_spanning.clone(), + |r| r.get_all_column_spanning()); + let last_spanning = self.rows.last().map_or_else( + || all_spanning.clone(), + |r| r.get_all_column_spanning()); let mut height = 0; // Compute columns width let col_width = self.get_all_column_width(); height += self.format - .print_line_separator(out, &col_width, LinePosition::Top)?; - if let Some(ref t) = *self.titles { - height += f(t, out, self.format, &col_width)?; + .print_line_separator(out, &col_width, + &all_spanning, &top_spanning, + LinePosition::Top)?; + if let Some(ref title) = *self.titles { + height += f(title, out, self.format, &col_width)?; height += self.format - .print_line_separator(out, &col_width, LinePosition::Title)?; + .print_line_separator(out, &col_width, + &title_spanning, &first_spanning, + LinePosition::Title)?; } // Print rows let mut iter = self.rows.iter().peekable(); - while let Some(r) = iter.next() { - height += f(r, out, self.format, &col_width)?; - if iter.peek().is_some() { + while let Some(current) = iter.next() { + height += f(current, out, self.format, &col_width)?; + if let Some(next) = iter.peek() { + let current_spanning = current.get_all_column_spanning(); + let next_spanning = next.get_all_column_spanning(); height += self.format - .print_line_separator(out, &col_width, LinePosition::Intern)?; + .print_line_separator(out, &col_width, + ¤t_spanning, &next_spanning, + LinePosition::Intern)?; } } height += self.format - .print_line_separator(out, &col_width, LinePosition::Bottom)?; + .print_line_separator(out, &col_width, + &last_spanning, &all_spanning, + LinePosition::Bottom)?; out.flush()?; Ok(height) } @@ -927,7 +952,7 @@ mod tests { .borders('|') .separators(&[format::LinePosition::Top, format::LinePosition::Bottom], - format::LineSeparator::new('-', '+', '+', '+')) + format::LineSeparator::new('-', '+')) .padding(1, 1) .build(); table.set_format(format); @@ -982,13 +1007,36 @@ mod tests { table.add_row(Row::new(vec![Cell::new("a"), Cell::new("bc"), Cell::new("def")])); table.add_row(Row::new(vec![Cell::new("def").style_spec("H02c"), Cell::new("a")])); let out = "\ -+----+----+-----+ ++----+----------+ | t1 | t2 | +====+====+=====+ | a | bc | def | +----+----+-----+ | def | a | -+----+----+-----+ ++---------+-----+ +"; + println!("{}", out); + println!("____"); + println!("{}", table.to_string().replace("\r\n","\n")); + assert_eq!(out, table.to_string().replace("\r\n","\n")); + assert_eq!(7, table.print(&mut StringWriter::new()).unwrap()); + } + + #[test] + fn test_horizontal_span_with_unicode() { + let mut table = Table::new(); + table.set_format(*FORMAT_BOX_CHARS); + table.add_row(Row::new(vec![Cell::new("TL"), Cell::new("TR").style_spec("H2c")])); + table.add_row(Row::new(vec![Cell::new("L"), Cell::new("C"), Cell::new("R")])); + table.add_row(Row::new(vec![Cell::new("BL").style_spec("H2c"), Cell::new("BR")])); + let out = "\ +┌────┬────────┐ +│ TL │ TR │ +├────┼───┬────┤ +│ L │ C │ R │ +├────┴───┼────┤ +│ BL │ BR │ +└────────┴────┘ "; println!("{}", out); println!("____"); diff --git a/src/row.rs b/src/row.rs index ae14429f1c..98ba5495c0 100644 --- a/src/row.rs +++ b/src/row.rs @@ -86,6 +86,25 @@ impl Row { 0 } + /// Get whether cells are spanned for all internal column separators + pub (crate) fn get_all_column_spanning(&self) -> Vec { + let column_count = self.column_count(); + let mut column_spanning = vec![false; column_count - 1]; + let mut i = 0; + let mut iter = self.cells.iter().peekable(); + while let Some(c) = iter.next() { + for _ in 0..c.get_hspan() - 1 { + column_spanning[i] = true; + i += 1; + } + if iter.peek().is_some() { + column_spanning[i] = false; + i += 1; + } + } + column_spanning + } + /// Get the cell at index `idx` pub fn get_cell(&self, idx: usize) -> Option<&Cell> { self.cells.get(idx)