1extern crate itertools;
58use std::cmp;
59use std::fmt;
60use std::iter;
61use std::vec;
62
63use self::itertools::join;
64
65type Row = Vec<String>;
67
68#[macro_export]
79macro_rules! row {
80 ($($content:expr), *) => ((vec![$($content.to_owned()), *]));
81}
82
83pub struct TableConfig<'a> {
85 width: usize,
86 padding: usize,
87 border: (&'a str, &'a str, &'a str),
88}
89
90impl<'a> Default for TableConfig<'a> {
95 fn default() -> TableConfig<'a> {
96 TableConfig {
97 width: 80,
98 padding: 1,
99 border: ("|", "-", "+"),
100 }
101 }
102}
103
104#[derive(Default)]
105pub struct Table<'a> {
106 title: Option<Row>,
107 rows: Vec<Row>,
108 config: TableConfig<'a>,
109}
110
111impl<'a> Table<'a> {
112 pub fn new(config: TableConfig<'a>) -> Table {
113 Table {
114 title: None,
115 rows: vec![],
116 config: config,
117 }
118 }
119
120 pub fn with_width(width: usize) -> Table<'a> {
122 let mut config = TableConfig::default();
123 config.width = width;
124 Table::new(config)
125 }
126
127 pub fn set_title(&mut self, title: Row) {
129 self.title = Some(title);
130 }
131
132 pub fn add_row(&mut self, row: Row) {
134 self.rows.push(row);
135 }
136
137 pub fn add_rows(&mut self, rows: &mut Vec<Row>) {
139 self.rows.append(rows);
140 }
141
142 fn dimensions(&self) -> Vec<usize> {
143 let dimensions = self.title
144 .iter()
145 .chain(self.rows.iter())
146 .map(|x| x.iter().map(|s| s.len()).collect::<Vec<_>>())
147 .fold(vec::Vec::<usize>::new(), |l, r| max_merge(&l, &r));
148 distribute(&dimensions, self.config.width, self.config.padding)
149 }
150
151 fn fmt_row(
152 &self,
153 row: &[String],
154 dimenstions: &[usize],
155 f: &mut fmt::Formatter,
156 ) -> fmt::Result {
157 let expanded = dimenstions
158 .iter()
159 .zip(row.iter())
160 .map(|(dim, cell)| split(cell, *dim))
161 .collect::<Vec<_>>();
162 let height = expanded.iter().map(|x| x.len()).max().unwrap_or(0);
163 for i in 0..height {
164 let row = join(
165 expanded
166 .iter()
167 .map(|x| {
168 x.get(i)
169 .and_then(|x| Some(x.to_owned()))
170 .unwrap_or_default()
171 })
172 .zip(dimenstions.iter())
173 .map(|(c, w)| {
174 format!("{pad}{cell: <width$}{pad}", pad = " ", width = w, cell = c)
175 }),
176 self.config.border.0,
177 );
178 write!(f, "{}\n", row)?;
179 }
180 Ok(())
181 }
182
183 fn fmt_seperator(&self, dimensions: &[usize], f: &mut fmt::Formatter) -> fmt::Result {
184 let row = join(
185 dimensions.iter().map(|dim| {
186 iter::repeat(self.config.border.1.to_string())
187 .take(dim + self.config.padding * 2)
188 .collect::<String>()
189 }),
190 self.config.border.2,
191 );
192 write!(f, "{}\n", row)
193 }
194}
195
196impl<'a> fmt::Display for Table<'a> {
197 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
198 let dimensions = self.dimensions();
199 if let Some(ref title) = self.title {
200 self.fmt_row(title, &dimensions, f)?;
201 self.fmt_seperator(&dimensions, f)?;
202 }
203 for row in &self.rows {
204 self.fmt_row(row, &dimensions, f)?;
205 }
206 Ok(())
207 }
208}
209
210fn split(cell: &str, w: usize) -> Vec<String> {
211 let mut lines = vec![];
212 let max = cell.len();
213 let mut from = 0;
214 while from < max {
215 let till = cmp::min(from + w, max);
216 let i = if till < max {
217 match cell[from..till].rfind(' ') {
218 Some(i) => i + 1,
219 None => w,
220 }
221 } else {
222 w
223 };
224 let till = cmp::min(from + i, max);
225 lines.push(cell[from..till].trim().to_owned());
226 from += i;
227 }
228 lines
229}
230
231fn flying(col_width: usize, cols: usize, width: usize, padding: usize) -> usize {
232 let space = cols * 2 * padding + (cols - 1);
233 let fair = (width - space) / cols;
234 cmp::min(col_width, fair)
235}
236
237fn max_merge(left: &[usize], right: &[usize]) -> Vec<usize> {
238 let mut merged = left.iter()
239 .zip(right.iter())
240 .map(|(l, r)| *cmp::max(l, r))
241 .collect::<Vec<_>>();
242 let both = merged.len();
243 merged.append(&mut left.iter().skip(both).cloned().collect::<Vec<_>>());
244 merged.append(&mut right.iter().skip(both).cloned().collect::<Vec<_>>());
245 merged
246}
247
248fn distribute(dimensions: &[usize], width: usize, padding: usize) -> Vec<usize> {
249 let mut indexed = dimensions.iter().cloned().enumerate().collect::<Vec<_>>();
250 indexed.sort_by(|a, b| a.1.cmp(&b.1));
251 let mut width = width;
252 let mut cols = dimensions.len();
253 let mut distributed = indexed
254 .iter()
255 .map(|&(i, x)| {
256 let size = flying(x, cols, width, padding);
257 cols -= 1;
258 if cols > 0 {
259 width -= size + 2 * padding + 1;
260 }
261 (i, size)
262 })
263 .collect::<Vec<_>>();
264 distributed.sort_by(|a, b| a.0.cmp(&b.0));
265 distributed.iter().map(|&(_, x)| x).collect()
266}
267
268#[cfg(test)]
269mod tests {
270 macro_rules! ownv {
271 ($($s:expr),*) => { vec!($($s.to_owned()), *) }
272 }
273
274 use super::*;
275 #[test]
276 fn it_works() {
277 let mut table = Table::default();
278 table.set_title(ownv!["who", "what"]);
279 table.add_rows(&mut vec![ownv!["a", "b"], ownv!["c", "d"]]);
280 table.add_row(ownv!["foobar", "foobar2000"]);
281 assert_eq!(table.dimensions(), vec![6, 10]);
282 let out = format!("{}", table);
283 let should = "\
284# who | what #
285#--------+------------#
286# a | b #
287# c | d #
288# foobar | foobar2000 #
289"
290 .replace("#", "");
291 assert_eq!(out, should);
292 }
293
294 #[test]
295 fn test_max_merge() {
296 let l = vec![1, 2, 3];
297 let r = vec![2, 0, 3, 4];
298 assert_eq!(max_merge(&l, &r), vec![2, 2, 3, 4]);
299 let l = vec![];
300 let r = vec![2, 0, 3, 4];
301 assert_eq!(max_merge(&l, &r), r);
302 }
303
304 #[test]
305 fn test_split() {
306 let cell = "foobar2000 foo";
307 assert_eq!(split(cell, 12), ownv!("foobar2000", "foo"));
308 let cell = "foobar2000 foo";
309 assert_eq!(split(cell, 12), ownv!("foobar2000", "foo"));
310 let cell = "foobar2000 foobar2000";
311 assert_eq!(split(cell, 12), ownv!("foobar2000", "foobar2000"));
312 let cell = "foobar2000 foobar2000";
313 assert_eq!(split(cell, 12), ownv!("foobar2000", "", "foobar2000"));
314 }
315
316 #[test]
317 fn test_distribute() {
318 let dims = vec![10, 5, 20, 15];
319 let dis = distribute(&dims, 40, 0);
320 assert_eq!(dis, vec![10, 5, 11, 11]);
321 }
322 #[test]
323 fn test_wrapping() {
324 let mut table = Table::with_width(20);
325 table.add_row(ownv!["da", "foobar foobar", "bar"]);
326 table.add_row(ownv!["da", "foobar!", "bar"]);
327 let out = format!("{}", table);
328 let should = "\
329# da | foobar | bar #
330# | foobar | #
331# da | foobar! | bar #
332"
333 .replace("#", "");
334 assert_eq!(out, should);
335 }
336
337}