Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prettier formatting when horizontal span is present #134

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 2 additions & 12 deletions examples/formatting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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
Expand Down
88 changes: 65 additions & 23 deletions src/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,40 @@ pub struct LineSeparator {
/// Internal junction separator
junc: char,
/// Left junction separator
ljunc: char,
ljunc: Option<char>,
/// Right junction separator
rjunc: char,
rjunc: Option<char>,
/// Top junction separator
tjunc: Option<char>,
/// Bottom junction separator
bjunc: Option<char>,
}

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),
}
}

Expand All @@ -72,25 +92,37 @@ impl LineSeparator {
fn print<T: Write + ?Sized>(&self,
out: &mut T,
col_width: &[usize],
current_spanning: &[bool],
next_spanning: &[bool],
padding: (usize, usize),
colsep: bool,
lborder: bool,
rborder: bool)
-> Result<usize, Error> {
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)
Expand All @@ -99,7 +131,7 @@ impl LineSeparator {

impl Default for LineSeparator {
fn default() -> Self {
LineSeparator::new('-', '+', '+', '+')
LineSeparator::new('-', '+')
}
}

Expand Down Expand Up @@ -224,6 +256,8 @@ impl TableFormat {
pub (crate) fn print_line_separator<T: Write + ?Sized>(&self,
out: &mut T,
col_width: &[usize],
current_spanning: &[bool],
next_spanning: &[bool],
pos: LinePosition)
-> Result<usize, Error> {
match *self.get_sep_for_line(pos) {
Expand All @@ -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(),
Expand Down Expand Up @@ -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
///
Expand Down Expand Up @@ -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();
}
Expand Down
72 changes: 60 additions & 12 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,27 +137,52 @@ impl<'a> TableSlice<'a> {
fn __print<T: Write + ?Sized, F>(&self, out: &mut T, f: F) -> Result<usize, Error>
where F: Fn(&Row, &mut T, &TableFormat, &[usize]) -> Result<usize, Error>
{
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,
&current_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)
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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!("____");
Expand Down
19 changes: 19 additions & 0 deletions src/row.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool> {
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)
Expand Down