tabled/features/style/style.rs
1//! This module contains a list of primitives which can be applied to change [`Table`] style.
2//!
3//! ## [`Style`]
4//!
5//! It is responsible for a table border style.
6//! An individual cell border can be set by [`Border`].
7//!
8//! ### Example
9//!
10//! ```
11//! use tabled::{Table, Style};
12//!
13//! let data = vec!["Hello", "2022"];
14//! let mut table = Table::new(&data);
15//! table.with(Style::psql());
16//!
17//! assert_eq!(
18//! table.to_string(),
19//! concat!(
20//! " &str \n",
21//! "-------\n",
22//! " Hello \n",
23//! " 2022 ",
24//! )
25//! )
26//! ```
27//!
28//! ## [`BorderText`]
29//!
30//! It's used to override a border with a custom text.
31//!
32//! ### Example
33//!
34//! ```
35//! use tabled::{Table, BorderText, Style};
36//!
37//! let data = vec!["Hello", "2022"];
38//! let table = Table::new(&data)
39//! .with(Style::psql())
40//! .with(BorderText::new(1, "Santa"))
41//! .to_string();
42//!
43//! assert_eq!(
44//! table,
45//! concat!(
46//! " &str \n",
47//! "Santa--\n",
48//! " Hello \n",
49//! " 2022 ",
50//! )
51//! )
52//! ```
53//!
54//! ## [`Border`]
55//!
56//! [`Border`] can be used to modify cell's borders.
57//!
58//! It's possible to set a collored border when `color` feature is on.
59//!
60//! ### Example
61//!
62//! ```
63//! use tabled::{Table, Style, Modify, object::Cell};
64//!
65//! let data = vec!["Hello", "2022"];
66//! let table = Table::new(&data)
67//! .with(Style::psql())
68//! .with(Modify::new(Cell(0, 0)).with(Style::modern().get_frame()))
69//! .to_string();
70//!
71//! assert_eq!(
72//! table,
73//! concat!(
74//! "┌───────┐\n",
75//! "│ &str │\n",
76//! "└───────┘\n",
77//! " Hello \n",
78//! " 2022 ",
79//! )
80//! )
81//! ```
82//!
83//! ## [`RawStyle`]
84//!
85//! A different representatio of [`Style`].
86//! With no checks in place.
87//!
88//! It also contains a list of types to support colors.
89//!
90//! [`Table`]: crate::Table
91//! [`BorderText`]: crate::border_text::BorderText
92//! [`RawStyle`]: crate::raw_style::RawStyle
93
94use std::marker::PhantomData;
95
96use papergrid::{records::Records, Borders};
97
98use crate::{style::StyleCorrectSpan, Border, Table, TableOption};
99
100use super::{HorizontalLine, Line, VerticalLine};
101
102/// Style is represents a theme of a [`Table`].
103///
104/// It tries to limit an controlling a valid state of it.
105/// It doesn't allow to call method [`Style::top_left_corner`] unless [`Style::left`] and [`Style::top`] is set.
106///
107/// You can turn [`Style`] into [`RawStyle`] to have more controll using [`Into`] implementation.
108///
109/// # Example
110///
111/// ```rust,no_run
112/// use tabled::{Table, Style};
113///
114/// let style = Style::ascii()
115/// .bottom('*')
116/// .inner_intersection(' ');
117///
118/// let data = vec!["Hello", "2021"];
119/// let table = Table::new(&data).with(style).to_string();
120///
121/// println!("{}", table);
122/// ```
123///
124/// [`Table`]: crate::Table
125/// [`RawStyle`]: crate::style::RawStyle
126#[derive(Debug, Clone)]
127pub struct Style<T, B, L, R, H, V, HLines = HLineArray<0>, VLines = VLineArray<0>> {
128 pub(crate) borders: Borders<char>,
129 pub(crate) horizontals: HLines,
130 pub(crate) verticals: VLines,
131 _top: PhantomData<T>,
132 _bottom: PhantomData<B>,
133 _left: PhantomData<L>,
134 _right: PhantomData<R>,
135 _horizontal: PhantomData<H>,
136 _vertical: PhantomData<V>,
137}
138
139type HLineArray<const N: usize> = [HorizontalLine; N];
140
141type VLineArray<const N: usize> = [VerticalLine; N];
142
143/// A marker struct which is used in [`Style`].
144#[derive(Debug, Clone)]
145pub struct On;
146
147impl Style<(), (), (), (), (), (), (), ()> {
148 /// This style is a style with no styling options on,
149 ///
150 /// ```text
151 /// id destribution link
152 /// 0 Fedora https://getfedora.org/
153 /// 2 OpenSUSE https://www.opensuse.org/
154 /// 3 Endeavouros https://endeavouros.com/
155 /// ```
156 ///
157 /// Note: The cells in the example have 1-left and 1-right indent.
158 ///
159 /// This style can be used as a base style to build a custom one.
160 ///
161 /// ```rust,no_run
162 /// # use tabled::Style;
163 /// let style = Style::empty()
164 /// .top('*')
165 /// .bottom('*')
166 /// .vertical('#')
167 /// .bottom_intersection('^')
168 /// .top_intersection('*');
169 /// ```
170 pub const fn empty() -> Style<(), (), (), (), (), ()> {
171 Style::new(
172 create_borders(
173 Line::empty(),
174 Line::empty(),
175 Line::empty(),
176 None,
177 None,
178 None,
179 ),
180 [],
181 [],
182 )
183 }
184
185 /// This style is analog of `empty` but with a vertical space(' ') line.
186 ///
187 /// ```text
188 /// id destribution link
189 /// 0 Fedora https://getfedora.org/
190 /// 2 OpenSUSE https://www.opensuse.org/
191 /// 3 Endeavouros https://endeavouros.com/
192 /// ```
193 pub const fn blank() -> Style<(), (), (), (), (), On> {
194 Style::new(
195 create_borders(
196 Line::empty(),
197 Line::empty(),
198 Line::empty(),
199 None,
200 None,
201 Some(' '),
202 ),
203 [],
204 [],
205 )
206 }
207
208 /// This is a style which relays only on ASCII charset.
209 ///
210 /// It has horizontal and vertical lines.
211 ///
212 /// ```text
213 /// +----+--------------+---------------------------+
214 /// | id | destribution | link |
215 /// +----+--------------+---------------------------+
216 /// | 0 | Fedora | https://getfedora.org/ |
217 /// +----+--------------+---------------------------+
218 /// | 2 | OpenSUSE | https://www.opensuse.org/ |
219 /// +----+--------------+---------------------------+
220 /// | 3 | Endeavouros | https://endeavouros.com/ |
221 /// +----+--------------+---------------------------+
222 /// ```
223 pub const fn ascii() -> Style<On, On, On, On, On, On> {
224 Style::new(
225 create_borders(
226 Line::full('-', '+', '+', '+'),
227 Line::full('-', '+', '+', '+'),
228 Line::full('-', '+', '+', '+'),
229 Some('|'),
230 Some('|'),
231 Some('|'),
232 ),
233 [],
234 [],
235 )
236 }
237
238 /// `psql` style looks like a table style `PostgreSQL` uses.
239 ///
240 /// It has only 1 horizontal line which splits header.
241 /// And no left and right vertical lines.
242 ///
243 /// ```text
244 /// id | destribution | link
245 /// ----+--------------+---------------------------
246 /// 0 | Fedora | https://getfedora.org/
247 /// 2 | OpenSUSE | https://www.opensuse.org/
248 /// 3 | Endeavouros | https://endeavouros.com/
249 /// ```
250 pub const fn psql() -> Style<(), (), (), (), (), On, HLineArray<1>> {
251 Style::new(
252 create_borders(
253 Line::empty(),
254 Line::empty(),
255 Line::empty(),
256 None,
257 None,
258 Some('|'),
259 ),
260 [HorizontalLine::new(1, Line::empty())
261 .main(Some('-'))
262 .intersection(Some('+'))],
263 [],
264 )
265 }
266
267 /// `markdown` style mimics a `Markdown` table style.
268 ///
269 /// ```text
270 /// | id | destribution | link |
271 /// |----|--------------|---------------------------|
272 /// | 0 | Fedora | https://getfedora.org/ |
273 /// | 2 | OpenSUSE | https://www.opensuse.org/ |
274 /// | 3 | Endeavouros | https://endeavouros.com/ |
275 /// ```
276 pub const fn markdown() -> Style<(), (), On, On, (), On, HLineArray<1>> {
277 Style::new(
278 create_borders(
279 Line::empty(),
280 Line::empty(),
281 Line::empty(),
282 Some('|'),
283 Some('|'),
284 Some('|'),
285 ),
286 [HorizontalLine::new(1, Line::full('-', '|', '|', '|'))],
287 [],
288 )
289 }
290
291 /// This style is analog of [`Style::ascii`] which uses UTF-8 charset.
292 ///
293 /// It has vertical and horizontal split lines.
294 ///
295 /// ```text
296 /// ┌────┬──────────────┬───────────────────────────┐
297 /// │ id │ destribution │ link │
298 /// ├────┼──────────────┼───────────────────────────┤
299 /// │ 0 │ Fedora │ https://getfedora.org/ │
300 /// ├────┼──────────────┼───────────────────────────┤
301 /// │ 2 │ OpenSUSE │ https://www.opensuse.org/ │
302 /// ├────┼──────────────┼───────────────────────────┤
303 /// │ 3 │ Endeavouros │ https://endeavouros.com/ │
304 /// └────┴──────────────┴───────────────────────────┘
305 /// ```
306 pub const fn modern() -> Style<On, On, On, On, On, On> {
307 Style::new(
308 create_borders(
309 Line::full('─', '┬', '┌', '┐'),
310 Line::full('─', '┴', '└', '┘'),
311 Line::full('─', '┼', '├', '┤'),
312 Some('│'),
313 Some('│'),
314 Some('│'),
315 ),
316 [],
317 [],
318 )
319 }
320
321 /// This style looks like a [`Style::modern`] but without horozizontal lines except a header.
322 ///
323 /// Beware: It uses UTF-8 characters.
324 ///
325 /// ```text
326 /// ┌────┬──────────────┬───────────────────────────┐
327 /// │ id │ destribution │ link │
328 /// ├────┼──────────────┼───────────────────────────┤
329 /// │ 0 │ Fedora │ https://getfedora.org/ │
330 /// │ 2 │ OpenSUSE │ https://www.opensuse.org/ │
331 /// │ 3 │ Endeavouros │ https://endeavouros.com/ │
332 /// └────┴──────────────┴───────────────────────────┘
333 /// ```
334 pub const fn sharp() -> Style<On, On, On, On, (), On, HLineArray<1>> {
335 Style::new(
336 create_borders(
337 Line::full('─', '┬', '┌', '┐'),
338 Line::full('─', '┴', '└', '┘'),
339 Line::empty(),
340 Some('│'),
341 Some('│'),
342 Some('│'),
343 ),
344 [HorizontalLine::new(1, Line::full('─', '┼', '├', '┤'))],
345 [],
346 )
347 }
348
349 /// This style looks like a [`Style::sharp`] but with rounded corners.
350 ///
351 /// Beware: It uses UTF-8 characters.
352 ///
353 /// ```text
354 /// ╭────┬──────────────┬───────────────────────────╮
355 /// │ id │ destribution │ link │
356 /// ├────┼──────────────┼───────────────────────────┤
357 /// │ 0 │ Fedora │ https://getfedora.org/ │
358 /// │ 2 │ OpenSUSE │ https://www.opensuse.org/ │
359 /// │ 3 │ Endeavouros │ https://endeavouros.com/ │
360 /// ╰────┴──────────────┴───────────────────────────╯
361 /// ```
362 pub const fn rounded() -> Style<On, On, On, On, (), On, HLineArray<1>> {
363 Style::new(
364 create_borders(
365 Line::full('─', '┬', '╭', '╮'),
366 Line::full('─', '┴', '╰', '╯'),
367 Line::empty(),
368 Some('│'),
369 Some('│'),
370 Some('│'),
371 ),
372 [HorizontalLine::new(1, Line::full('─', '┼', '├', '┤'))],
373 [],
374 )
375 }
376
377 /// This style uses a chars which resembles '2 lines'.
378 ///
379 /// Beware: It uses UTF8 characters.
380 ///
381 /// ```text
382 /// ╔════╦══════════════╦═══════════════════════════╗
383 /// ║ id ║ destribution ║ link ║
384 /// ╠════╬══════════════╬═══════════════════════════╣
385 /// ║ 0 ║ Fedora ║ https://getfedora.org/ ║
386 /// ╠════╬══════════════╬═══════════════════════════╣
387 /// ║ 2 ║ OpenSUSE ║ https://www.opensuse.org/ ║
388 /// ╠════╬══════════════╬═══════════════════════════╣
389 /// ║ 3 ║ Endeavouros ║ https://endeavouros.com/ ║
390 /// ╚════╩══════════════╩═══════════════════════════╝
391 /// ```
392 pub const fn extended() -> Style<On, On, On, On, On, On> {
393 Style::new(
394 create_borders(
395 Line::full('═', '╦', '╔', '╗'),
396 Line::full('═', '╩', '╚', '╝'),
397 Line::full('═', '╬', '╠', '╣'),
398 Some('║'),
399 Some('║'),
400 Some('║'),
401 ),
402 [],
403 [],
404 )
405 }
406
407 /// This is a style uses only '.' and ':' chars.
408 /// It has a vertical and horizontal split lines.
409 ///
410 /// ```text
411 /// .................................................
412 /// : id : destribution : link :
413 /// :....:..............:...........................:
414 /// : 0 : Fedora : https://getfedora.org/ :
415 /// :....:..............:...........................:
416 /// : 2 : OpenSUSE : https://www.opensuse.org/ :
417 /// :....:..............:...........................:
418 /// : 3 : Endeavouros : https://endeavouros.com/ :
419 /// :....:..............:...........................:
420 /// ```
421 pub const fn dots() -> Style<On, On, On, On, On, On> {
422 Style::new(
423 create_borders(
424 Line::full('.', '.', '.', '.'),
425 Line::full('.', ':', ':', ':'),
426 Line::full('.', ':', ':', ':'),
427 Some(':'),
428 Some(':'),
429 Some(':'),
430 ),
431 [],
432 [],
433 )
434 }
435
436 /// This style is one of table views in `ReStructuredText`.
437 ///
438 /// ```text
439 /// ==== ============== ===========================
440 /// id destribution link
441 /// ==== ============== ===========================
442 /// 0 Fedora https://getfedora.org/
443 /// 2 OpenSUSE https://www.opensuse.org/
444 /// 3 Endeavouros https://endeavouros.com/
445 /// ==== ============== ===========================
446 /// ```
447 pub const fn re_structured_text() -> Style<On, On, (), (), (), On, HLineArray<1>> {
448 Style::new(
449 create_borders(
450 Line::new(Some('='), Some(' '), None, None),
451 Line::new(Some('='), Some(' '), None, None),
452 Line::empty(),
453 None,
454 None,
455 Some(' '),
456 ),
457 [HorizontalLine::new(
458 1,
459 Line::new(Some('='), Some(' '), None, None),
460 )],
461 [],
462 )
463 }
464
465 /// This is a theme analog of [`Style::rounded`], but in using ascii charset and
466 /// with no horizontal lines.
467 ///
468 /// ```text
469 /// .-----------------------------------------------.
470 /// | id | destribution | link |
471 /// | 0 | Fedora | https://getfedora.org/ |
472 /// | 2 | OpenSUSE | https://www.opensuse.org/ |
473 /// | 3 | Endeavouros | https://endeavouros.com/ |
474 /// '-----------------------------------------------'
475 /// ```
476 pub const fn ascii_rounded() -> Style<On, On, On, On, (), On> {
477 Style::new(
478 create_borders(
479 Line::full('-', '-', '.', '.'),
480 Line::full('-', '-', '\'', '\''),
481 Line::empty(),
482 Some('|'),
483 Some('|'),
484 Some('|'),
485 ),
486 [],
487 [],
488 )
489 }
490
491 /// Try to fix the style when table contains spans.
492 ///
493 /// By default [`Style`] doesn't implies any logic to better render split lines when
494 /// [`Span`] is used.
495 ///
496 /// So this function can be used to set the split lines in regard of spans used.
497 ///
498 /// # Example
499 ///
500 /// ```
501 /// use tabled::{TableIteratorExt, Style, Modify, format::Format, Span, object::Cell};
502 ///
503 /// let data = vec![
504 /// ("09", "June", "2022"),
505 /// ("10", "July", "2022"),
506 /// ];
507 ///
508 /// let mut table = data.table();
509 /// table
510 /// .with(
511 /// Modify::new(Cell(0, 0))
512 /// .with(Format::new(|_| String::from("date")))
513 /// .with(Span::column(3))
514 /// );
515 ///
516 /// assert_eq!(
517 /// table.to_string(),
518 /// concat!(
519 /// "+----+------+------+\n",
520 /// "| date |\n",
521 /// "+----+------+------+\n",
522 /// "| 09 | June | 2022 |\n",
523 /// "+----+------+------+\n",
524 /// "| 10 | July | 2022 |\n",
525 /// "+----+------+------+",
526 /// )
527 /// );
528 ///
529 /// table.with(Style::correct_spans());
530 ///
531 /// assert_eq!(
532 /// table.to_string(),
533 /// concat!(
534 /// "+------------------+\n",
535 /// "| date |\n",
536 /// "+----+------+------+\n",
537 /// "| 09 | June | 2022 |\n",
538 /// "+----+------+------+\n",
539 /// "| 10 | July | 2022 |\n",
540 /// "+----+------+------+",
541 /// )
542 /// );
543 /// ```
544 ///
545 /// [`Span`]: crate::Span
546 pub const fn correct_spans() -> StyleCorrectSpan {
547 StyleCorrectSpan
548 }
549}
550
551impl<T, B, L, R, H, V, HLines, VLines> Style<T, B, L, R, H, V, HLines, VLines> {
552 /// Frame function returns a frame as a border.
553 ///
554 /// # Example
555 ///
556 /// ```
557 /// use tabled::{Table, Style, Highlight, object::Rows};
558 ///
559 /// let data = [["10:52:19", "Hello"], ["10:52:20", "World"]];
560 /// let mut table = Table::new(data);
561 /// table.with(Highlight::new(Rows::first(), Style::modern().get_frame()));
562 ///
563 /// assert_eq!(
564 /// table.to_string(),
565 /// concat!(
566 /// "┌──────────────────┐\n",
567 /// "│ 0 | 1 │\n",
568 /// "└──────────────────┘\n",
569 /// "| 10:52:19 | Hello |\n",
570 /// "+----------+-------+\n",
571 /// "| 10:52:20 | World |\n",
572 /// "+----------+-------+",
573 /// )
574 /// );
575 /// ```
576 pub const fn get_frame(&self) -> Border {
577 Border::new_raw(Some(papergrid::Border {
578 top: self.borders.top,
579 bottom: self.borders.bottom,
580 left: self.borders.vertical_left,
581 right: self.borders.vertical_right,
582 left_top_corner: self.borders.top_left,
583 right_top_corner: self.borders.top_right,
584 left_bottom_corner: self.borders.bottom_left,
585 right_bottom_corner: self.borders.bottom_right,
586 }))
587 }
588
589 /// Get a [`Style`]'s default horizontal line.
590 ///
591 /// It doesn't return an overloaded line via [`Style::horizontals`].
592 ///
593 /// # Example
594 ///
595 /// ```
596 /// use tabled::{style::{Style, HorizontalLine, Line}, TableIteratorExt};
597 ///
598 /// let table = (0..3)
599 /// .map(|i| ("Hello", "World", i))
600 /// .table()
601 /// .with(Style::ascii().off_horizontal().horizontals([HorizontalLine::new(1, Style::modern().get_horizontal())]))
602 /// .to_string();
603 ///
604 /// assert_eq!(
605 /// table,
606 /// concat!(
607 /// "+-------+-------+-----+\n",
608 /// "| &str | &str | i32 |\n",
609 /// "├───────┼───────┼─────┤\n",
610 /// "| Hello | World | 0 |\n",
611 /// "| Hello | World | 1 |\n",
612 /// "| Hello | World | 2 |\n",
613 /// "+-------+-------+-----+",
614 /// )
615 /// )
616 /// ```
617 pub const fn get_horizontal(&self) -> Line {
618 Line::new(
619 self.borders.horizontal,
620 self.borders.intersection,
621 self.borders.horizontal_left,
622 self.borders.horizontal_right,
623 )
624 }
625
626 /// Get a [`Style`]'s default horizontal line.
627 ///
628 /// It doesn't return an overloaded line via [`Style::verticals`].
629 ///
630 /// # Example
631 ///
632 /// ```
633 /// use tabled::{style::{Style, VerticalLine, Line}, TableIteratorExt};
634 ///
635 /// let table = (0..3)
636 /// .map(|i| ("Hello", "World", i))
637 /// .table()
638 /// .with(Style::ascii().off_horizontal().verticals([VerticalLine::new(1, Style::modern().get_vertical())]))
639 /// .to_string();
640 ///
641 /// assert_eq!(
642 /// table,
643 /// concat!(
644 /// "+-------┬-------+-----+\n",
645 /// "| &str │ &str | i32 |\n",
646 /// "| Hello │ World | 0 |\n",
647 /// "| Hello │ World | 1 |\n",
648 /// "| Hello │ World | 2 |\n",
649 /// "+-------┴-------+-----+",
650 /// )
651 /// )
652 /// ```
653 pub const fn get_vertical(&self) -> Line {
654 Line::new(
655 self.borders.vertical,
656 self.borders.intersection,
657 self.borders.top_intersection,
658 self.borders.bottom_intersection,
659 )
660 }
661
662 /// Sets a top border.
663 ///
664 /// Any corners and intersections which were set will be overridden.
665 pub fn top(mut self, c: char) -> Style<On, B, L, R, H, V, HLines, VLines>
666 where
667 for<'a> &'a mut VLines: IntoIterator<Item = &'a mut VerticalLine>,
668 {
669 self.borders.top = Some(c);
670
671 if self.borders.has_left() {
672 self.borders.top_left = Some(c);
673 }
674
675 if self.borders.has_right() {
676 self.borders.top_right = Some(c);
677 }
678
679 if self.borders.has_vertical() {
680 self.borders.top_intersection = Some(c);
681 }
682
683 for vl in &mut self.verticals {
684 if let Some(line) = &mut vl.line {
685 line.connector1 = Some(c);
686 }
687 }
688
689 Style::new(self.borders, self.horizontals, self.verticals)
690 }
691
692 /// Sets a bottom border.
693 ///
694 /// Any corners and intersections which were set will be overridden.
695 pub fn bottom(mut self, c: char) -> Style<T, On, L, R, H, V, HLines, VLines>
696 where
697 for<'a> &'a mut VLines: IntoIterator<Item = &'a mut VerticalLine>,
698 {
699 self.borders.bottom = Some(c);
700
701 if self.borders.has_left() {
702 self.borders.bottom_left = Some(c);
703 }
704
705 if self.borders.has_right() {
706 self.borders.bottom_right = Some(c);
707 }
708
709 if self.borders.has_vertical() {
710 self.borders.bottom_intersection = Some(c);
711 }
712
713 for vl in &mut self.verticals {
714 if let Some(line) = &mut vl.line {
715 line.connector2 = Some(c);
716 }
717 }
718
719 Style::new(self.borders, self.horizontals, self.verticals)
720 }
721
722 /// Sets a left border.
723 ///
724 /// Any corners and intersections which were set will be overridden.
725 pub fn left(mut self, c: char) -> Style<T, B, On, R, H, V, HLines, VLines>
726 where
727 for<'a> &'a mut HLines: IntoIterator<Item = &'a mut HorizontalLine>,
728 {
729 self.borders.vertical_left = Some(c);
730
731 if self.borders.has_top() {
732 self.borders.top_left = Some(c);
733 }
734
735 if self.borders.has_bottom() {
736 self.borders.bottom_left = Some(c);
737 }
738
739 if self.borders.has_horizontal() {
740 self.borders.horizontal_left = Some(c);
741 }
742
743 for hl in &mut self.horizontals {
744 if let Some(line) = &mut hl.line {
745 line.connector1 = Some(c);
746 }
747 }
748
749 Style::new(self.borders, self.horizontals, self.verticals)
750 }
751
752 /// Sets a right border.
753 ///
754 /// Any corners and intersections which were set will be overridden.
755 pub fn right(mut self, c: char) -> Style<T, B, L, On, H, V, HLines, VLines>
756 where
757 for<'a> &'a mut HLines: IntoIterator<Item = &'a mut HorizontalLine>,
758 {
759 self.borders.vertical_right = Some(c);
760
761 if self.borders.has_top() {
762 self.borders.top_right = Some(c);
763 }
764
765 if self.borders.has_bottom() {
766 self.borders.bottom_right = Some(c);
767 }
768
769 if self.borders.has_horizontal() {
770 self.borders.horizontal_right = Some(c);
771 }
772
773 for hl in &mut self.horizontals {
774 if let Some(line) = &mut hl.line {
775 line.connector2 = Some(c);
776 }
777 }
778
779 Style::new(self.borders, self.horizontals, self.verticals)
780 }
781
782 /// Sets a horizontal split line.
783 ///
784 /// Any corners and intersections which were set will be overridden.
785 pub fn horizontal(mut self, c: char) -> Style<T, B, L, R, On, V, HLines, VLines>
786 where
787 for<'a> &'a mut VLines: IntoIterator<Item = &'a mut VerticalLine>,
788 {
789 self.borders.horizontal = Some(c);
790
791 if self.borders.has_vertical() {
792 self.borders.intersection = Some(c);
793 }
794
795 if self.borders.has_left() {
796 self.borders.horizontal_left = Some(c);
797 }
798
799 if self.borders.has_right() {
800 self.borders.horizontal_right = Some(c);
801 }
802
803 for vl in &mut self.verticals {
804 if let Some(line) = &mut vl.line {
805 line.intersection = Some(c);
806 }
807 }
808
809 Style::new(self.borders, self.horizontals, self.verticals)
810 }
811
812 /// Sets a vertical split line.
813 ///
814 /// Any corners and intersections which were set will be overridden.
815 pub fn vertical(mut self, c: char) -> Style<T, B, L, R, H, On, HLines, VLines>
816 where
817 for<'a> &'a mut HLines: IntoIterator<Item = &'a mut HorizontalLine>,
818 {
819 self.borders.vertical = Some(c);
820
821 if self.borders.has_horizontal() {
822 self.borders.intersection = Some(c);
823 }
824
825 if self.borders.has_top() {
826 self.borders.top_intersection = Some(c);
827 }
828
829 if self.borders.has_bottom() {
830 self.borders.bottom_intersection = Some(c);
831 }
832
833 for hl in &mut self.horizontals {
834 if let Some(line) = &mut hl.line {
835 line.intersection = Some(c);
836 }
837 }
838
839 Style::new(self.borders, self.horizontals, self.verticals)
840 }
841
842 /// Set border horizontal lines.
843 ///
844 /// # Example
845 ///
846 /// ```
847 /// use tabled::{style::{Style, HorizontalLine, Line}, TableIteratorExt};
848 ///
849 /// let table = (0..3)
850 /// .map(|i| ("Hello", i))
851 /// .table()
852 /// .with(Style::rounded().horizontals((1..4).map(|i| HorizontalLine::new(i, Line::filled('#')))))
853 /// .to_string();
854 ///
855 /// assert_eq!(
856 /// table,
857 /// concat!(
858 /// "╭───────┬─────╮\n",
859 /// "│ &str │ i32 │\n",
860 /// "###############\n",
861 /// "│ Hello │ 0 │\n",
862 /// "###############\n",
863 /// "│ Hello │ 1 │\n",
864 /// "###############\n",
865 /// "│ Hello │ 2 │\n",
866 /// "╰───────┴─────╯",
867 /// )
868 /// )
869 /// ```
870 pub fn horizontals<NewLines>(self, lines: NewLines) -> Style<T, B, L, R, H, V, NewLines, VLines>
871 where
872 NewLines: IntoIterator<Item = HorizontalLine> + Clone,
873 {
874 Style::new(self.borders, lines, self.verticals)
875 }
876
877 /// Set border vertical lines.
878 ///
879 /// # Example
880 ///
881 /// ```
882 /// use tabled::{style::{Style, VerticalLine, Line}, TableIteratorExt};
883 ///
884 /// let table = (0..3)
885 /// .map(|i| ("Hello", i))
886 /// .table()
887 /// .with(Style::rounded().verticals((0..3).map(|i| VerticalLine::new(i, Line::filled('#')))))
888 /// .to_string();
889 ///
890 /// assert_eq!(
891 /// table,
892 /// concat!(
893 /// "#───────#─────#\n",
894 /// "# &str # i32 #\n",
895 /// "├───────┼─────┤\n",
896 /// "# Hello # 0 #\n",
897 /// "# Hello # 1 #\n",
898 /// "# Hello # 2 #\n",
899 /// "#───────#─────#",
900 /// )
901 /// )
902 /// ```
903 pub fn verticals<NewLines>(self, lines: NewLines) -> Style<T, B, L, R, H, V, HLines, NewLines>
904 where
905 NewLines: IntoIterator<Item = VerticalLine> + Clone,
906 {
907 Style::new(self.borders, self.horizontals, lines)
908 }
909
910 /// Removes all horizontal lines set by [`Style::horizontals`]
911 pub fn off_horizontals(self) -> Style<T, B, L, R, H, V, HLineArray<0>, VLines> {
912 Style::new(self.borders, [], self.verticals)
913 }
914
915 /// Removes all verticals lines set by [`Style::verticals`]
916 pub fn off_verticals(self) -> Style<T, B, L, R, H, V, HLines, VLineArray<0>> {
917 Style::new(self.borders, self.horizontals, [])
918 }
919}
920
921impl<B, R, H, V, HLines, VLines> Style<On, B, On, R, H, V, HLines, VLines> {
922 /// Sets a top left corner.
923 pub fn top_left_corner(mut self, c: char) -> Self {
924 self.borders.top_left = Some(c);
925
926 Style::new(self.borders, self.horizontals, self.verticals)
927 }
928}
929
930impl<B, L, H, V, HLines, VLines> Style<On, B, L, On, H, V, HLines, VLines> {
931 /// Sets a top right corner.
932 pub fn top_right_corner(mut self, c: char) -> Self {
933 self.borders.top_right = Some(c);
934
935 Style::new(self.borders, self.horizontals, self.verticals)
936 }
937}
938
939impl<T, L, H, V, HLines, VLines> Style<T, On, L, On, H, V, HLines, VLines> {
940 /// Sets a bottom right corner.
941 pub fn bottom_right_corner(mut self, c: char) -> Self {
942 self.borders.bottom_right = Some(c);
943
944 Style::new(self.borders, self.horizontals, self.verticals)
945 }
946}
947
948impl<T, R, H, V, HLines, VLines> Style<T, On, On, R, H, V, HLines, VLines> {
949 /// Sets a bottom left corner.
950 pub fn bottom_left_corner(mut self, c: char) -> Self {
951 self.borders.bottom_left = Some(c);
952
953 Style::new(self.borders, self.horizontals, self.verticals)
954 }
955}
956
957impl<T, B, R, V, HLines, VLines> Style<T, B, On, R, On, V, HLines, VLines> {
958 /// Sets a left intersection char.
959 pub fn left_intersection(mut self, c: char) -> Self {
960 self.borders.horizontal_left = Some(c);
961
962 Style::new(self.borders, self.horizontals, self.verticals)
963 }
964}
965
966impl<T, B, L, V, HLines, VLines> Style<T, B, L, On, On, V, HLines, VLines> {
967 /// Sets a right intersection char.
968 pub fn right_intersection(mut self, c: char) -> Self {
969 self.borders.horizontal_right = Some(c);
970
971 Style::new(self.borders, self.horizontals, self.verticals)
972 }
973}
974
975impl<B, L, R, H, HLines, VLines> Style<On, B, L, R, H, On, HLines, VLines> {
976 /// Sets a top intersection char.
977 pub fn top_intersection(mut self, c: char) -> Self {
978 self.borders.top_intersection = Some(c);
979
980 Style::new(self.borders, self.horizontals, self.verticals)
981 }
982}
983
984impl<T, L, R, H, HLines, VLines> Style<T, On, L, R, H, On, HLines, VLines> {
985 /// Sets a bottom intersection char.
986 pub fn bottom_intersection(mut self, c: char) -> Self {
987 self.borders.bottom_intersection = Some(c);
988
989 Style::new(self.borders, self.horizontals, self.verticals)
990 }
991}
992
993impl<T, B, L, R, HLines, VLines> Style<T, B, L, R, On, On, HLines, VLines> {
994 /// Sets an inner intersection char.
995 /// A char between horizontal and vertical split lines.
996 pub fn inner_intersection(mut self, c: char) -> Self {
997 self.borders.intersection = Some(c);
998
999 Style::new(self.borders, self.horizontals, self.verticals)
1000 }
1001}
1002
1003impl<B, L, R, H, V, HLines, VLines> Style<On, B, L, R, H, V, HLines, VLines> {
1004 /// Removes top border.
1005 pub fn off_top(mut self) -> Style<(), B, L, R, H, V, HLines, VerticalLineIter<VLines::IntoIter>>
1006 where
1007 VLines: IntoIterator<Item = VerticalLine> + Clone,
1008 {
1009 self.borders.top = None;
1010 self.borders.top_intersection = None;
1011 self.borders.top_left = None;
1012 self.borders.top_right = None;
1013
1014 let iter = VerticalLineIter::new(self.verticals.into_iter(), false, true, false);
1015 Style::new(self.borders, self.horizontals, iter)
1016 }
1017}
1018
1019impl<T, L, R, H, V, HLines, VLines> Style<T, On, L, R, H, V, HLines, VLines> {
1020 /// Removes bottom border.
1021 pub fn off_bottom(
1022 mut self,
1023 ) -> Style<T, (), L, R, H, V, HLines, VerticalLineIter<VLines::IntoIter>>
1024 where
1025 VLines: IntoIterator<Item = VerticalLine> + Clone,
1026 {
1027 self.borders.bottom = None;
1028 self.borders.bottom_intersection = None;
1029 self.borders.bottom_left = None;
1030 self.borders.bottom_right = None;
1031
1032 let iter = VerticalLineIter::new(self.verticals.into_iter(), false, false, true);
1033 Style::new(self.borders, self.horizontals, iter)
1034 }
1035}
1036
1037impl<T, B, R, H, V, HLines, VLines> Style<T, B, On, R, H, V, HLines, VLines> {
1038 /// Removes left border.
1039 pub fn off_left(
1040 mut self,
1041 ) -> Style<T, B, (), R, H, V, HorizontalLineIter<HLines::IntoIter>, VLines>
1042 where
1043 HLines: IntoIterator<Item = HorizontalLine> + Clone,
1044 {
1045 self.borders.vertical_left = None;
1046 self.borders.horizontal_left = None;
1047 self.borders.top_left = None;
1048 self.borders.bottom_left = None;
1049
1050 let iter = HorizontalLineIter::new(self.horizontals.into_iter(), false, true, false);
1051 Style::new(self.borders, iter, self.verticals)
1052 }
1053}
1054
1055impl<T, B, L, H, V, HLines, VLines> Style<T, B, L, On, H, V, HLines, VLines> {
1056 /// Removes right border.
1057 pub fn off_right(
1058 mut self,
1059 ) -> Style<T, B, L, (), H, V, HorizontalLineIter<HLines::IntoIter>, VLines>
1060 where
1061 HLines: IntoIterator<Item = HorizontalLine> + Clone,
1062 {
1063 self.borders.vertical_right = None;
1064 self.borders.horizontal_right = None;
1065 self.borders.top_right = None;
1066 self.borders.bottom_right = None;
1067
1068 let iter = HorizontalLineIter::new(self.horizontals.into_iter(), false, false, true);
1069 Style::new(self.borders, iter, self.verticals)
1070 }
1071}
1072
1073impl<T, B, L, R, V, HLines, VLines> Style<T, B, L, R, On, V, HLines, VLines> {
1074 /// Removes horizontal split lines.
1075 ///
1076 /// Not including custom split lines.
1077 pub fn off_horizontal(
1078 mut self,
1079 ) -> Style<T, B, L, R, (), V, HLines, VerticalLineIter<VLines::IntoIter>>
1080 where
1081 VLines: IntoIterator<Item = VerticalLine> + Clone,
1082 {
1083 self.borders.horizontal = None;
1084 self.borders.horizontal_left = None;
1085 self.borders.horizontal_right = None;
1086 self.borders.intersection = None;
1087
1088 let iter = VerticalLineIter::new(self.verticals.into_iter(), true, false, false);
1089 Style::new(self.borders, self.horizontals, iter)
1090 }
1091}
1092
1093impl<T, B, L, R, H, HLines, VLines> Style<T, B, L, R, H, On, HLines, VLines> {
1094 /// Removes vertical split lines.
1095 pub fn off_vertical(
1096 mut self,
1097 ) -> Style<T, B, L, R, H, (), HorizontalLineIter<HLines::IntoIter>, VLines>
1098 where
1099 HLines: IntoIterator<Item = HorizontalLine> + Clone,
1100 {
1101 self.borders.vertical = None;
1102 self.borders.top_intersection = None;
1103 self.borders.bottom_intersection = None;
1104 self.borders.intersection = None;
1105
1106 let iter = HorizontalLineIter::new(self.horizontals.into_iter(), true, false, false);
1107 Style::new(self.borders, iter, self.verticals)
1108 }
1109}
1110
1111impl<T, B, L, R, H, V, HLines, VLines> Style<T, B, L, R, H, V, HLines, VLines> {
1112 const fn new(borders: Borders, horizontals: HLines, verticals: VLines) -> Self {
1113 Self {
1114 borders,
1115 horizontals,
1116 verticals,
1117 _top: PhantomData,
1118 _bottom: PhantomData,
1119 _left: PhantomData,
1120 _right: PhantomData,
1121 _horizontal: PhantomData,
1122 _vertical: PhantomData,
1123 }
1124 }
1125}
1126
1127impl<T, B, L, R, H, V, HLines, VLines, I> TableOption<I> for Style<T, B, L, R, H, V, HLines, VLines>
1128where
1129 I: Records,
1130 HLines: IntoIterator<Item = HorizontalLine> + Clone,
1131 VLines: IntoIterator<Item = VerticalLine> + Clone,
1132{
1133 fn change(&mut self, table: &mut Table<I>) {
1134 table.get_config_mut().clear_theme();
1135 table.get_config_mut().set_borders(self.borders.clone());
1136
1137 if table.shape().0 > 1 {
1138 for mut hl in self.horizontals.clone() {
1139 hl.change(table);
1140 }
1141 }
1142
1143 if table.shape().1 > 1 {
1144 for mut vl in self.verticals.clone() {
1145 vl.change(table);
1146 }
1147 }
1148
1149 table.destroy_width_cache();
1150 table.destroy_height_cache();
1151 }
1152}
1153
1154const fn create_borders(
1155 top: Line,
1156 bottom: Line,
1157 horizontal: Line,
1158 left: Option<char>,
1159 right: Option<char>,
1160 vertical: Option<char>,
1161) -> Borders {
1162 Borders {
1163 top: top.main,
1164 bottom: bottom.main,
1165 top_left: top.connector1,
1166 top_right: top.connector2,
1167 bottom_left: bottom.connector1,
1168 bottom_right: bottom.connector2,
1169 top_intersection: top.intersection,
1170 bottom_intersection: bottom.intersection,
1171 horizontal_left: horizontal.connector1,
1172 horizontal_right: horizontal.connector2,
1173 horizontal: horizontal.main,
1174 intersection: horizontal.intersection,
1175 vertical_left: left,
1176 vertical_right: right,
1177 vertical,
1178 }
1179}
1180
1181/// An interator which limits [`Line`] influence on iterations over lines for in [`Style`].
1182#[derive(Debug, Clone)]
1183pub struct HorizontalLineIter<I> {
1184 iter: I,
1185 intersection: bool,
1186 left: bool,
1187 right: bool,
1188}
1189
1190impl<I> HorizontalLineIter<I> {
1191 fn new(iter: I, intersection: bool, left: bool, right: bool) -> Self {
1192 Self {
1193 iter,
1194 intersection,
1195 left,
1196 right,
1197 }
1198 }
1199}
1200
1201impl<I> Iterator for HorizontalLineIter<I>
1202where
1203 I: Iterator<Item = HorizontalLine>,
1204{
1205 type Item = HorizontalLine;
1206
1207 fn next(&mut self) -> Option<Self::Item> {
1208 let mut hl = self.iter.next()?;
1209
1210 if let Some(mut line) = hl.line {
1211 if self.intersection {
1212 line.intersection = None;
1213 }
1214
1215 if self.left {
1216 line.connector1 = None;
1217 }
1218
1219 if self.right {
1220 line.connector2 = None;
1221 }
1222
1223 hl.line = Some(line);
1224 }
1225
1226 Some(hl)
1227 }
1228}
1229
1230/// An interator which limits [`Line`] influence on iterations over lines for in [`Style`].
1231#[derive(Debug, Clone)]
1232pub struct VerticalLineIter<I> {
1233 iter: I,
1234 intersection: bool,
1235 top: bool,
1236 bottom: bool,
1237}
1238
1239impl<I> VerticalLineIter<I> {
1240 fn new(iter: I, intersection: bool, top: bool, bottom: bool) -> Self {
1241 Self {
1242 iter,
1243 intersection,
1244 top,
1245 bottom,
1246 }
1247 }
1248}
1249
1250impl<I> Iterator for VerticalLineIter<I>
1251where
1252 I: Iterator<Item = VerticalLine>,
1253{
1254 type Item = VerticalLine;
1255
1256 fn next(&mut self) -> Option<Self::Item> {
1257 let mut hl = self.iter.next()?;
1258
1259 if let Some(mut line) = hl.line {
1260 if self.intersection {
1261 line.intersection = None;
1262 }
1263
1264 if self.top {
1265 line.connector1 = None;
1266 }
1267
1268 if self.bottom {
1269 line.connector2 = None;
1270 }
1271
1272 hl.line = Some(line);
1273 }
1274
1275 Some(hl)
1276 }
1277}