Skip to content

Commit bdfb150

Browse files
authored
Merge pull request #101 from jonasbb/html-and-evcxr-integration
Html and Evcxr integration
2 parents 1e06ea7 + 1980557 commit bdfb150

File tree

8 files changed

+291
-2
lines changed

8 files changed

+291
-2
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ keywords = ["tab", "table", "format", "pretty", "print"]
1212
categories = ["command-line-interface"]
1313
license = "BSD-3-Clause"
1414
edition = "2018"
15+
exclude = [
16+
"prettytable-evcxr.png"
17+
]
1518

1619
[badges]
1720
appveyor = { repository = "phsym/prettytable-rs", branch = "master", service = "github" }
@@ -20,6 +23,7 @@ codecov = { repository = "phsym/prettytable-rs", branch = "master", service = "g
2023

2124
[features]
2225
default = ["win_crlf", "csv"]
26+
evcxr = []
2327
win_crlf = []
2428

2529
[[bin]]

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ A formatted and aligned table printer library for [Rust](https://www.rust-lang.o
2929
* [Importing](#user-content-importing)
3030
* [Exporting](#user-content-exporting)
3131
* [Note on line endings](#user-content-note-on-line-endings)
32+
* [Evcxr Integration](#evcxr-integration)
3233

3334
## Including
3435

@@ -379,3 +380,19 @@ on any platform.
379380
This customization capability will probably move to Formatting API in a future release.
380381

381382
Additional examples are provided in the documentation and in [examples](./examples/) directory.
383+
384+
## Evcxr Integration
385+
386+
[Evcxr][evcxr] is a Rust REPL and a [Jupyter notebook kernel][evcxr-jupyter].
387+
This crate integrates into Evcxr and the Jupyter notebooks using the `evcxr` feature flag, which enables native displays of tables.
388+
This includes support for displaying colors and various formattings.
389+
390+
You can include prettytable as a dependency using this line:
391+
```
392+
:dep prettytable = { git = "https://github.com/phsym/prettytable-rs", package = "prettytable-rs", features = ["evcxr"] }
393+
```
394+
395+
![prettytable being used in a Jupyter notebook with Evcxr Rust kernel.](./prettytable-evcxr.png)
396+
397+
[evcxr]: https://github.com/google/evcxr/
398+
[evcxr-jupyter]: https://github.com/google/evcxr/blob/master/evcxr_jupyter/README.md

prettytable-evcxr.png

70.3 KB
Loading

src/cell.rs

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
//! This module contains definition of table/row cells stuff
22
33
use super::format::Alignment;
4-
use super::utils::display_width;
5-
use super::utils::print_align;
4+
use super::utils::{display_width, print_align, HtmlEscape};
65
use super::{color, Attr, Terminal};
76
use std::io::{Error, Write};
87
use std::string::ToString;
@@ -244,6 +243,79 @@ impl Cell {
244243
Err(e) => Err(term_error_to_io_error(e)),
245244
}
246245
}
246+
247+
/// Print the cell in HTML format to `out`.
248+
pub fn print_html<T: Write + ?Sized>(&self, out: &mut T) -> Result<usize, Error> {
249+
/// Convert the color to a hex value useful in CSS
250+
fn color2hex(color: color::Color) -> &'static str {
251+
match color {
252+
color::BLACK => "#000000",
253+
color::RED => "#aa0000",
254+
color::GREEN => "#00aa00",
255+
color::YELLOW => "#aa5500",
256+
color::BLUE => "#0000aa",
257+
color::MAGENTA => "#aa00aa",
258+
color::CYAN => "#00aaaa",
259+
color::WHITE => "#aaaaaa",
260+
color::BRIGHT_BLACK => "#555555",
261+
color::BRIGHT_RED => "#ff5555",
262+
color::BRIGHT_GREEN => "#55ff55",
263+
color::BRIGHT_YELLOW => "#ffff55",
264+
color::BRIGHT_BLUE => "#5555ff",
265+
color::BRIGHT_MAGENTA => "#ff55ff",
266+
color::BRIGHT_CYAN => "#55ffff",
267+
color::BRIGHT_WHITE => "#ffffff",
268+
269+
// Unknown colors, fallback to blakc
270+
_ => "#000000",
271+
}
272+
};
273+
274+
let colspan = if self.hspan > 1 {
275+
format!(" colspan=\"{}\"", self.hspan)
276+
} else {
277+
String::new()
278+
};
279+
280+
// Process style properties like color
281+
let mut styles = String::new();
282+
for style in &self.style {
283+
match style {
284+
Attr::Bold => styles += "font-weight: bold;",
285+
Attr::Italic(true) => styles += "font-style: italic;",
286+
Attr::Underline(true) => styles += "text-decoration: underline;",
287+
Attr::ForegroundColor(c) => {
288+
styles += "color: ";
289+
styles += color2hex(*c);
290+
styles += ";";
291+
}
292+
Attr::BackgroundColor(c) => {
293+
styles += "background-color: ";
294+
styles += color2hex(*c);
295+
styles += ";";
296+
}
297+
_ => {}
298+
}
299+
}
300+
// Process alignment
301+
match self.align {
302+
Alignment::LEFT => styles += "text-align: left;",
303+
Alignment::CENTER => styles += "text-align: center;",
304+
Alignment::RIGHT => styles += "text-align: right;",
305+
}
306+
307+
let content = self.content.join("<br />");
308+
out.write_all(
309+
format!(
310+
"<td{1} style=\"{2}\">{0}</td>",
311+
HtmlEscape(&content),
312+
colspan,
313+
styles
314+
)
315+
.as_bytes(),
316+
)?;
317+
Ok(self.hspan)
318+
}
247319
}
248320

249321
fn term_error_to_io_error(te: ::term::Error) -> Error {
@@ -360,6 +432,25 @@ mod tests {
360432
assert_eq!(out.as_string(), "由系统自动更新 ");
361433
}
362434

435+
#[test]
436+
fn print_ascii_html() {
437+
let ascii_cell = Cell::new("hello");
438+
assert_eq!(ascii_cell.get_width(), 5);
439+
440+
let mut out = StringWriter::new();
441+
let _ = ascii_cell.print_html(&mut out);
442+
assert_eq!(out.as_string(), r#"<td style="text-align: left;">hello</td>"#);
443+
}
444+
445+
#[test]
446+
fn print_html_special_chars() {
447+
let ascii_cell = Cell::new("<abc\">&'");
448+
449+
let mut out = StringWriter::new();
450+
let _ = ascii_cell.print_html(&mut out);
451+
assert_eq!(out.as_string(), r#"<td style="text-align: left;">&lt;abc&quot;&gt;&amp;&#39;</td>"#);
452+
}
453+
363454
#[test]
364455
fn align_left() {
365456
let cell = Cell::new_align("test", Alignment::LEFT);

src/evcxr.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//! This modules contains traits and implementations to work within Evcxr
2+
3+
use super::TableSlice;
4+
use super::utils::StringWriter;
5+
use std::io::Write;
6+
7+
/// Evcxr specific output trait
8+
pub trait EvcxrDisplay {
9+
/// Print self in one or multiple Evcxr compatile types.
10+
fn evcxr_display(&self);
11+
}
12+
13+
impl<'a, T> EvcxrDisplay for T
14+
where
15+
T: AsRef<TableSlice<'a>>,
16+
{
17+
fn evcxr_display(&self) {
18+
let mut writer = StringWriter::new();
19+
// Plain Text
20+
let _ = writer.write_all(b"EVCXR_BEGIN_CONTENT text/plain\n");
21+
let _ = self.as_ref().print(&mut writer);
22+
let _ = writer.write_all(b"\nEVCXR_END_CONTENT\n");
23+
24+
// Html
25+
let _ = writer.write_all(b"EVCXR_BEGIN_CONTENT text/html\n");
26+
let _ = self.as_ref().print_html(&mut writer);
27+
let _ = writer.write_all(b"\nEVCXR_END_CONTENT\n");
28+
println!("{}", writer.as_string());
29+
}
30+
}

src/lib.rs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ mod utils;
2424
#[cfg(feature = "csv")]
2525
pub mod csv;
2626

27+
#[cfg(feature = "evcxr")]
28+
pub mod evcxr;
29+
2730
pub use row::Row;
2831
pub use cell::Cell;
2932
use format::{TableFormat, LinePosition, consts};
@@ -204,6 +207,28 @@ impl<'a> TableSlice<'a> {
204207
pub fn printstd(&self) -> usize {
205208
self.print_tty(false)
206209
}
210+
211+
/// Print table in HTML format to `out`.
212+
pub fn print_html<T: Write + ?Sized>(&self, out: &mut T) -> Result<(), Error> {
213+
// Compute column width
214+
let column_num = self.get_column_num();
215+
out.write_all(b"<table>")?;
216+
// Print titles / table header
217+
if let Some(ref t) = *self.titles {
218+
out.write_all(b"<th>")?;
219+
t.print_html(out, column_num)?;
220+
out.write_all(b"</th>")?;
221+
}
222+
// Print rows
223+
for r in self.rows {
224+
out.write_all(b"<tr>")?;
225+
r.print_html(out, column_num)?;
226+
out.write_all(b"</tr>")?;
227+
}
228+
out.write_all(b"</table>")?;
229+
out.flush()?;
230+
Ok(())
231+
}
207232
}
208233

209234
impl<'a> IntoIterator for &'a TableSlice<'a> {
@@ -372,6 +397,10 @@ impl Table {
372397
self.as_ref().printstd()
373398
}
374399

400+
/// Print table in HTML format to `out`.
401+
pub fn print_html<T: Write + ?Sized>(&self, out: &mut T) -> Result<(), Error> {
402+
self.as_ref().print_html(out)
403+
}
375404
}
376405

377406
impl Index<usize> for Table {
@@ -980,4 +1009,69 @@ mod tests {
9801009
assert_eq!(out, table.to_string().replace("\r\n","\n"));
9811010
assert_eq!(7, table.print(&mut StringWriter::new()).unwrap());
9821011
}
1012+
1013+
#[test]
1014+
fn table_html() {
1015+
let mut table = Table::new();
1016+
table.add_row(Row::new(vec![Cell::new("a"), Cell::new("bc"), Cell::new("def")]));
1017+
table.add_row(Row::new(vec![Cell::new("def"), Cell::new("bc"), Cell::new("a")]));
1018+
table.set_titles(Row::new(vec![Cell::new("t1"), Cell::new("t2"), Cell::new("t3")]));
1019+
let out = "\
1020+
<table>\
1021+
<th><td style=\"text-align: left;\">t1</td><td style=\"text-align: left;\">t2</td><td style=\"text-align: left;\">t3</td></th>\
1022+
<tr><td style=\"text-align: left;\">a</td><td style=\"text-align: left;\">bc</td><td style=\"text-align: left;\">def</td></tr>\
1023+
<tr><td style=\"text-align: left;\">def</td><td style=\"text-align: left;\">bc</td><td style=\"text-align: left;\">a</td></tr>\
1024+
</table>";
1025+
let mut writer = StringWriter::new();
1026+
assert!(table.print_html(&mut writer).is_ok());
1027+
assert_eq!(writer.as_string().replace("\r\n", "\n"), out);
1028+
table.unset_titles();
1029+
let out = "\
1030+
<table>\
1031+
<tr><td style=\"text-align: left;\">a</td><td style=\"text-align: left;\">bc</td><td style=\"text-align: left;\">def</td></tr>\
1032+
<tr><td style=\"text-align: left;\">def</td><td style=\"text-align: left;\">bc</td><td style=\"text-align: left;\">a</td></tr>\
1033+
</table>";
1034+
let mut writer = StringWriter::new();
1035+
assert!(table.print_html(&mut writer).is_ok());
1036+
assert_eq!(writer.as_string().replace("\r\n", "\n"), out);
1037+
}
1038+
1039+
#[test]
1040+
fn table_html_colors() {
1041+
let mut table = Table::new();
1042+
table.add_row(Row::new(vec![
1043+
Cell::new("bold").style_spec("b"),
1044+
Cell::new("italic").style_spec("i"),
1045+
Cell::new("underline").style_spec("u"),
1046+
]));
1047+
table.add_row(Row::new(vec![
1048+
Cell::new("left").style_spec("l"),
1049+
Cell::new("center").style_spec("c"),
1050+
Cell::new("right").style_spec("r"),
1051+
]));
1052+
table.add_row(Row::new(vec![
1053+
Cell::new("red").style_spec("Fr"),
1054+
Cell::new("black").style_spec("Fd"),
1055+
Cell::new("yellow").style_spec("Fy"),
1056+
]));
1057+
table.add_row(Row::new(vec![
1058+
Cell::new("bright magenta on cyan").style_spec("FMBc"),
1059+
Cell::new("white on bright green").style_spec("FwBG"),
1060+
Cell::new("default on blue").style_spec("Bb"),
1061+
]));
1062+
table.set_titles(Row::new(vec![
1063+
Cell::new("span horizontal").style_spec("H3"),
1064+
]));
1065+
let out = "\
1066+
<table>\
1067+
<th><td colspan=\"3\" style=\"text-align: left;\">span horizontal</td></th>\
1068+
<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>\
1069+
<tr><td style=\"text-align: left;\">left</td><td style=\"text-align: center;\">center</td><td style=\"text-align: right;\">right</td></tr>\
1070+
<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>\
1071+
<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>\
1072+
</table>";
1073+
let mut writer = StringWriter::new();
1074+
assert!(table.print_html(&mut writer).is_ok());
1075+
assert_eq!(writer.as_string().replace("\r\n", "\n"), out);
1076+
}
9831077
}

src/row.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,21 @@ impl Row {
205205
-> Result<usize, Error> {
206206
self.__print(out, format, col_width, Cell::print_term)
207207
}
208+
209+
/// Print the row in HTML format to `out`.
210+
///
211+
/// If the row is has fewer columns than `col_num`, the row is padded with empty cells.
212+
pub fn print_html<T: Write + ?Sized>(&self, out: &mut T, col_num: usize) -> Result<(), Error> {
213+
let mut printed_columns = 0;
214+
for cell in self.iter() {
215+
printed_columns += cell.print_html(out)?;
216+
}
217+
// Pad with empty cells, if target width is not reached
218+
for _ in 0..col_num - printed_columns {
219+
Cell::default().print_html(out)?;
220+
}
221+
Ok(())
222+
}
208223
}
209224

210225
impl Default for Row {

src/utils.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
//! Internal only utilities
2+
use std::fmt;
23
use std::io::{Error, ErrorKind, Write};
34
use std::str;
45

@@ -105,6 +106,43 @@ pub fn display_width(text: &str) -> usize {
105106
width - hidden
106107
}
107108

109+
/// Wrapper struct which will emit the HTML-escaped version of the contained
110+
/// string when passed to a format string.
111+
pub struct HtmlEscape<'a>(pub &'a str);
112+
113+
impl<'a> fmt::Display for HtmlEscape<'a> {
114+
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
115+
// Because the internet is always right, turns out there's not that many
116+
// characters to escape: http://stackoverflow.com/questions/7381974
117+
let HtmlEscape(s) = *self;
118+
let pile_o_bits = s;
119+
let mut last = 0;
120+
for (i, ch) in s.bytes().enumerate() {
121+
match ch as char {
122+
'<' | '>' | '&' | '\'' | '"' => {
123+
fmt.write_str(&pile_o_bits[last.. i])?;
124+
let s = match ch as char {
125+
'>' => "&gt;",
126+
'<' => "&lt;",
127+
'&' => "&amp;",
128+
'\'' => "&#39;",
129+
'"' => "&quot;",
130+
_ => unreachable!()
131+
};
132+
fmt.write_str(s)?;
133+
last = i + 1;
134+
}
135+
_ => {}
136+
}
137+
}
138+
139+
if last < s.len() {
140+
fmt.write_str(&pile_o_bits[last..])?;
141+
}
142+
Ok(())
143+
}
144+
}
145+
108146
#[cfg(test)]
109147
mod tests {
110148
use super::*;

0 commit comments

Comments
 (0)