1use itertools::Itertools;
7use std::fmt::Display;
8
9pub trait TeX {
11 fn tex_math_symbol() -> String;
12 fn tex_string(&self) -> String;
13}
14
15pub fn tex_table<S, I, J, I1, I2, D, F>(caption: &str, head: S, rows: I1, cols: I2, entry: F, math_mode: bool, hor_at_top: bool) -> String
17where
18 S: Display,
19 I: Display,
20 J: Display,
21 I1: IntoIterator<Item = I>,
22 I2: IntoIterator<Item = J>,
23 D: Display,
24 F: Fn(&I, &J) -> D
25{
26 fn disp<S>(s: S, math_mode: bool) -> String where S: Display {
27 if math_mode {
28 let s = s.to_string();
29 if s.is_empty() {
30 "$ $".to_string()
31 } else {
32 format!("${}$", s)
33 }
34 } else {
35 s.to_string()
36 }
37 }
38
39 let cols = cols.into_iter().collect_vec();
40 let mut res = String::new();
41
42 res += r#"\begin{table}
43\centering
44\begin{tabular}"#;
45
46 res += &format!("{{r|{}}}\n", "l".repeat(cols.len()));
47
48 let row = |head: String, cells: Vec<String>|
50 std::iter::once(head).chain(cells).join(" & ") + " \\\\\n";
51
52 let hor = row(
53 disp(head, math_mode),
54 cols.iter().map(|c| disp(c, math_mode)).collect_vec()
55 );
56
57 if hor_at_top {
58 res += &hor;
59 res += "\\hline\n";
60 }
61
62 for i in rows {
63 res += &row(
64 disp(&i, math_mode),
65 cols.iter().map(|j| disp(entry(&i, j), math_mode)).collect_vec()
66 );
67 }
68
69 if !hor_at_top {
70 res += "\\hline\n";
71 res += &hor;
72 }
73
74 res += "\\end{tabular}\n";
75 res += &format!("\\caption{{{caption}}}\n");
76 res += "\\end{table}\n";
77 res
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 #[test]
85 fn test_tex_table() {
86 let _table = tex_table("Caption", "i, j", [1, 2, 3], [4, 5, 6, 7], |i, j| i * 10 + j, true, false);
87 }
89
90 #[test]
91 fn table_without_columns() {
92 let entry = |i: &i32, j: &i32| i * 10 + j;
94 let none: [i32; 0] = [];
95
96 let t = tex_table("Caption", "i", none, none, entry, true, true);
97 assert!(t.contains("{r|}"));
98 assert!(!t.contains('&'), "empty table has a stray `&`:\n{t}");
99
100 let t = tex_table("Caption", "i", [1, 2], none, entry, true, true);
101 assert!(!t.contains('&'), "column-less rows have a stray `&`:\n{t}");
102
103 let t = tex_table("Caption", "i", [1], [4, 5], entry, true, true);
105 assert!(t.lines().all(|l| !l.ends_with("\\\\") || l.matches('&').count() == 2), "{t}");
106 }
107}