1use easydoc_core::{CellData, DocxRow, TableStyle};
6
7pub struct Table {
11 headers: Vec<String>,
12 rows: Vec<Vec<CellData>>,
13 style: Option<TableStyle>,
14}
15
16impl Table {
17 #[must_use]
19 pub fn from_data<T: DocxRow>(data: &[T]) -> Self {
20 let headers = T::schema()
21 .iter()
22 .filter(|c| !c.ignored)
23 .map(|c| c.name.clone())
24 .collect();
25
26 let rows = data.iter().filter_map(|item| item.to_row().ok()).collect();
27
28 Self {
29 headers,
30 rows,
31 style: None,
32 }
33 }
34
35 #[must_use]
37 pub fn header_style(mut self, style: TableStyle) -> Self {
38 self.style = Some(style);
39 self
40 }
41
42 #[must_use]
44 pub fn banded_rows(mut self, enabled: bool) -> Self {
45 self.style.get_or_insert_default().banded_rows = enabled;
46 self
47 }
48
49 #[must_use]
51 pub fn auto_width(mut self) -> Self {
52 self.style.get_or_insert_default().auto_width = true;
53 self
54 }
55
56 pub(crate) fn headers(&self) -> &[String] {
57 &self.headers
58 }
59
60 pub(crate) fn rows(&self) -> &[Vec<CellData>] {
61 &self.rows
62 }
63
64 #[allow(dead_code)]
65 pub(crate) fn table_style(&self) -> Option<&TableStyle> {
66 self.style.as_ref()
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[derive(Debug, Clone)]
76 struct TestUser {
77 name: String,
78 age: u32,
79 email: String,
80 }
81
82 impl DocxRow for TestUser {
83 fn schema() -> &'static [easydoc_core::metadata::TableColumn] {
84 static SCHEMA: std::sync::LazyLock<Vec<easydoc_core::metadata::TableColumn>> =
85 std::sync::LazyLock::new(|| {
86 vec![
87 easydoc_core::metadata::TableColumn::new("Name", "name", 0),
88 easydoc_core::metadata::TableColumn::new("Age", "age", 1),
89 easydoc_core::metadata::TableColumn::new("Email", "email", 2),
90 ]
91 });
92 &SCHEMA
93 }
94
95 fn from_row(_row: &easydoc_core::RowData) -> easydoc_core::Result<Self> {
96 unimplemented!()
97 }
98 fn from_row_with_converters(
99 _row: &easydoc_core::RowData,
100 _registry: &easydoc_core::ConverterRegistry,
101 ) -> easydoc_core::Result<Self> {
102 unimplemented!()
103 }
104 fn to_row(&self) -> easydoc_core::Result<Vec<easydoc_core::CellData>> {
105 Ok(vec![
106 easydoc_core::CellData::new(self.name.clone()),
107 easydoc_core::CellData::new(i64::from(self.age)),
108 easydoc_core::CellData::new(self.email.clone()),
109 ])
110 }
111 fn to_row_with_converters(
112 &self,
113 _registry: &easydoc_core::ConverterRegistry,
114 ) -> easydoc_core::Result<Vec<easydoc_core::CellData>> {
115 self.to_row()
116 }
117 }
118
119 #[test]
120 fn table_from_data_empty() {
121 let users: Vec<TestUser> = vec![];
122 let t = Table::from_data(&users);
123 assert!(t.rows().is_empty());
124 assert!(!t.headers().is_empty());
125 }
126
127 #[test]
128 fn table_from_data_with_rows() {
129 let users = vec![
130 TestUser {
131 name: "Alice".into(),
132 age: 30,
133 email: "a@b.com".into(),
134 },
135 TestUser {
136 name: "Bob".into(),
137 age: 25,
138 email: "b@c.com".into(),
139 },
140 ];
141 let t = Table::from_data(&users);
142 assert_eq!(t.rows().len(), 2);
143 assert_eq!(t.headers().len(), 3);
144 }
145
146 #[test]
147 fn table_builder_methods() {
148 let t = Table::from_data::<TestUser>(&[])
149 .banded_rows(true)
150 .auto_width();
151 assert!(t.style.is_some());
152 assert!(t.style.as_ref().unwrap().banded_rows);
153 assert!(t.style.as_ref().unwrap().auto_width);
154 }
155}