summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPierre-Henri Symoneaux <pierre.henri.symoneaux@gmail.com>2018-09-18 10:32:31 +0200
committerPierre-Henri Symoneaux <pierre.henri.symoneaux@gmail.com>2018-09-18 11:58:38 +0200
commit06d834e3a57abefdfd0356f464219323a2af5158 (patch)
tree3480aa3b469b4002dc420a4e2d1ef6089e697188
parentceaf8bb7f562b20c6d76f0e803f0bc0b00acdfcc (diff)
Implemented horizontal span
Updated rust compatibility Fixed UT Added span support from macro Example + updated README Small update to README
-rw-r--r--.travis.yml6
-rw-r--r--README.md27
-rw-r--r--examples/span.rs31
-rw-r--r--src/cell.rs106
-rw-r--r--src/lib.rs28
-rw-r--r--src/main.rs4
-rw-r--r--src/row.rs71
7 files changed, 204 insertions, 69 deletions
diff --git a/.travis.yml b/.travis.yml
index 5674fa2..f6b9da3 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -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
diff --git a/README.md b/README.md
index d1f9157..af660a4 100644
--- a/README.md
+++ b/README.md
@@ -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
@@ -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
@@ -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.
@@ -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
@@ -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.
diff --git a/examples/span.rs b/examples/span.rs
new file mode 100644
index 0000000..61e1815
--- /dev/null
+++ b/examples/span.rs
@@ -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();
+} \ No newline at end of file
diff --git a/src/cell.rs b/src/cell.rs
index cca22d4..de13f36 100644
--- a/src/cell.rs
+++ b/src/cell.rs
@@ -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.
///
@@ -17,6 +18,7 @@ pub struct Cell {
width: usize,
align: Alignment,
style: Vec<Attr>,
+ hspan: usize,
}
impl Cell {
@@ -36,6 +38,7 @@ impl Cell {
width: width,
align: align,
style: Vec::new(),
+ hspan: 1,
}
}
@@ -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();
@@ -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,
@@ -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 */ }
}
}
@@ -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")
@@ -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)),
}
}
@@ -238,6 +268,7 @@ impl Default for Cell {
width: 0,
align: Alignment::LEFT,
style: Vec::new(),
+ hspan: 1,
}
}
}
@@ -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() {
@@ -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);
@@ -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]
diff --git a/src/lib.rs b/src/lib.rs
index 906f97b..03c011a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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;
}
@@ -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;
}
@@ -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
@@ -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;
diff --git a/src/main.rs b/src/main.rs
index 8251340..c653413 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -13,7 +13,7 @@ use prettytable::{Attr, color};
fn main() {
let _ = table!();
let mut table = Table::new();
- table.add_row(row![FrByb->"ABC", "DEFG", "HIJKLMN"]);
+ table.add_row(row![FrByH2b->"This is a long spanning cell", "DEFG", "HIJKLMN"]);
table.add_row(row!["foobar", "bar", "foo"]);
table.add_row(row![]);
// Add style to a full row
@@ -21,7 +21,7 @@ fn main() {
table.add_row(Row::new(vec![
Cell::new("foobar2"),
// Create a cell with a red foreground color
- Cell::new("bar2").with_style(Attr::ForegroundColor(color::RED)),
+ Cell::new_align("bar2", Alignment::CENTER).with_style(Attr::ForegroundColor(color::RED)).with_hspan(2),
// Create a cell with red foreground color, yellow background color, with bold characters
Cell::new("foo2").style_spec("FrByb")])
);
diff --git a/src/row.rs b/src/row.rs
index f02f9ef..a604dc7 100644
--- a/src/row.rs
+++ b/src/row.rs
@@ -28,9 +28,17 @@ impl Row {
Self::new(vec![Cell::default(); 0])
}
+ /// Count the number of column required in the table grid.
+ /// It takes into account horizontal spanning of cells. For
+ /// example, a cell with an hspan of 3 will add 3 column to the grid
+ pub fn column_count(&self) -> usize {
+ self.cells.iter().map(|c| c.get_hspan()).sum()
+ }
+
/// Get the number of cells in this row
pub fn len(&self) -> usize {
self.cells.len()
+ // self.cells.iter().map(|c| c.get_hspan()).sum()
}
/// Check if the row is empty (has no cell)
@@ -52,11 +60,31 @@ impl Row {
/// Get the minimum width required by the cell in the column `column`.
/// Return 0 if the cell does not exist in this row
- pub fn get_cell_width(&self, column: usize) -> usize {
- self.cells
- .get(column)
- .map(|cell| cell.get_width())
- .unwrap_or(0)
+ pub fn get_column_width(&self, column: usize, format: &TableFormat) -> usize {
+ let mut i = 0;
+ for c in &self.cells {
+ if i + c.get_hspan()-1 >= column {
+ if c.get_hspan() == 1 {
+ return c.get_width();
+ }
+ let (lp, rp) = format.get_padding();
+ let sep = format.get_column_separator(ColumnPosition::Intern).map(|_| 1).unwrap_or_default();
+ let rem = lp + rp +sep;
+ let mut w = c.get_width();
+ if w > rem {
+ w -= rem;
+ } else {
+ w = 0;
+ }
+ return (w as f64 / c.get_hspan() as f64).ceil() as usize;
+ }
+ i += c.get_hspan();
+ }
+ 0
+ // self.cells
+ // .get(column)
+ // .map(|cell| cell.get_width())
+ // .unwrap_or(0)
}
/// Get the cell at index `idx`
@@ -69,12 +97,12 @@ impl Row {
self.cells.get_mut(idx)
}
- /// Set the `cell` in the row at the given `column`
- pub fn set_cell(&mut self, cell: Cell, column: usize) -> Result<(), &str> {
- if column >= self.len() {
+ /// Set the `cell` in the row at the given `idx` index
+ pub fn set_cell(&mut self, cell: Cell, idx: usize) -> Result<(), &str> {
+ if idx >= self.len() {
return Err("Cannot find cell");
}
- self.cells[column] = cell;
+ self.cells[idx] = cell;
Ok(())
}
@@ -124,18 +152,31 @@ impl Row {
out.write_all(&vec![b' '; format.get_indent()])?;
format.print_column_separator(out, ColumnPosition::Left)?;
let (lp, rp) = format.get_padding();
- for j in 0..col_width.len() {
- out.write_all(&vec![b' '; lp])?;
+ let mut j = 0;
+ let mut hspan = 0; // The additional offset caused by cell's horizontal spanning
+ while j+hspan < col_width.len() {
+ out.write_all(&vec![b' '; lp])?; // Left padding
+ // skip_r_fill skip filling the end of the last cell if there's no character
+ // delimiting the end of the table
let skip_r_fill = (j == col_width.len() - 1) &&
format.get_column_separator(ColumnPosition::Right).is_none();
match self.get_cell(j) {
- Some(c) => f(c, out, i, col_width[j], skip_r_fill)?,
- None => f(&Cell::default(), out, i, col_width[j], skip_r_fill)?,
+ Some(c) => {
+ // In case of horizontal spanning, width is the sum of all spanned columns' width
+ let mut w = col_width[j+hspan..j+hspan+c.get_hspan()].iter().sum();
+ let real_span = c.get_hspan()-1;
+ w += real_span * (lp + rp) + real_span * format.get_column_separator(ColumnPosition::Intern).map(|_| 1).unwrap_or_default();
+ // Print cell content
+ f(c, out, i, w, skip_r_fill)?;
+ hspan += real_span; // Add span to offset
+ },
+ None => f(&Cell::default(), out, i, col_width[j+hspan], skip_r_fill)?,
};
- out.write_all(&vec![b' '; rp])?;
- if j < col_width.len() - 1 {
+ out.write_all(&vec![b' '; rp])?; // Right padding
+ if j+hspan < col_width.len() - 1 {
format.print_column_separator(out, ColumnPosition::Intern)?;
}
+ j+=1;
}
format.print_column_separator(out, ColumnPosition::Right)?;
out.write_all(NEWLINE)?;