1use std::{borrow::Cow, collections::BTreeMap};
2
3use once_cell::sync::Lazy;
4
5use crate::{alignment::Alignment, width::visible_width};
6
7#[derive(Clone, Debug)]
9pub struct Line {
10 pub begin: Cow<'static, str>,
12 pub fill: Cow<'static, str>,
14 pub separator: Cow<'static, str>,
16 pub end: Cow<'static, str>,
18}
19
20impl Line {
21 fn borrowed(
22 begin: &'static str,
23 fill: &'static str,
24 separator: &'static str,
25 end: &'static str,
26 ) -> Self {
27 Self {
28 begin: Cow::Borrowed(begin),
29 fill: Cow::Borrowed(fill),
30 separator: Cow::Borrowed(separator),
31 end: Cow::Borrowed(end),
32 }
33 }
34}
35
36#[derive(Clone, Debug)]
38pub struct DataRow {
39 pub begin: Cow<'static, str>,
41 pub separator: Cow<'static, str>,
43 pub end: Cow<'static, str>,
45}
46
47impl DataRow {
48 fn borrowed(begin: &'static str, separator: &'static str, end: &'static str) -> Self {
49 Self {
50 begin: Cow::Borrowed(begin),
51 separator: Cow::Borrowed(separator),
52 end: Cow::Borrowed(end),
53 }
54 }
55}
56
57pub type LineFn = fn(col_widths: &[usize], col_aligns: &[Alignment]) -> String;
59pub type RowFn =
61 fn(cell_values: &[String], col_widths: &[usize], col_aligns: &[Alignment]) -> String;
62
63#[derive(Clone, Debug, Default)]
65pub enum LineFormat {
66 #[default]
68 None,
69 Static(Line),
71 Text(Cow<'static, str>),
73 Dynamic(LineFn),
75}
76
77#[derive(Clone, Debug, Default)]
79pub enum RowFormat {
80 #[default]
82 None,
83 Static(DataRow),
85 Dynamic(RowFn),
87}
88
89#[derive(Clone, Debug)]
91pub struct TableFormat {
92 pub line_above: LineFormat,
94 pub line_below_header: LineFormat,
96 pub line_between_rows: LineFormat,
98 pub line_below: LineFormat,
100 pub header_row: RowFormat,
102 pub data_row: RowFormat,
104 pub padding: usize,
106 pub with_header_hide: &'static [&'static str],
108}
109
110impl TableFormat {
111 pub fn hides(&self, component: &str) -> bool {
113 self.with_header_hide.contains(&component)
114 }
115}
116
117fn line(
118 begin: &'static str,
119 fill: &'static str,
120 separator: &'static str,
121 end: &'static str,
122) -> Line {
123 Line::borrowed(begin, fill, separator, end)
124}
125
126fn row(begin: &'static str, separator: &'static str, end: &'static str) -> DataRow {
127 DataRow::borrowed(begin, separator, end)
128}
129
130fn default_align(aligns: &[Alignment], index: usize) -> Alignment {
131 aligns.get(index).copied().unwrap_or(Alignment::Left)
132}
133
134fn pipe_segment_with_colons(align: Alignment, width: usize) -> String {
135 let width = width.max(1);
136 match align {
137 Alignment::Right | Alignment::Decimal => {
138 if width == 1 {
139 ":".to_string()
140 } else {
141 format!("{:-<w$}:", "-", w = width - 1)
142 }
143 }
144 Alignment::Center => {
145 if width <= 2 {
146 "::".chars().take(width).collect()
147 } else {
148 format!(":{}:", "-".repeat(width - 2))
149 }
150 }
151 Alignment::Left => {
152 if width == 1 {
153 ":".to_string()
154 } else {
155 format!(":{:-<w$}", "-", w = width - 1)
156 }
157 }
158 }
159}
160
161fn pipe_line_with_colons(col_widths: &[usize], col_aligns: &[Alignment]) -> String {
162 if col_aligns.is_empty() {
163 let line = col_widths
164 .iter()
165 .map(|w| "-".repeat(*w))
166 .collect::<Vec<_>>()
167 .join("|");
168 return format!("|{}|", line);
169 }
170
171 let segments = col_widths
172 .iter()
173 .enumerate()
174 .map(|(idx, width)| pipe_segment_with_colons(default_align(col_aligns, idx), *width))
175 .collect::<Vec<_>>();
176 format!("|{}|", segments.join("|"))
177}
178
179fn mediawiki_row_with_attrs(
180 separator: &str,
181 cell_values: &[String],
182 col_aligns: &[Alignment],
183) -> String {
184 let mut values = Vec::with_capacity(cell_values.len());
185 for (idx, cell) in cell_values.iter().enumerate() {
186 let align = match default_align(col_aligns, idx) {
187 Alignment::Right | Alignment::Decimal => r#" align="right"| "#,
188 Alignment::Center => r#" align="center"| "#,
189 Alignment::Left => "",
190 };
191 let mut value = String::new();
192 if align.is_empty() {
193 value.push(' ');
194 value.push_str(cell);
195 value.push(' ');
196 } else {
197 value.push_str(align);
198 value.push_str(cell);
199 }
200 values.push(value);
201 }
202 let mut result = String::new();
203 let colsep = separator.repeat(2);
204 result.push_str(separator);
205 result.push_str(&values.join(&colsep));
206 while result.ends_with(char::is_whitespace) {
207 result.pop();
208 }
209 result
210}
211
212fn mediawiki_header_row(
213 cell_values: &[String],
214 col_widths: &[usize],
215 col_aligns: &[Alignment],
216) -> String {
217 let _ = col_widths;
218 mediawiki_row_with_attrs("!", cell_values, col_aligns)
219}
220
221fn mediawiki_data_row(
222 cell_values: &[String],
223 col_widths: &[usize],
224 col_aligns: &[Alignment],
225) -> String {
226 let _ = col_widths;
227 mediawiki_row_with_attrs("|", cell_values, col_aligns)
228}
229
230fn moin_row_with_attrs(
231 celltag: &str,
232 header: Option<&str>,
233 cell_values: &[String],
234 col_widths: &[usize],
235 col_aligns: &[Alignment],
236) -> String {
237 let mut out = String::new();
238 for (idx, value) in cell_values.iter().enumerate() {
239 let alignment = default_align(col_aligns, idx);
240 let align_attr = match alignment {
241 Alignment::Right | Alignment::Decimal => r#"<style="text-align: right;">"#,
242 Alignment::Center => r#"<style="text-align: center;">"#,
243 Alignment::Left => "",
244 };
245 let total_width = col_widths.get(idx).copied().unwrap_or(value.len());
246 let inner_width = total_width.saturating_sub(2);
247 let mut core = value.clone();
248 let current_width = visible_width(&core, false);
249 if current_width < inner_width {
250 let padding = inner_width - current_width;
251 let (left_pad, right_pad) = match alignment {
252 Alignment::Right | Alignment::Decimal => (padding, 0),
253 Alignment::Center => (padding / 2, padding - (padding / 2)),
254 Alignment::Left => (0, padding),
255 };
256 core = format!("{}{}{}", " ".repeat(left_pad), core, " ".repeat(right_pad));
257 }
258 let mut base = String::new();
259 if let Some(marker) = header {
260 base.push_str(marker);
261 }
262 base.push_str(&core);
263 if let Some(marker) = header {
264 base.push_str(marker);
265 }
266 out.push_str(celltag);
267 out.push_str(align_attr);
268 out.push(' ');
269 out.push_str(&base);
270 out.push(' ');
271 }
272 out.push_str("||");
273 out
274}
275
276fn moin_header_row(
277 cell_values: &[String],
278 col_widths: &[usize],
279 col_aligns: &[Alignment],
280) -> String {
281 moin_row_with_attrs("||", Some("'''"), cell_values, col_widths, col_aligns)
282}
283
284fn moin_data_row(cell_values: &[String], col_widths: &[usize], col_aligns: &[Alignment]) -> String {
285 moin_row_with_attrs("||", None, cell_values, col_widths, col_aligns)
286}
287
288fn html_begin_table_without_header(_col_widths: &[usize], _col_aligns: &[Alignment]) -> String {
289 "<table>\n<tbody>".to_string()
290}
291
292fn html_escape(input: &str) -> String {
293 input
294 .chars()
295 .map(|c| match c {
296 '&' => "&".to_string(),
297 '<' => "<".to_string(),
298 '>' => ">".to_string(),
299 '"' => """.to_string(),
300 '\'' => "'".to_string(),
301 _ => c.to_string(),
302 })
303 .collect()
304}
305
306fn html_row_with_attrs(
307 celltag: &str,
308 unsafe_mode: bool,
309 cell_values: &[String],
310 col_widths: &[usize],
311 col_aligns: &[Alignment],
312) -> String {
313 let _ = col_widths;
314 let mut values = Vec::with_capacity(cell_values.len());
315 for (idx, cell) in cell_values.iter().enumerate() {
316 let align = match default_align(col_aligns, idx) {
317 Alignment::Right | Alignment::Decimal => r#" style="text-align: right;""#,
318 Alignment::Center => r#" style="text-align: center;""#,
319 Alignment::Left => "",
320 };
321 let content = if unsafe_mode {
322 cell.clone()
323 } else {
324 html_escape(cell)
325 };
326 let value = format!("<{celltag}{align}>{content}</{celltag}>");
327 values.push(value);
328 }
329 let mut rowhtml = format!("<tr>{}</tr>", values.join(""));
330 rowhtml.truncate(rowhtml.trim_end().len());
331 if celltag == "th" {
332 format!("<table>\n<thead>\n{rowhtml}\n</thead>\n<tbody>")
333 } else {
334 rowhtml
335 }
336}
337
338fn html_header_row_safe(
339 cell_values: &[String],
340 col_widths: &[usize],
341 col_aligns: &[Alignment],
342) -> String {
343 html_row_with_attrs("th", false, cell_values, col_widths, col_aligns)
344}
345
346fn html_data_row_safe(
347 cell_values: &[String],
348 col_widths: &[usize],
349 col_aligns: &[Alignment],
350) -> String {
351 html_row_with_attrs("td", false, cell_values, col_widths, col_aligns)
352}
353
354fn html_header_row_unsafe(
355 cell_values: &[String],
356 col_widths: &[usize],
357 col_aligns: &[Alignment],
358) -> String {
359 html_row_with_attrs("th", true, cell_values, col_widths, col_aligns)
360}
361
362fn html_data_row_unsafe(
363 cell_values: &[String],
364 col_widths: &[usize],
365 col_aligns: &[Alignment],
366) -> String {
367 html_row_with_attrs("td", true, cell_values, col_widths, col_aligns)
368}
369
370fn latex_line_begin_tabular(_col_widths: &[usize], col_aligns: &[Alignment]) -> String {
371 latex_line_begin_tabular_internal(col_aligns, false, false)
372}
373
374fn latex_line_begin_tabular_booktabs(_col_widths: &[usize], col_aligns: &[Alignment]) -> String {
375 latex_line_begin_tabular_internal(col_aligns, true, false)
376}
377
378fn latex_line_begin_tabular_longtable(_col_widths: &[usize], col_aligns: &[Alignment]) -> String {
379 latex_line_begin_tabular_internal(col_aligns, false, true)
380}
381
382fn latex_line_begin_tabular_internal(
383 col_aligns: &[Alignment],
384 booktabs: bool,
385 longtable: bool,
386) -> String {
387 let columns: String = col_aligns
388 .iter()
389 .map(|align| match align {
390 Alignment::Right | Alignment::Decimal => 'r',
391 Alignment::Center => 'c',
392 Alignment::Left => 'l',
393 })
394 .collect();
395 let begin = if longtable {
396 "\\begin{longtable}{"
397 } else {
398 "\\begin{tabular}{"
399 };
400 let mut result = String::new();
401 result.push_str(begin);
402 result.push_str(&columns);
403 result.push_str("}\n");
404 result.push_str(if booktabs { "\\toprule" } else { "\\hline" });
405 result
406}
407
408const LATEX_ESCAPE_RULES: &[(char, &str)] = &[
409 ('&', r"\&"),
410 ('%', r"\%"),
411 ('$', r"\$"),
412 ('#', r"\#"),
413 ('_', r"\_"),
414 ('^', r"\^{}"),
415 ('{', r"\{"),
416 ('}', r"\}"),
417 ('~', r"\textasciitilde{}"),
418 ('\\', r"\textbackslash{}"),
419 ('<', r"\ensuremath{<}"),
420 ('>', r"\ensuremath{>}"),
421];
422
423fn latex_escape(value: &str, escape: bool) -> String {
424 if !escape {
425 return value.to_string();
426 }
427 let mut out = String::with_capacity(value.len());
428 for ch in value.chars() {
429 if let Some((_, replacement)) = LATEX_ESCAPE_RULES
430 .iter()
431 .find(|(candidate, _)| *candidate == ch)
432 {
433 out.push_str(replacement);
434 } else {
435 out.push(ch);
436 }
437 }
438 out
439}
440
441fn latex_row(cell_values: &[String], col_widths: &[usize], col_aligns: &[Alignment]) -> String {
442 latex_row_internal(cell_values, col_widths, col_aligns, true)
443}
444
445fn latex_row_raw(cell_values: &[String], col_widths: &[usize], col_aligns: &[Alignment]) -> String {
446 latex_row_internal(cell_values, col_widths, col_aligns, false)
447}
448
449fn latex_row_internal(
450 cell_values: &[String],
451 _col_widths: &[usize],
452 _col_aligns: &[Alignment],
453 escape: bool,
454) -> String {
455 let escaped = cell_values
456 .iter()
457 .map(|cell| latex_escape(cell, escape))
458 .collect::<Vec<_>>();
459 format!("{}\\\\", escaped.join("&"))
460}
461
462fn textile_row_with_attrs(
463 cell_values: &[String],
464 col_widths: &[usize],
465 col_aligns: &[Alignment],
466) -> String {
467 let _ = col_widths;
468 if cell_values.is_empty() {
469 return String::from("||");
470 }
471 let mut values = cell_values.to_vec();
472 if let Some(first) = values.first_mut() {
473 first.push(' ');
474 }
475 let mut parts = Vec::with_capacity(values.len());
476 for (idx, value) in values.into_iter().enumerate() {
477 let align_prefix = match default_align(col_aligns, idx) {
478 Alignment::Left => "<.",
479 Alignment::Right | Alignment::Decimal => ">.",
480 Alignment::Center => "=.",
481 };
482 parts.push(format!("{}{}", align_prefix, value));
483 }
484 format!("|{}|", parts.join("|"))
485}
486
487fn asciidoc_alignment_code(align: Alignment) -> char {
488 match align {
489 Alignment::Left => '<',
490 Alignment::Right | Alignment::Decimal => '>',
491 Alignment::Center => '^',
492 }
493}
494
495fn asciidoc_make_header_line(
496 is_header: bool,
497 col_widths: &[usize],
498 col_aligns: &[Alignment],
499) -> String {
500 let mut column_specifiers = Vec::new();
501 for (idx, width) in col_widths.iter().enumerate() {
502 let align_char = asciidoc_alignment_code(default_align(col_aligns, idx));
503 column_specifiers.push(format!("{width}{align_char}"));
504 }
505 let mut header_entries = vec![format!("cols=\"{}\"", column_specifiers.join(","))];
506 if is_header {
507 header_entries.push("options=\"header\"".to_string());
508 }
509 format!("[{}]\n|====", header_entries.join(","))
510}
511
512fn asciidoc_line_above(col_widths: &[usize], col_aligns: &[Alignment]) -> String {
513 asciidoc_make_header_line(false, col_widths, col_aligns)
514}
515
516fn asciidoc_header_row(
517 cell_values: &[String],
518 col_widths: &[usize],
519 col_aligns: &[Alignment],
520) -> String {
521 let header = asciidoc_make_header_line(true, col_widths, col_aligns);
522 let data_line = format!("|{}", cell_values.join("|"));
523 format!("{header}\n{data_line}")
524}
525
526fn asciidoc_data_row(
527 cell_values: &[String],
528 _col_widths: &[usize],
529 _col_aligns: &[Alignment],
530) -> String {
531 format!("|{}", cell_values.join("|"))
532}
533
534fn build_formats() -> BTreeMap<&'static str, TableFormat> {
535 let mut formats = BTreeMap::new();
536 formats.insert(
537 "asciidoc",
538 TableFormat {
539 line_above: LineFormat::Dynamic(asciidoc_line_above),
540 line_below_header: LineFormat::None,
541 line_between_rows: LineFormat::None,
542 line_below: LineFormat::Static(line("|====", "", "", "")),
543 header_row: RowFormat::Dynamic(asciidoc_header_row),
544 data_row: RowFormat::Dynamic(asciidoc_data_row),
545 padding: 1,
546 with_header_hide: &["lineabove"],
547 },
548 );
549 formats.insert(
550 "colon_grid",
551 TableFormat {
552 line_above: LineFormat::Static(line("", "-", " ", "")),
553 line_below_header: LineFormat::Static(line("", "-", " ", "")),
554 line_between_rows: LineFormat::None,
555 line_below: LineFormat::Static(line("", "-", " ", "")),
556 header_row: RowFormat::Static(row("", " ", "")),
557 data_row: RowFormat::Static(row("", " ", "")),
558 padding: 0,
559 with_header_hide: &["lineabove", "linebelow"],
560 },
561 );
562 formats.insert(
563 "double_grid",
564 TableFormat {
565 line_above: LineFormat::Static(line("\u{2554}", "\u{2550}", "\u{2566}", "\u{2557}")),
566 line_below_header: LineFormat::Static(line(
567 "\u{2560}", "\u{2550}", "\u{256c}", "\u{2563}",
568 )),
569 line_between_rows: LineFormat::Static(line(
570 "\u{2560}", "\u{2550}", "\u{256c}", "\u{2563}",
571 )),
572 line_below: LineFormat::Static(line("\u{255a}", "\u{2550}", "\u{2569}", "\u{255d}")),
573 header_row: RowFormat::Static(row("\u{2551}", "\u{2551}", "\u{2551}")),
574 data_row: RowFormat::Static(row("\u{2551}", "\u{2551}", "\u{2551}")),
575 padding: 1,
576 with_header_hide: &[],
577 },
578 );
579 formats.insert(
580 "double_outline",
581 TableFormat {
582 line_above: LineFormat::Static(line("\u{2554}", "\u{2550}", "\u{2566}", "\u{2557}")),
583 line_below_header: LineFormat::Static(line(
584 "\u{2560}", "\u{2550}", "\u{256c}", "\u{2563}",
585 )),
586 line_between_rows: LineFormat::None,
587 line_below: LineFormat::Static(line("\u{255a}", "\u{2550}", "\u{2569}", "\u{255d}")),
588 header_row: RowFormat::Static(row("\u{2551}", "\u{2551}", "\u{2551}")),
589 data_row: RowFormat::Static(row("\u{2551}", "\u{2551}", "\u{2551}")),
590 padding: 1,
591 with_header_hide: &[],
592 },
593 );
594 formats.insert(
595 "fancy_grid",
596 TableFormat {
597 line_above: LineFormat::Static(line("\u{2552}", "\u{2550}", "\u{2564}", "\u{2555}")),
598 line_below_header: LineFormat::Static(line(
599 "\u{255e}", "\u{2550}", "\u{256a}", "\u{2561}",
600 )),
601 line_between_rows: LineFormat::Static(line(
602 "\u{251c}", "\u{2500}", "\u{253c}", "\u{2524}",
603 )),
604 line_below: LineFormat::Static(line("\u{2558}", "\u{2550}", "\u{2567}", "\u{255b}")),
605 header_row: RowFormat::Static(row("\u{2502}", "\u{2502}", "\u{2502}")),
606 data_row: RowFormat::Static(row("\u{2502}", "\u{2502}", "\u{2502}")),
607 padding: 1,
608 with_header_hide: &[],
609 },
610 );
611 formats.insert(
612 "fancy_outline",
613 TableFormat {
614 line_above: LineFormat::Static(line("\u{2552}", "\u{2550}", "\u{2564}", "\u{2555}")),
615 line_below_header: LineFormat::Static(line(
616 "\u{255e}", "\u{2550}", "\u{256a}", "\u{2561}",
617 )),
618 line_between_rows: LineFormat::None,
619 line_below: LineFormat::Static(line("\u{2558}", "\u{2550}", "\u{2567}", "\u{255b}")),
620 header_row: RowFormat::Static(row("\u{2502}", "\u{2502}", "\u{2502}")),
621 data_row: RowFormat::Static(row("\u{2502}", "\u{2502}", "\u{2502}")),
622 padding: 1,
623 with_header_hide: &[],
624 },
625 );
626 formats.insert(
627 "github",
628 TableFormat {
629 line_above: LineFormat::Static(line("|", "-", "|", "|")),
630 line_below_header: LineFormat::Static(line("|", "-", "|", "|")),
631 line_between_rows: LineFormat::None,
632 line_below: LineFormat::None,
633 header_row: RowFormat::Static(row("|", "|", "|")),
634 data_row: RowFormat::Static(row("|", "|", "|")),
635 padding: 1,
636 with_header_hide: &["lineabove"],
637 },
638 );
639 formats.insert(
640 "grid",
641 TableFormat {
642 line_above: LineFormat::Static(line("+", "-", "+", "+")),
643 line_below_header: LineFormat::Static(line("+", "=", "+", "+")),
644 line_between_rows: LineFormat::Static(line("+", "-", "+", "+")),
645 line_below: LineFormat::Static(line("+", "-", "+", "+")),
646 header_row: RowFormat::Static(row("|", "|", "|")),
647 data_row: RowFormat::Static(row("|", "|", "|")),
648 padding: 1,
649 with_header_hide: &[],
650 },
651 );
652 formats.insert(
653 "heavy_grid",
654 TableFormat {
655 line_above: LineFormat::Static(line("\u{250f}", "\u{2501}", "\u{2533}", "\u{2513}")),
656 line_below_header: LineFormat::Static(line(
657 "\u{2523}", "\u{2501}", "\u{254b}", "\u{252b}",
658 )),
659 line_between_rows: LineFormat::Static(line(
660 "\u{2523}", "\u{2501}", "\u{254b}", "\u{252b}",
661 )),
662 line_below: LineFormat::Static(line("\u{2517}", "\u{2501}", "\u{253b}", "\u{251b}")),
663 header_row: RowFormat::Static(row("\u{2503}", "\u{2503}", "\u{2503}")),
664 data_row: RowFormat::Static(row("\u{2503}", "\u{2503}", "\u{2503}")),
665 padding: 1,
666 with_header_hide: &[],
667 },
668 );
669 formats.insert(
670 "heavy_outline",
671 TableFormat {
672 line_above: LineFormat::Static(line("\u{250f}", "\u{2501}", "\u{2533}", "\u{2513}")),
673 line_below_header: LineFormat::Static(line(
674 "\u{2523}", "\u{2501}", "\u{254b}", "\u{252b}",
675 )),
676 line_between_rows: LineFormat::None,
677 line_below: LineFormat::Static(line("\u{2517}", "\u{2501}", "\u{253b}", "\u{251b}")),
678 header_row: RowFormat::Static(row("\u{2503}", "\u{2503}", "\u{2503}")),
679 data_row: RowFormat::Static(row("\u{2503}", "\u{2503}", "\u{2503}")),
680 padding: 1,
681 with_header_hide: &[],
682 },
683 );
684 formats.insert(
685 "html",
686 TableFormat {
687 line_above: LineFormat::Dynamic(html_begin_table_without_header),
688 line_below_header: LineFormat::Text(Cow::Borrowed("")),
689 line_between_rows: LineFormat::None,
690 line_below: LineFormat::Static(line("</tbody>\n</table>", "", "", "")),
691 header_row: RowFormat::Dynamic(html_header_row_safe),
692 data_row: RowFormat::Dynamic(html_data_row_safe),
693 padding: 0,
694 with_header_hide: &["lineabove"],
695 },
696 );
697 formats.insert(
698 "jira",
699 TableFormat {
700 line_above: LineFormat::None,
701 line_below_header: LineFormat::None,
702 line_between_rows: LineFormat::None,
703 line_below: LineFormat::None,
704 header_row: RowFormat::Static(row("||", "||", "||")),
705 data_row: RowFormat::Static(row("|", "|", "|")),
706 padding: 1,
707 with_header_hide: &[],
708 },
709 );
710 formats.insert(
711 "latex",
712 TableFormat {
713 line_above: LineFormat::Dynamic(latex_line_begin_tabular),
714 line_below_header: LineFormat::Static(line("\\hline", "", "", "")),
715 line_between_rows: LineFormat::None,
716 line_below: LineFormat::Static(line("\\hline\n\\end{tabular}", "", "", "")),
717 header_row: RowFormat::Dynamic(latex_row),
718 data_row: RowFormat::Dynamic(latex_row),
719 padding: 1,
720 with_header_hide: &[],
721 },
722 );
723 formats.insert(
724 "latex_booktabs",
725 TableFormat {
726 line_above: LineFormat::Dynamic(latex_line_begin_tabular_booktabs),
727 line_below_header: LineFormat::Static(line("\\midrule", "", "", "")),
728 line_between_rows: LineFormat::None,
729 line_below: LineFormat::Static(line("\\bottomrule\n\\end{tabular}", "", "", "")),
730 header_row: RowFormat::Dynamic(latex_row),
731 data_row: RowFormat::Dynamic(latex_row),
732 padding: 1,
733 with_header_hide: &[],
734 },
735 );
736 formats.insert(
737 "latex_longtable",
738 TableFormat {
739 line_above: LineFormat::Dynamic(latex_line_begin_tabular_longtable),
740 line_below_header: LineFormat::Static(line("\\hline\n\\endhead", "", "", "")),
741 line_between_rows: LineFormat::None,
742 line_below: LineFormat::Static(line("\\hline\n\\end{longtable}", "", "", "")),
743 header_row: RowFormat::Dynamic(latex_row),
744 data_row: RowFormat::Dynamic(latex_row),
745 padding: 1,
746 with_header_hide: &[],
747 },
748 );
749 formats.insert(
750 "latex_raw",
751 TableFormat {
752 line_above: LineFormat::Dynamic(latex_line_begin_tabular),
753 line_below_header: LineFormat::Static(line("\\hline", "", "", "")),
754 line_between_rows: LineFormat::None,
755 line_below: LineFormat::Static(line("\\hline\n\\end{tabular}", "", "", "")),
756 header_row: RowFormat::Dynamic(latex_row_raw),
757 data_row: RowFormat::Dynamic(latex_row_raw),
758 padding: 1,
759 with_header_hide: &[],
760 },
761 );
762 formats.insert(
763 "mediawiki",
764 TableFormat {
765 line_above: LineFormat::Static(line(
766 "{| class=\"wikitable\" style=\"text-align: left;\"",
767 "",
768 "",
769 "\n|+ <!-- caption -->\n|-",
770 )),
771 line_below_header: LineFormat::Static(line("|-", "", "", "")),
772 line_between_rows: LineFormat::Static(line("|-", "", "", "")),
773 line_below: LineFormat::Static(line("|}", "", "", "")),
774 header_row: RowFormat::Dynamic(mediawiki_header_row),
775 data_row: RowFormat::Dynamic(mediawiki_data_row),
776 padding: 0,
777 with_header_hide: &[],
778 },
779 );
780 formats.insert(
781 "mixed_grid",
782 TableFormat {
783 line_above: LineFormat::Static(line("\u{250d}", "\u{2501}", "\u{252f}", "\u{2511}")),
784 line_below_header: LineFormat::Static(line(
785 "\u{251d}", "\u{2501}", "\u{253f}", "\u{2525}",
786 )),
787 line_between_rows: LineFormat::Static(line(
788 "\u{251c}", "\u{2500}", "\u{253c}", "\u{2524}",
789 )),
790 line_below: LineFormat::Static(line("\u{2515}", "\u{2501}", "\u{2537}", "\u{2519}")),
791 header_row: RowFormat::Static(row("\u{2502}", "\u{2502}", "\u{2502}")),
792 data_row: RowFormat::Static(row("\u{2502}", "\u{2502}", "\u{2502}")),
793 padding: 1,
794 with_header_hide: &[],
795 },
796 );
797 formats.insert(
798 "mixed_outline",
799 TableFormat {
800 line_above: LineFormat::Static(line("\u{250d}", "\u{2501}", "\u{252f}", "\u{2511}")),
801 line_below_header: LineFormat::Static(line(
802 "\u{251d}", "\u{2501}", "\u{253f}", "\u{2525}",
803 )),
804 line_between_rows: LineFormat::None,
805 line_below: LineFormat::Static(line("\u{2515}", "\u{2501}", "\u{2537}", "\u{2519}")),
806 header_row: RowFormat::Static(row("\u{2502}", "\u{2502}", "\u{2502}")),
807 data_row: RowFormat::Static(row("\u{2502}", "\u{2502}", "\u{2502}")),
808 padding: 1,
809 with_header_hide: &[],
810 },
811 );
812 formats.insert(
813 "moinmoin",
814 TableFormat {
815 line_above: LineFormat::None,
816 line_below_header: LineFormat::None,
817 line_between_rows: LineFormat::None,
818 line_below: LineFormat::None,
819 header_row: RowFormat::Dynamic(moin_header_row),
820 data_row: RowFormat::Dynamic(moin_data_row),
821 padding: 1,
822 with_header_hide: &[],
823 },
824 );
825 formats.insert(
826 "orgtbl",
827 TableFormat {
828 line_above: LineFormat::None,
829 line_below_header: LineFormat::Static(line("|", "-", "+", "|")),
830 line_between_rows: LineFormat::None,
831 line_below: LineFormat::None,
832 header_row: RowFormat::Static(row("|", "|", "|")),
833 data_row: RowFormat::Static(row("|", "|", "|")),
834 padding: 1,
835 with_header_hide: &[],
836 },
837 );
838 formats.insert(
839 "outline",
840 TableFormat {
841 line_above: LineFormat::Static(line("+", "-", "+", "+")),
842 line_below_header: LineFormat::Static(line("+", "=", "+", "+")),
843 line_between_rows: LineFormat::None,
844 line_below: LineFormat::Static(line("+", "-", "+", "+")),
845 header_row: RowFormat::Static(row("|", "|", "|")),
846 data_row: RowFormat::Static(row("|", "|", "|")),
847 padding: 1,
848 with_header_hide: &[],
849 },
850 );
851 formats.insert(
852 "pipe",
853 TableFormat {
854 line_above: LineFormat::Dynamic(pipe_line_with_colons),
855 line_below_header: LineFormat::Dynamic(pipe_line_with_colons),
856 line_between_rows: LineFormat::None,
857 line_below: LineFormat::None,
858 header_row: RowFormat::Static(row("|", "|", "|")),
859 data_row: RowFormat::Static(row("|", "|", "|")),
860 padding: 1,
861 with_header_hide: &["lineabove"],
862 },
863 );
864 formats.insert(
865 "plain",
866 TableFormat {
867 line_above: LineFormat::None,
868 line_below_header: LineFormat::None,
869 line_between_rows: LineFormat::None,
870 line_below: LineFormat::None,
871 header_row: RowFormat::Static(row("", " ", "")),
872 data_row: RowFormat::Static(row("", " ", "")),
873 padding: 0,
874 with_header_hide: &[],
875 },
876 );
877 formats.insert(
878 "presto",
879 TableFormat {
880 line_above: LineFormat::None,
881 line_below_header: LineFormat::Static(line("", "-", "+", "")),
882 line_between_rows: LineFormat::None,
883 line_below: LineFormat::None,
884 header_row: RowFormat::Static(row("", "|", "")),
885 data_row: RowFormat::Static(row("", "|", "")),
886 padding: 1,
887 with_header_hide: &[],
888 },
889 );
890 formats.insert(
891 "pretty",
892 TableFormat {
893 line_above: LineFormat::Static(line("+", "-", "+", "+")),
894 line_below_header: LineFormat::Static(line("+", "-", "+", "+")),
895 line_between_rows: LineFormat::None,
896 line_below: LineFormat::Static(line("+", "-", "+", "+")),
897 header_row: RowFormat::Static(row("|", "|", "|")),
898 data_row: RowFormat::Static(row("|", "|", "|")),
899 padding: 1,
900 with_header_hide: &[],
901 },
902 );
903 formats.insert(
904 "psql",
905 TableFormat {
906 line_above: LineFormat::Static(line("+", "-", "+", "+")),
907 line_below_header: LineFormat::Static(line("|", "-", "+", "|")),
908 line_between_rows: LineFormat::None,
909 line_below: LineFormat::Static(line("+", "-", "+", "+")),
910 header_row: RowFormat::Static(row("|", "|", "|")),
911 data_row: RowFormat::Static(row("|", "|", "|")),
912 padding: 1,
913 with_header_hide: &[],
914 },
915 );
916 formats.insert(
917 "rounded_grid",
918 TableFormat {
919 line_above: LineFormat::Static(line("\u{256d}", "\u{2500}", "\u{252c}", "\u{256e}")),
920 line_below_header: LineFormat::Static(line(
921 "\u{251c}", "\u{2500}", "\u{253c}", "\u{2524}",
922 )),
923 line_between_rows: LineFormat::Static(line(
924 "\u{251c}", "\u{2500}", "\u{253c}", "\u{2524}",
925 )),
926 line_below: LineFormat::Static(line("\u{2570}", "\u{2500}", "\u{2534}", "\u{256f}")),
927 header_row: RowFormat::Static(row("\u{2502}", "\u{2502}", "\u{2502}")),
928 data_row: RowFormat::Static(row("\u{2502}", "\u{2502}", "\u{2502}")),
929 padding: 1,
930 with_header_hide: &[],
931 },
932 );
933 formats.insert(
934 "rounded_outline",
935 TableFormat {
936 line_above: LineFormat::Static(line("\u{256d}", "\u{2500}", "\u{252c}", "\u{256e}")),
937 line_below_header: LineFormat::Static(line(
938 "\u{251c}", "\u{2500}", "\u{253c}", "\u{2524}",
939 )),
940 line_between_rows: LineFormat::None,
941 line_below: LineFormat::Static(line("\u{2570}", "\u{2500}", "\u{2534}", "\u{256f}")),
942 header_row: RowFormat::Static(row("\u{2502}", "\u{2502}", "\u{2502}")),
943 data_row: RowFormat::Static(row("\u{2502}", "\u{2502}", "\u{2502}")),
944 padding: 1,
945 with_header_hide: &[],
946 },
947 );
948 formats.insert(
949 "rst",
950 TableFormat {
951 line_above: LineFormat::Static(line("", "=", " ", "")),
952 line_below_header: LineFormat::Static(line("", "=", " ", "")),
953 line_between_rows: LineFormat::None,
954 line_below: LineFormat::Static(line("", "=", " ", "")),
955 header_row: RowFormat::Static(row("", " ", "")),
956 data_row: RowFormat::Static(row("", " ", "")),
957 padding: 0,
958 with_header_hide: &[],
959 },
960 );
961 formats.insert(
962 "simple",
963 TableFormat {
964 line_above: LineFormat::Static(line("", "-", " ", "")),
965 line_below_header: LineFormat::Static(line("", "-", " ", "")),
966 line_between_rows: LineFormat::None,
967 line_below: LineFormat::Static(line("", "-", " ", "")),
968 header_row: RowFormat::Static(row("", " ", "")),
969 data_row: RowFormat::Static(row("", " ", "")),
970 padding: 0,
971 with_header_hide: &["lineabove", "linebelow"],
972 },
973 );
974 formats.insert(
975 "simple_grid",
976 TableFormat {
977 line_above: LineFormat::Static(line("\u{250c}", "\u{2500}", "\u{252c}", "\u{2510}")),
978 line_below_header: LineFormat::Static(line(
979 "\u{251c}", "\u{2500}", "\u{253c}", "\u{2524}",
980 )),
981 line_between_rows: LineFormat::Static(line(
982 "\u{251c}", "\u{2500}", "\u{253c}", "\u{2524}",
983 )),
984 line_below: LineFormat::Static(line("\u{2514}", "\u{2500}", "\u{2534}", "\u{2518}")),
985 header_row: RowFormat::Static(row("\u{2502}", "\u{2502}", "\u{2502}")),
986 data_row: RowFormat::Static(row("\u{2502}", "\u{2502}", "\u{2502}")),
987 padding: 1,
988 with_header_hide: &[],
989 },
990 );
991 formats.insert(
992 "simple_outline",
993 TableFormat {
994 line_above: LineFormat::Static(line("\u{250c}", "\u{2500}", "\u{252c}", "\u{2510}")),
995 line_below_header: LineFormat::Static(line(
996 "\u{251c}", "\u{2500}", "\u{253c}", "\u{2524}",
997 )),
998 line_between_rows: LineFormat::None,
999 line_below: LineFormat::Static(line("\u{2514}", "\u{2500}", "\u{2534}", "\u{2518}")),
1000 header_row: RowFormat::Static(row("\u{2502}", "\u{2502}", "\u{2502}")),
1001 data_row: RowFormat::Static(row("\u{2502}", "\u{2502}", "\u{2502}")),
1002 padding: 1,
1003 with_header_hide: &[],
1004 },
1005 );
1006 formats.insert(
1007 "textile",
1008 TableFormat {
1009 line_above: LineFormat::None,
1010 line_below_header: LineFormat::None,
1011 line_between_rows: LineFormat::None,
1012 line_below: LineFormat::None,
1013 header_row: RowFormat::Static(row("|_. ", "|_.", "|")),
1014 data_row: RowFormat::Dynamic(textile_row_with_attrs),
1015 padding: 1,
1016 with_header_hide: &[],
1017 },
1018 );
1019 formats.insert(
1020 "tsv",
1021 TableFormat {
1022 line_above: LineFormat::None,
1023 line_below_header: LineFormat::None,
1024 line_between_rows: LineFormat::None,
1025 line_below: LineFormat::None,
1026 header_row: RowFormat::Static(row("", "\t", "")),
1027 data_row: RowFormat::Static(row("", "\t", "")),
1028 padding: 0,
1029 with_header_hide: &[],
1030 },
1031 );
1032 formats.insert(
1033 "unsafehtml",
1034 TableFormat {
1035 line_above: LineFormat::Dynamic(html_begin_table_without_header),
1036 line_below_header: LineFormat::Text(Cow::Borrowed("")),
1037 line_between_rows: LineFormat::None,
1038 line_below: LineFormat::Static(line("</tbody>\n</table>", "", "", "")),
1039 header_row: RowFormat::Dynamic(html_header_row_unsafe),
1040 data_row: RowFormat::Dynamic(html_data_row_unsafe),
1041 padding: 0,
1042 with_header_hide: &["lineabove"],
1043 },
1044 );
1045 formats.insert(
1046 "youtrack",
1047 TableFormat {
1048 line_above: LineFormat::None,
1049 line_below_header: LineFormat::None,
1050 line_between_rows: LineFormat::None,
1051 line_below: LineFormat::None,
1052 header_row: RowFormat::Static(row("|| ", " || ", " || ")),
1053 data_row: RowFormat::Static(row("| ", " | ", " |")),
1054 padding: 1,
1055 with_header_hide: &[],
1056 },
1057 );
1058 formats
1059}
1060
1061static TABLE_FORMATS: Lazy<BTreeMap<&'static str, TableFormat>> = Lazy::new(build_formats);
1062
1063static TABLE_FORMAT_NAMES: Lazy<Vec<&'static str>> =
1064 Lazy::new(|| TABLE_FORMATS.keys().copied().collect());
1065
1066pub fn table_format(name: &str) -> Option<&'static TableFormat> {
1068 TABLE_FORMATS.get(name)
1069}
1070
1071pub fn tabulate_formats() -> &'static [&'static str] {
1073 TABLE_FORMAT_NAMES.as_slice()
1074}
1075
1076pub fn simple_separated_format<S: Into<String>>(separator: S) -> TableFormat {
1078 let separator: Cow<'static, str> = Cow::Owned(separator.into());
1079 TableFormat {
1080 line_above: LineFormat::None,
1081 line_below_header: LineFormat::None,
1082 line_between_rows: LineFormat::None,
1083 line_below: LineFormat::None,
1084 header_row: RowFormat::Static(DataRow {
1085 begin: Cow::Borrowed(""),
1086 separator: separator.clone(),
1087 end: Cow::Borrowed(""),
1088 }),
1089 data_row: RowFormat::Static(DataRow {
1090 begin: Cow::Borrowed(""),
1091 separator,
1092 end: Cow::Borrowed(""),
1093 }),
1094 padding: 0,
1095 with_header_hide: &[],
1096 }
1097}