Skip to content

Commit

Permalink
Merge pull request #85 from phsym/hspan
Browse files Browse the repository at this point in the history
Implemented horizontal span
  • Loading branch information
phsym authored Sep 18, 2018
2 parents ceaf8bb + 06d834e commit 71800c9
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 69 deletions.
6 changes: 0 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
language: rust
rust:
- 1.20.0
- 1.21.0
- 1.22.1
- 1.23.0
- 1.24.1
- 1.25.0
- 1.26.2
- 1.27.2
- 1.28.0
Expand Down
27 changes: 15 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Include the library as a dependency to your project by adding the following line
prettytable-rs = "^0.7"
```

The library requires at least `rust v1.20.0`.
The library requires at least `rust v1.26.0`.

## Basic usage

Expand Down Expand Up @@ -143,7 +143,7 @@ Rows may have different numbers of cells. The table will automatically adapt to

## Do it with style!

Tables can have a styled output with background and foreground colors, bold and italic as configurable settings, thanks to the `term` crate.
Tables can have a styled output with background and foreground colors, bold and italic as configurable settings, thanks to the `term` crate. Alignment in cells can also be set (Left, Right, Center), and a cell can span accross multiple columns.

`term` style attributes are reexported

Expand All @@ -155,35 +155,37 @@ Tables can have a styled output with background and foreground colors, bold and

table.add_row(Row::new(vec![
Cell::new("foobar")
.with_style(Attr::Bold),
.with_style(Attr::ForegroundColor(color::GREEN))
.with_style(Attr::Bold)
.with_style(Attr::ForegroundColor(color::GREEN)),
Cell::new("bar")
.with_style(Attr::BackgroundColor(color::RED)),
.with_style(Attr::Italic(true)),
Cell::new("foo")]));
.with_style(Attr::BackgroundColor(color::RED))
.with_style(Attr::Italic(true))
.with_hspan(2),
Cell::new("foo")
]));
```

- through style strings:
```rust
table.add_row(Row::new(vec![
Cell::new("foobar").style_spec("bFg"),
Cell::new("bar").style_spec("Bri"),
Cell::new("bar").style_spec("BriH2"),
Cell::new("foo")]));
```

- using `row!` macro:
```rust
table.add_row(row![bFg->"foobar", Bri->"bar", "foo"]);
table.add_row(row![bFg->"foobar", BriH2->"bar", "foo"]);
```

- using `table!` macro (this one creates a new table, unlike previous examples):
```rust
table!([bFg->"foobar", Bri->"bar", "foo"]);
table!([bFg->"foobar", BriH2->"bar", "foo"]);
```

Here
- **bFg** means **bold**, **F**oreground: **g**reen,
- **Bri** means **B**ackground: **r**ed, **i**talic.
- **BriH2** means **B**ackground: **r**ed, **i**talic, **H**orizontal span of **2**.

Another example: **FrBybc** means **F**oreground: **r**ed, **B**ackground: **y**ellow, **b**old, **c**enter.

Expand Down Expand Up @@ -214,6 +216,7 @@ All cases of styling cells in macros:

* **F** : **F**oreground (must be followed by a color specifier)
* **B** : **B**ackground (must be followed by a color specifier)
* **H** : **H**orizontal span (must be followed by a number)
* **b** : **b**old
* **i** : **i**talic
* **u** : **u**nderline
Expand Down Expand Up @@ -375,6 +378,6 @@ Since `v0.6.3`, platform specific line endings are activated though the default
When this feature is deactivated (for instance with the `--no-default-features` flag in cargo), line endings will be rendered with `\n`
on any platform.

This customization capability will probably move to Formatting API in `v0.7`.
This customization capability will probably move to Formatting API in a future release.

Additional examples are provided in the documentation and in [examples](./examples/) directory.
31 changes: 31 additions & 0 deletions examples/span.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#[macro_use]
extern crate prettytable;
use prettytable::{row::Row, cell::Cell, format::Alignment};


fn main() {

/*
The following code will output
+---------------+---------------+--------------+
| A table with horizontal span |
+===============+===============+==============+
| This is a cell with span of 2 | span of 1 |
+---------------+---------------+--------------+
| span of 1 | span of 1 | span of 1 |
+---------------+---------------+--------------+
| This cell with a span of 3 is centered |
+---------------+---------------+--------------+
*/

let mut table: prettytable::Table = table![
[H2 -> "This is a cell with span of 2", "span of 1"],
["span of 1", "span of 1", "span of 1"],
[H03c -> "This cell with a span of 3 is centered"]
];
table.set_titles(Row::new(vec![
Cell::new_align("A table with horizontal span", Alignment::CENTER).with_hspan(3)
]));
table.printstd();
}
106 changes: 75 additions & 31 deletions src/cell.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
//! This module contains definition of table/row cells stuff

