summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPierre-Henri Symoneaux <phsym@users.noreply.github.com>2019-08-26 09:57:24 +0200
committerGitHub <noreply@github.com>2019-08-26 09:57:24 +0200
commitbdfb15081cf5e44934a08941f18a6eaba8275e1e (patch)
tree2196d98f4c791f2ab9efdfe921805a683330164a
parent1e06ea72ccf03cd2129d104360e1e07679190a07 (diff)
parent19805573d9260c074f19442b02d62cb8a1769913 (diff)
Merge pull request #101 from jonasbb/html-and-evcxr-integration
Html and Evcxr integration
-rw-r--r--Cargo.toml4
-rw-r--r--README.md17
-rw-r--r--prettytable-evcxr.pngbin0 -> 72017 bytes
-rw-r--r--src/cell.rs95
-rw-r--r--src/evcxr.rs30
-rw-r--r--src/lib.rs94
-rw-r--r--src/row.rs15
-rw-r--r--src/utils.rs38
8 files changed, 291 insertions, 2 deletions
diff --git a/Cargo.toml b/Cargo.toml
index a3d0939..b860676 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,6 +12,9 @@ keywords = ["tab", "table", "format", "pretty", "print"]
categories = ["command-line-interface"]
license = "BSD-3-Clause"
edition = "2018"
+exclude = [
+ "prettytable-evcxr.png"
+]
[badges]
appveyor = { repository = "phsym/prettytable-rs", branch = "master", service = "github" }
@@ -20,6 +23,7 @@ codecov = { repository = "phsym/prettytable-rs", branch = "master", service = "g
[features]
default = ["win_crlf", "csv"]
+evcxr = []
win_crlf = []
[[bin]]
diff --git a/README.md b/README.md
index d702cf8..d1fa0ea 100644
--- a/README.md
+++ b/README.md
@@ -29,6 +29,7 @@ A formatted and aligned table printer library for [Rust](https://www.rust-lang.o
* [Importing](#user-content-importing)
* [Exporting](#user-content-exporting)
* [Note on line endings](#user-content-note-on-line-endings)
+ * [Evcxr Integration](#evcxr-integration)
## Including
@@ -379,3 +380,19 @@ on any platform.
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.
+
+## Evcxr Integration
+
+[Evcxr][evcxr] is a Rust REPL and a [Jupyter notebook kernel][evcxr-jupyter].
+This crate integrates into Evcxr and the Jupyter notebooks using the `evcxr` feature flag, which enables native displays of tables.
+This includes support for displaying colors and various formattings.
+
+You can include prettytable as a dependency using this line:
+```
+:dep prettytable = { git = "https://github.com/phsym/prettytable-rs", package = "prettytable-rs", features = ["evcxr"] }
+```
+
+![prettytable being used in a Jupyter notebook with Evcxr Rust kernel.](./prettytable-evcxr.png)
+
+[evcxr]: https://github.com/google/evcxr/
+[evcxr-jupyter]: https://github.com/google/evcxr/blob/master/evcxr_jupyter/README.md
diff --git a/prettytable-evcxr.png b/prettytable-evcxr.png
new file mode 100644
index 0000000..22264be
--- /dev/null
+++ b/prettytable-evcxr.png
Binary files differ
diff --git a/src/cell.rs b/src/cell.rs
index 795c4c7..410ebd1 100644
--- a/src/cell.rs
+++ b/src/cell.rs
@@ -1,8 +1,7 @@
//! This module contains definition of table/row cells stuff
use super::format::Alignment;
-use super::utils::display_width;
-use super::utils::print_align;
+use super::utils::{display_width, print_align, HtmlEscape};
use super::{color, Attr, Terminal};
use std::io::{Error, Write};
use std::string::ToString;
@@ -244,6 +243,79 @@ impl Cell {
Err(e) => Err(term_error_to_io_error(e)),
}
}
+
+ /// Print the cell in HTML format to `out`.
+ pub fn print_html<T: Write + ?Sized>(&self, out: &mut T) -> Result<usize, Error> {
+ /// Convert the color to a hex value useful in CSS
+ fn color2hex(color: color::Color) -> &'static str {
+ match color {
+ color::BLACK => "#000000",
+ color::RED => "#aa0000",
+ color::GREEN => "#00aa00",
+ color::YELLOW => "#aa5500",
+ color::BLUE => "#0000aa",
+ color::MAGENTA => "#aa00aa",
+ color::CYAN => "#00aaaa",
+ color::WHITE => "#aaaaaa",
+ color::BRIGHT_BLACK => "#555555",
+ color::BRIGHT_RED => "#ff5555",
+ color::BRIGHT_GREEN => "#55ff55",
+ color::BRIGHT_YELLOW => "#ffff55",
+ color::BRIGHT_BLUE => "#5555ff",
+ color::BRIGHT_MAGENTA => "#ff55ff",
+ color::BRIGHT_CYAN => "#55ffff",
+ color::BRIGHT_WHITE => "#ffffff",
+
+ // Unknown colors, fallback to blakc
+ _ => "#000000",
+ }
+ };
+
+ let colspan = if self.hspan > 1 {
+ format!(" colspan=\"{}\"", self.hspan)
+ } else {
+ String::new()
+ };
+
+ // Process style properties like color
+ let mut styles = String::new();
+ for style in &self.style {
+ match style {
+ Attr::Bold => styles += "font-weight: bold;",
+ Attr::Italic(true) => styles += "font-style: italic;",
+ Attr::Underline(true) => styles += "text-decoration: underline;",
+ Attr::ForegroundColor(c) => {
+ styles += "color: ";
+ styles += color2hex(*c);
+ styles += ";";
+ }
+ Attr::BackgroundColor(c) => {
+ styles += "background-color: ";
+ styles += color2hex(*c);
+ styles += ";";
+ }
+ _ => {}
+ }
+ }
+ // Process alignment
+ match self.align {
+ Alignment::LEFT => styles += "text-align: left;",
+ Alignment::CENTER => styles += "text-align: center;",
+ Alignment::RIGHT => styles += "text-align: right;",
+ }
+
+ let content = self.content.join("<br />");
+ out.write_all(
+ format!(
+ "<td{1} style=\"{2}\">{0}</td>",
+ HtmlEscape(&content),
+ colspan,
+ styles
+ )
+ .as_bytes(),
+ )?;
+ Ok(self.hspan)
+ }
}
fn term_error_to_io_error(te: ::term::Error) -> Error {
@@ -361,6 +433,25 @@ mod tests {
}
#[test]
+ fn print_ascii_html() {
+ let ascii_cell = Cell::new("hello");
+ assert_eq!(ascii_cell.get_width(), 5);
+
+ let mut out = StringWriter::new();
+ let _ = ascii_cell.print_html(&mut out);
+ assert_eq!(out.as_string(), r#"<td style="text-align: left;">hello</td>"#);
+ }
+
+ #[test]
+ fn print_html_special_chars() {
+ let ascii_cell = Cell::new("<abc\">&'");
+
+ let mut out = StringWriter::new();
+ let _ = ascii_cell.print_html(&mut out);
+ assert_eq!(out.as_string(), r#"<td style="text-align: left;">&lt;abc&quot;&gt;&amp;&#39;</td>"#);
+ }
+
+ #[test]
fn align_left() {
let cell = Cell::new_align("test", Alignment::LEFT);
let mut out = StringWriter::new();
diff --git a/src/evcxr.rs b/src/evcxr.rs
new file mode 100644
index 0000000..11d632a
--- /dev/null
+++ b/src/evcxr.rs
@@ -0,0 +1,30 @@
+//! This modules contains traits and implementations to work within Evcxr
+
+use super::TableSlice;
+use super::utils::StringWriter;
+use std::io::Write;
+
+/// Evcxr specific output trait
+pub trait EvcxrDisplay {
+ /// Print self in one or multiple Evcxr compatile types.
+ fn evcxr_display(&self);
+}
+
+impl<'a, T> EvcxrDisplay for T
+where
+ T: AsRef<TableSlice<'a>>,
+{
+ fn evcxr_display(&self) {
+ let mut writer = StringWriter::new();
+ // Plain Text
+ let _ = writer.write_all(b"EVCXR_BEGIN_CONTENT text/plain\n");
+ let _ = self.as_ref().print(&mut writer);
+ let _ = writer.write_all(b"\nEVCXR_END_CONTENT\n");
+
+ // Html
+ let _ = writer.write_all(b"EVCXR_BEGIN_CONTENT text/html\n");
+ let _ = self.as_ref().print_html(&mut writer);
+ let _ = writer.write_all(b"\nEVCXR_END_CONTENT\n");
+ println!("{}", writer.as_string());
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 66daaf9..9ccb008 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -24,6 +24,9 @@ mod utils;
#[cfg(feature = "csv")]
pub mod csv;
+#[cfg(feature = "evcxr")]
+pub mod evcxr;
+
pub use row::Row;
pub use cell::Cell;
use format::{TableFormat, LinePosition, consts};
@@ -204,6 +207,28 @@ impl<'a> TableSlice<'a> {
pub fn printstd(&self) -> usize {
self.print_tty(false)
}
+
+ /// Print table in HTML format to `out`.
+ pub fn print_html<T: Write + ?Sized>(&self, out: &mut T) -> Result<(), Error> {
+ // Compute column width
+ let column_num = self.get_column_num();
+ out.write_all(b"<table>")?;
+ // Print titles / table header
+ if let Some(ref t) = *self.titles {
+ out.write_all(b"<th>")?;
+ t.print_html(out, column_num)?;
+ out.write_all(b"</th>")?;
+ }
+ // Print rows
+ for r in self.rows {
+ out.write_all(b"<tr>")?;
+ r.print_html(out, column_num)?;
+ out.write_all(b"</tr>")?;
+ }
+ out.write_all(b"</table>")?;
+ out.flush()?;
+ Ok(())
+ }
}
impl<'a> IntoIterator for &'a TableSlice<'a> {
@@ -372,6 +397,10 @@ impl Table {
self.as_ref().printstd()
}
+ /// Print table in HTML format to `out`.
+ pub fn print_html<T: Write + ?Sized>(&self, out: &mut T) -> Result<(), Error> {
+ self.as_ref().print_html(out)
+ }
}
impl Index<usize> for Table {
@@ -980,4 +1009,69 @@ mod tests {
assert_eq!(out, table.to_string().replace("\r\n","\n"));
assert_eq!(7, table.print(&mut StringWriter::new()).unwrap());
}
+
+ #[test]
+ fn table_html() {
+ let mut table = Table::new();
+ table.add_row(Row::new(vec![Cell::new("a"), Cell::new("bc"), Cell::new("def")]));
+ table.add_row(Row::new(vec![Cell::new("def"), Cell::new("bc"), Cell::new("a")]));
+ table.set_titles(Row::new(vec![Cell::new("t1"), Cell::new("t2"), Cell::new("t3")]));
+ let out = "\
+<table>\
+<th><td style=\"text-align: left;\">t1</td><td style=\"text-align: left;\">t2</td><td style=\"text-align: left;\">t3</td></th>\
+<tr><td style=\"text-align: left;\">a</td><td style=\"text-align: left;\">bc</td><td style=\"text-align: left;\">def</td></tr>\
+<tr><td style=\"text-align: left;\">def</td><td style=\"text-align: left;\">bc</td><td style=\"text-align: left;\">a</td></tr>\
+</table>";
+ let mut writer = StringWriter::new();
+ assert!(table.print_html(&mut writer).is_ok());
+ assert_eq!(writer.as_string().replace("\r\n", "\n"), out);
+ table.unset_titles();
+ let out = "\
+<table>\
+<tr><td style=\"text-align: left;\">a</td><td style=\"text-align: left;\">bc</td><td style=\"text-align: left;\">def</td></tr>\
+<tr><td style=\"text-align: left;\">def</td><td style=\"text-align: left;\">bc</td><td style=\"text-align: left;\">a</td></tr>\
+</table>";
+ let mut writer = StringWriter::new();
+ assert!(table.print_html(&mut writer).is_ok());
+ assert_eq!(writer.as_string().replace("\r\n", "\n"), out);
+ }
+
+ #[test]
+ fn table_html_colors() {
+ let mut table = Table::new();
+ table.add_row(Row::new(vec![
+ Cell::new("bold").style_spec("b"),
+ Cell::new("italic").style_spec("i"),
+ Cell::new("underline").style_spec("u"),
+ ]));
+ table.add_row(Row::new(vec![
+ Cell::new("left").style_spec("l"),
+ Cell::new("center").style_spec("c"),
+ Cell::new("right").style_spec("r"),
+ ]));
+ table.add_row(Row::new(vec![
+ Cell::new("red").style_spec("Fr"),
+ Cell::new("black").style_spec("Fd"),
+ Cell::new("yellow").style_spec("Fy"),
+ ]));
+ table.add_row(Row::new(vec![
+ Cell::new("bright magenta on cyan").style_spec("FMBc"),
+ Cell::new("white on bright green").style_spec("FwBG"),
+ Cell::new("default on blue").style_spec("Bb"),
+ ]));
+ table.set_titles(Row::new(vec![
+ Cell::new("span horizontal").style_spec("H3"),
+ ]));
+ let out = "\
+<table>\
+<th><td colspan=\"3\" style=\"text-align: left;\">span horizontal</td></th>\
+<tr><td style=\"font-weight: bold;text-align: left;\">bold</td><td style=\"font-style: italic;text-align: left;\">italic</td><td style=\"text-decoration: underline;text-align: left;\">underline</td></tr>\
+<tr><td style=\"text-align: left;\">left</td><td style=\"text-align: center;\">center</td><td style=\"text-align: right;\">right</td></tr>\
+<tr><td style=\"color: #aa0000;text-align: left;\">red</td><td style=\"color: #000000;text-align: left;\">black</td><td style=\"color: #aa5500;text-align: left;\">yellow</td></tr>\
+<tr><td style=\"color: #ff55ff;background-color: #00aaaa;text-align: left;\">bright magenta on cyan</td><td style=\"color: #aaaaaa;background-color: #55ff55;text-align: left;\">white on bright green</td><td style=\"background-color: #0000aa;text-align: left;\">default on blue</td></tr>\
+</table>";
+ let mut writer = StringWriter::new();
+ assert!(table.print_html(&mut writer).is_ok());
+ assert_eq!(writer.as_string().replace("\r\n", "\n"), out);
+ }
}
diff --git a/src/row.rs b/src/row.rs
index 94c0d8e..dd3efcf 100644
--- a/src/row.rs
+++ b/src/row.rs
@@ -205,6 +205,21 @@ impl Row {
-> Result<usize, Error> {
self.__print(out, format, col_width, Cell::print_term)
}
+
+ /// Print the row in HTML format to `out`.
+ ///
+ /// If the row is has fewer columns than `col_num`, the row is padded with empty cells.
+ pub fn print_html<T: Write + ?Sized>(&self, out: &mut T, col_num: usize) -> Result<(), Error> {
+ let mut printed_columns = 0;
+ for cell in self.iter() {
+ printed_columns += cell.print_html(out)?;
+ }
+ // Pad with empty cells, if target width is not reached
+ for _ in 0..col_num - printed_columns {
+ Cell::default().print_html(out)?;
+ }
+ Ok(())
+ }
}
impl Default for Row {
diff --git a/src/utils.rs b/src/utils.rs
index f2d89ae..dbd8db1 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -1,4 +1,5 @@
//! Internal only utilities
+use std::fmt;
use std::io::{Error, ErrorKind, Write};
use std::str;
@@ -105,6 +106,43 @@ pub fn display_width(text: &str) -> usize {
width - hidden
}
+/// Wrapper struct which will emit the HTML-escaped version of the contained
+/// string when passed to a format string.
+pub struct HtmlEscape<'a>(pub &'a str);
+
+impl<'a> fmt::Display for HtmlEscape<'a> {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+ // Because the internet is always right, turns out there's not that many
+ // characters to escape: http://stackoverflow.com/questions/7381974
+ let HtmlEscape(s) = *self;
+ let pile_o_bits = s;
+ let mut last = 0;
+ for (i, ch) in s.bytes().enumerate() {
+ match ch as char {
+ '<' | '>' | '&' | '\'' | '"' => {
+ fmt.write_str(&pile_o_bits[last.. i])?;
+ let s = match ch as char {
+ '>' => "&gt;",
+ '<' => "&lt;",
+ '&' => "&amp;",
+ '\'' => "&#39;",
+ '"' => "&quot;",
+ _ => unreachable!()
+ };
+ fmt.write_str(s)?;
+ last = i + 1;
+ }
+ _ => {}
+ }
+ }
+
+ if last < s.len() {
+ fmt.write_str(&pile_o_bits[last..])?;
+ }
+ Ok(())
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;