1use super::format::Alignment;
4use super::utils::{display_width, print_align, HtmlEscape};
5use super::{color, Attr, Terminal};
6use std::io::{Error, Write};
7use std::str::FromStr;
8use std::string::ToString;
9
10#[derive(Clone, Debug, Hash, PartialEq, Eq)]
15pub struct Cell {
16 content: Vec<String>,
17 width: usize,
18 align: Alignment,
19 style: Vec<Attr>,
20 hspan: usize,
21}
22
23impl Cell {
24 pub fn new_align(string: &str, align: Alignment) -> Cell {
27 let content: Vec<String> = string.lines().map(|x| x.to_string()).collect();
28 let mut width = 0;
29 for cont in &content {
30 let l = display_width(&cont[..]);
31 if l > width {
32 width = l;
33 }
34 }
35 Cell {
36 content,
37 width,
38 align,
39 style: Vec::new(),
40 hspan: 1,
41 }
42 }
43
44 pub fn new(string: &str) -> Cell {
47 Cell::new_align(string, Alignment::LEFT)
48 }
49
50 pub fn align(&mut self, align: Alignment) {
52 self.align = align;
53 }
54
55 pub fn style(&mut self, attr: Attr) {
57 self.style.push(attr);
58 }
59
60 pub fn with_style(mut self, attr: Attr) -> Cell {
62 self.style(attr);
63 self
64 }
65
66 pub fn with_hspan(mut self, hspan: usize) -> Cell {
68 self.set_hspan(hspan);
69 self
70 }
71
72 pub fn reset_style(&mut self) {
74 self.style.clear();
75 self.align(Alignment::LEFT);
76 }
77
78 pub fn style_spec(mut self, spec: &str) -> Cell {
116 self.reset_style();
117 let mut foreground = false;
118 let mut background = false;
119 let mut it = spec.chars().peekable();
120 while let Some(c) = it.next() {
121 if foreground || background {
122 let color = match c {
123 'r' => color::RED,
124 'R' => color::BRIGHT_RED,
125 'b' => color::BLUE,
126 'B' => color::BRIGHT_BLUE,
127 'g' => color::GREEN,
128 'G' => color::BRIGHT_GREEN,
129 'y' => color::YELLOW,
130 'Y' => color::BRIGHT_YELLOW,
131 'c' => color::CYAN,
132 'C' => color::BRIGHT_CYAN,
133 'm' => color::MAGENTA,
134 'M' => color::BRIGHT_MAGENTA,
135 'w' => color::WHITE,
136 'W' => color::BRIGHT_WHITE,
137 'd' => color::BLACK,
138 'D' => color::BRIGHT_BLACK,
139 _ => {
140 foreground = false;
142 background = false;
143 continue;
144 }
145 };
146 if foreground {
147 self.style(Attr::ForegroundColor(color));
148 } else if background {
149 self.style(Attr::BackgroundColor(color));
150 }
151 foreground = false;
152 background = false;
153 } else {
154 match c {
155 'F' => foreground = true,
156 'B' => background = true,
157 'b' => self.style(Attr::Bold),
158 'i' => self.style(Attr::Italic(true)),
159 'u' => self.style(Attr::Underline(true)),
160 'c' => self.align(Alignment::CENTER),
161 'l' => self.align(Alignment::LEFT),
162 'r' => self.align(Alignment::RIGHT),
163 'H' => {
164 let mut span_s = String::new();
165 while let Some('0'..='9') = it.peek() {
166 span_s.push(it.next().unwrap());
167 }
168 let span = usize::from_str(&span_s).unwrap();
169 self.set_hspan(span);
170 }
171 _ => { }
172 }
173 }
174 }
175 self
176 }
177
178 pub(crate) fn get_height(&self) -> usize {
181 self.content.len()
182 }
183
184 pub(crate) fn get_width(&self) -> usize {
187 self.width
188 }
189
190 pub fn set_hspan(&mut self, hspan: usize) {
192 self.hspan = if hspan == 0 { 1 } else { hspan };
193 }
194
195 pub fn get_hspan(&self) -> usize {
197 self.hspan
198 }
199
200 pub fn get_content(&self) -> String {
202 self.content.join("\n")
203 }
204
205 pub(crate) fn print<T: Write + ?Sized>(
211 &self,
212 out: &mut T,
213 idx: usize,
214 col_width: usize,
215 skip_right_fill: bool,
216 ) -> Result<(), Error> {
217 let c = self.content.get(idx).map(|s| s.as_ref()).unwrap_or("");
218 print_align(out, self.align, c, ' ', col_width, skip_right_fill)
219 }
220
221 pub(crate) fn print_term<T: Terminal + ?Sized>(
224 &self,
225 out: &mut T,
226 idx: usize,
227 col_width: usize,
228 skip_right_fill: bool,
229 ) -> Result<(), Error> {
230 for a in &self.style {
231 match out.attr(*a) {
232 Ok(..) | Err(::term::Error::NotSupported) | Err(::term::Error::ColorOutOfRange) => {
233 } Err(e) => return Err(term_error_to_io_error(e)),
235 };
236 }
237 self.print(out, idx, col_width, skip_right_fill)?;
238 match out.reset() {
239 Ok(..) | Err(::term::Error::NotSupported) | Err(::term::Error::ColorOutOfRange) => {
240 Ok(())
241 }
242 Err(e) => Err(term_error_to_io_error(e)),
243 }
244 }
245
246 pub fn print_html<T: Write + ?Sized>(&self, out: &mut T) -> Result<usize, Error> {
248 fn color2hex(color: color::Color) -> &'static str {
250 match color {
251 color::BLACK => "#000000",
252 color::RED => "#aa0000",
253 color::GREEN => "#00aa00",
254 color::YELLOW => "#aa5500",
255 color::BLUE => "#0000aa",
256 color::MAGENTA => "#aa00aa",
257 color::CYAN => "#00aaaa",
258 color::WHITE => "#aaaaaa",
259 color::BRIGHT_BLACK => "#555555",
260 color::BRIGHT_RED => "#ff5555",
261 color::BRIGHT_GREEN => "#55ff55",
262 color::BRIGHT_YELLOW => "#ffff55",
263 color::BRIGHT_BLUE => "#5555ff",
264 color::BRIGHT_MAGENTA => "#ff55ff",
265 color::BRIGHT_CYAN => "#55ffff",
266 color::BRIGHT_WHITE => "#ffffff",
267
268 _ => "#000000",
270 }
271 }
272
273 let colspan = if self.hspan > 1 {
274 format!(" colspan=\"{}\"", self.hspan)
275 } else {
276 String::new()
277 };
278
279 let mut styles = String::new();
281 for style in &self.style {
282 match style {
283 Attr::Bold => styles += "font-weight: bold;",
284 Attr::Italic(true) => styles += "font-style: italic;",
285 Attr::Underline(true) => styles += "text-decoration: underline;",
286 Attr::ForegroundColor(c) => {
287 styles += "color: ";
288 styles += color2hex(*c);
289 styles += ";";
290 }
291 Attr::BackgroundColor(c) => {
292 styles += "background-color: ";
293 styles += color2hex(*c);
294 styles += ";";
295 }
296 _ => {}
297 }
298 }
299 match self.align {
301 Alignment::LEFT => styles += "text-align: left;",
302 Alignment::CENTER => styles += "text-align: center;",
303 Alignment::RIGHT => styles += "text-align: right;",
304 }
305
306 let content = self.content.join("<br />");
307 out.write_all(
308 format!(
309 "<td{1} style=\"{2}\">{0}</td>",
310 HtmlEscape(&content),
311 colspan,
312 styles
313 )
314 .as_bytes(),
315 )?;
316 Ok(self.hspan)
317 }
318}
319
320fn term_error_to_io_error(te: ::term::Error) -> Error {
321 match te {
322 ::term::Error::Io(why) => why,
323 _ => Error::new(::std::io::ErrorKind::Other, te),
324 }
325}
326
327impl<'a, T: ToString> From<&'a T> for Cell {
328 fn from(f: &T) -> Cell {
329 Cell::new(&f.to_string())
330 }
331}
332
333impl ToString for Cell {
334 fn to_string(&self) -> String {
335 self.get_content()
336 }
337}
338
339impl Default for Cell {
340 fn default() -> Cell {
342 Cell {
343 content: vec!["".to_string(); 1],
344 width: 0,
345 align: Alignment::LEFT,
346 style: Vec::new(),
347 hspan: 1,
348 }
349 }
350}
351
352#[macro_export]
380macro_rules! cell {
381 () => {
382 $crate::Cell::default()
383 };
384 ($value:expr) => {
385 $crate::Cell::new(&$value.to_string())
386 };
387 ($style:ident -> $value:expr) => {
388 $crate::cell!($value).style_spec(stringify!($style))
389 };
390}
391
392#[cfg(test)]
393mod tests {
394 use super::Cell;
395 use crate::format::Alignment;
396 use crate::utils::StringWriter;
397 use term::{color, Attr};
398
399 #[test]
400 fn get_content() {
401 let cell = Cell::new("test");
402 assert_eq!(cell.get_content(), "test");
403 }
404
405 #[test]
406 fn print_ascii() {
407 let ascii_cell = Cell::new("hello");
408 assert_eq!(ascii_cell.get_width(), 5);
409
410 let mut out = StringWriter::new();
411 let _ = ascii_cell.print(&mut out, 0, 10, false);
412 assert_eq!(out.as_string(), "hello ");
413 }
414
415 #[test]
416 fn print_unicode() {
417 let unicode_cell = Cell::new("привет");
418 assert_eq!(unicode_cell.get_width(), 6);
419
420 let mut out = StringWriter::new();
421 let _ = unicode_cell.print(&mut out, 0, 10, false);
422 assert_eq!(out.as_string(), "привет ");
423 }
424
425 #[test]
426 fn print_cjk() {
427 let unicode_cell = Cell::new("由系统自动更新");
428 assert_eq!(unicode_cell.get_width(), 14);
429 let mut out = StringWriter::new();
430 let _ = unicode_cell.print(&mut out, 0, 20, false);
431 assert_eq!(out.as_string(), "由系统自动更新 ");
432 }
433
434 #[test]
435 fn print_ascii_html() {
436 let ascii_cell = Cell::new("hello");
437 assert_eq!(ascii_cell.get_width(), 5);
438
439 let mut out = StringWriter::new();
440 let _ = ascii_cell.print_html(&mut out);
441 assert_eq!(
442 out.as_string(),
443 r#"<td style="text-align: left;">hello</td>"#
444 );
445 }
446
447 #[test]
448 fn print_html_special_chars() {
449 let ascii_cell = Cell::new("<abc\">&'");
450
451 let mut out = StringWriter::new();
452 let _ = ascii_cell.print_html(&mut out);
453 assert_eq!(
454 out.as_string(),
455 r#"<td style="text-align: left;"><abc">&'</td>"#
456 );
457 }
458
459 #[test]
460 fn align_left() {
461 let cell = Cell::new_align("test", Alignment::LEFT);
462 let mut out = StringWriter::new();
463 let _ = cell.print(&mut out, 0, 10, false);
464 assert_eq!(out.as_string(), "test ");
465 }
466
467 #[test]
468 fn align_center() {
469 let cell = Cell::new_align("test", Alignment::CENTER);
470 let mut out = StringWriter::new();
471 let _ = cell.print(&mut out, 0, 10, false);
472 assert_eq!(out.as_string(), " test ");
473 }
474
475 #[test]
476 fn align_right() {
477 let cell = Cell::new_align("test", Alignment::RIGHT);
478 let mut out = StringWriter::new();
479 let _ = cell.print(&mut out, 0, 10, false);
480 assert_eq!(out.as_string(), " test");
481 }
482
483 #[test]
484 fn style_spec() {
485 let mut cell = Cell::new("test").style_spec("FrBBbuic");
486 assert_eq!(cell.style.len(), 5);
487 assert!(cell.style.contains(&Attr::Underline(true)));
488 assert!(cell.style.contains(&Attr::Italic(true)));
489 assert!(cell.style.contains(&Attr::Bold));
490 assert!(cell.style.contains(&Attr::ForegroundColor(color::RED)));
491 assert!(cell
492 .style
493 .contains(&Attr::BackgroundColor(color::BRIGHT_BLUE)));
494 assert_eq!(cell.align, Alignment::CENTER);
495
496 cell = cell.style_spec("FDBwr");
497 assert_eq!(cell.style.len(), 2);
498 assert!(cell
499 .style
500 .contains(&Attr::ForegroundColor(color::BRIGHT_BLACK)));
501 assert!(cell.style.contains(&Attr::BackgroundColor(color::WHITE)));
502 assert_eq!(cell.align, Alignment::RIGHT);
503
504 cell = cell.clone();
506 cell = cell.style_spec("FzBr");
507 assert!(cell.style.contains(&Attr::BackgroundColor(color::RED)));
508 assert_eq!(cell.style.len(), 1);
509 cell = cell.style_spec("zzz");
510 assert!(cell.style.is_empty());
511 assert_eq!(cell.get_hspan(), 1);
512 cell = cell.style_spec("FDBwH03r");
513 assert_eq!(cell.get_hspan(), 3);
514 }
515
516 #[test]
517 fn reset_style() {
518 let mut cell = Cell::new("test")
519 .with_style(Attr::ForegroundColor(color::BRIGHT_BLACK))
520 .with_style(Attr::BackgroundColor(color::WHITE));
521 cell.align(Alignment::RIGHT);
522
523 assert_eq!(cell.style.len(), 2);
525 assert_eq!(cell.align, Alignment::RIGHT);
526 cell.reset_style();
527 assert_eq!(cell.style.len(), 0);
528 assert_eq!(cell.align, Alignment::LEFT);
529 }
530
531 #[test]
532 fn default_empty_cell() {
533 let cell = Cell::default();
534 assert_eq!(cell.align, Alignment::LEFT);
535 assert!(cell.style.is_empty());
536 assert_eq!(cell.get_content(), "");
537 assert_eq!(cell.to_string(), "");
538 assert_eq!(cell.get_height(), 1);
539 assert_eq!(cell.get_width(), 0);
540 }
541}