use std::io::{Write, Error};
use std::string::ToString;
use super::{Attr, Terminal, color};
use super::format::Alignment;
use super::utils::display_width;
use super::utils::print_align;
use super::{color, Attr, Terminal};
use std::io::{Error, Write};
use std::string::ToString;
use std::str::FromStr;

/// Represent a table cell containing a string.
///
Expand All @@ -17,6 +18,7 @@ pub struct Cell {
width: usize,
align: Alignment,
style: Vec<Attr>,
hspan: usize,
}

impl Cell {
Expand All @@ -36,6 +38,7 @@ impl Cell {
width: width,
align: align,
style: Vec::new(),
hspan: 1,
}
}

Expand All @@ -61,6 +64,12 @@ impl Cell {
self
}

/// Add horizontal spanning to the cell
pub fn with_hspan(mut self, hspan: usize) -> Cell {
self.set_hspan(hspan);
self
}

/// Remove all style attributes and reset alignment to default (LEFT)
pub fn reset_style(&mut self) {
self.style.clear();
Expand Down Expand Up @@ -107,7 +116,8 @@ impl Cell {
self.reset_style();
let mut foreground = false;
let mut background = false;
for c in spec.chars() {
let mut it = spec.chars().peekable();
while let Some(c) = it.next() {
if foreground || background {
let color = match c {
'r' => color::RED,
Expand Down Expand Up @@ -150,6 +160,14 @@ impl Cell {
'c' => self.align(Alignment::CENTER),
'l' => self.align(Alignment::LEFT),
'r' => self.align(Alignment::RIGHT),
'H' => {
let mut span_s = String::new();
while let Some('0'..='9') = it.peek() {
span_s.push(it.next().unwrap());
}
let span = usize::from_str(&span_s).unwrap();
self.set_hspan(span);
}
_ => { /* Silently ignore unknown tags */ }
}
}
Expand All @@ -167,6 +185,16 @@ impl Cell {
self.width
}

/// Set horizontal span for this cell (must be > 0)
pub fn set_hspan(&mut self, hspan: usize) {
self.hspan = if hspan <= 0 {1} else {hspan};
}

/// Get horizontal span of this cell (> 0)
pub fn get_hspan(&self) -> usize {
self.hspan
}

/// Return a copy of the full string contained in the cell
pub fn get_content(&self) -> String {
self.content.join("\n")
Expand All @@ -176,36 +204,38 @@ impl Cell {
/// `idx` is the line index to print. `col_width` is the column width used to
/// fill the cells with blanks so it fits in the table.
/// If `ìdx` is higher than this cell's height, it will print empty content
pub fn print<T: Write + ?Sized>(&self,
out: &mut T,
idx: usize,
col_width: usize,
skip_right_fill: bool)
-> Result<(), Error> {
pub fn print<T: Write + ?Sized>(
&self,
out: &mut T,
idx: usize,
col_width: usize,
skip_right_fill: bool,
) -> Result<(), Error> {
let c = self.content.get(idx).map(|s| s.as_ref()).unwrap_or("");
print_align(out, self.align, c, ' ', col_width, skip_right_fill)
}

/// Apply style then call `print` to print the cell into a terminal
pub fn print_term<T: Terminal + ?Sized>(&self,
out: &mut T,
idx: usize,
col_width: usize,
skip_right_fill: bool)
-> Result<(), Error> {
pub fn print_term<T: Terminal + ?Sized>(
&self,
out: &mut T,
idx: usize,
col_width: usize,
skip_right_fill: bool,
) -> Result<(), Error> {
for a in &self.style {
match out.attr(*a) {
Ok(..) |
Err(::term::Error::NotSupported) |
Err(::term::Error::ColorOutOfRange) => (), // Ignore unsupported atrributes
Ok(..) | Err(::term::Error::NotSupported) | Err(::term::Error::ColorOutOfRange) => {
()
} // Ignore unsupported atrributes
Err(e) => return Err(term_error_to_io_error(e)),
};
}
self.print(out, idx, col_width, skip_right_fill)?;
match out.reset() {
Ok(..) |
Err(::term::Error::NotSupported) |
Err(::term::Error::ColorOutOfRange) => Ok(()),
Ok(..) | Err(::term::Error::NotSupported) | Err(::term::Error::ColorOutOfRange) => {
Ok(())
}
Err(e) => Err(term_error_to_io_error(e)),
}
}
Expand Down Expand Up @@ -238,6 +268,7 @@ impl Default for Cell {
width: 0,
align: Alignment::LEFT,
style: Vec::new(),
hspan: 1,
}
}
}
Expand Down Expand Up @@ -271,17 +302,23 @@ impl Default for Cell {
/// ```
#[macro_export]
macro_rules! cell {
() => ($crate::cell::Cell::default());
($value:expr) => ($crate::cell::Cell::new(&$value.to_string()));
($style:ident -> $value:expr) => (cell!($value).style_spec(stringify!($style)));
() => {
$crate::cell::Cell::default()
};
($value:expr) => {
$crate::cell::Cell::new(&$value.to_string())
};
($style:ident -> $value:expr) => {
cell!($value).style_spec(stringify!($style))
};
}

#[cfg(test)]
mod tests {
use cell::Cell;
use utils::StringWriter;
use format::Alignment;
use term::{Attr, color};
use term::{color, Attr};
use utils::StringWriter;

#[test]
fn get_content() {
Expand Down Expand Up @@ -350,14 +387,18 @@ mod tests {
assert!(cell.style.contains(&Attr::Italic(true)));
assert!(cell.style.contains(&Attr::Bold));
assert!(cell.style.contains(&Attr::ForegroundColor(color::RED)));
assert!(cell.style
.contains(&Attr::BackgroundColor(color::BRIGHT_BLUE)));
assert!(
cell.style
.contains(&Attr::BackgroundColor(color::BRIGHT_BLUE))
);
assert_eq!(cell.align, Alignment::CENTER);

cell = cell.style_spec("FDBwr");
assert_eq!(cell.style.len(), 2);
assert!(cell.style
.contains(&Attr::ForegroundColor(color::BRIGHT_BLACK)));
assert!(
cell.style
.contains(&Attr::ForegroundColor(color::BRIGHT_BLACK))
);
assert!(cell.style.contains(&Attr::BackgroundColor(color::WHITE)));
assert_eq!(cell.align, Alignment::RIGHT);

Expand All @@ -368,6 +409,9 @@ mod tests {
assert_eq!(cell.style.len(), 1);
cell = cell.style_spec("zzz");
assert!(cell.style.is_empty());
assert_eq!(cell.get_hspan(), 1);
cell = cell.style_spec("FDBwH03r");
assert_eq!(cell.get_hspan(), 3);
}

#[test]
Expand Down
28 changes: 25 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ impl<'a> TableSlice<'a> {
pub fn get_column_num(&self) -> usize {
let mut cnum = 0;
for r in self.rows {
let l = r.len();
let l = r.column_count();
if l > cnum {
cnum = l;
}
Expand All @@ -102,11 +102,11 @@ impl<'a> TableSlice<'a> {
/// Return 0 if the column does not exists;
fn get_column_width(&self, col_idx: usize) -> usize {
let mut width = match *self.titles {
Some(ref t) => t.get_cell_width(col_idx),
Some(ref t) => t.get_column_width(col_idx, self.format),
None => 0,
};
for r in self.rows {
let l = r.get_cell_width(col_idx);
let l = r.get_column_width(col_idx, self.format);
if l > width {
width = l;
}
Expand All @@ -120,6 +120,7 @@ impl<'a> TableSlice<'a> {
let colnum = self.get_column_num();
let mut col_width = vec![0usize; colnum];
for i in 0..colnum {
// TODO: calling "get_column_width()" in a loop is inefficient
col_width[i] = self.get_column_width(i);
}
col_width
Expand Down Expand Up @@ -1030,6 +1031,27 @@ mod tests {
assert_eq!(out, table.to_string().replace("\r\n","\n"));
}

#[test]
fn test_horizontal_span() {
let mut table = Table::new();
table.set_titles(Row::new(vec![Cell::new("t1"), Cell::new("t2").with_hspan(2)]));
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"));
}

#[cfg(feature = "csv")]
mod csv {
use Table;
Expand Down
Loading

0 comments on commit 71800c9

Please sign in to comment.