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