1#![warn(future_incompatible)]
2#![warn(missing_copy_implementations)]
3#![warn(missing_docs)]
4#![warn(nonstandard_style)]
5#![warn(rust_2018_compatibility)]
6#![warn(rust_2018_idioms)]
7#![warn(trivial_casts, trivial_numeric_casts)]
8#![warn(unused)]
9
10#![deny(unsafe_code)]
11
12
13use std::cmp::max;
106use std::fmt;
107use std::iter::repeat;
108
109extern crate unicode_width;
110use unicode_width::UnicodeWidthStr;
111
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116pub enum Alignment {
117
118 Left,
120
121 Right,
123}
124
125
126#[derive(PartialEq, Debug, Clone)]
132pub struct Cell {
133
134 pub contents: String,
136
137 pub width: Width,
139
140 pub alignment: Alignment,
142}
143
144impl From<String> for Cell {
145 fn from(string: String) -> Self {
146 Self {
147 width: UnicodeWidthStr::width(&*string),
148 contents: string,
149 alignment: Alignment::Left,
150 }
151 }
152}
153
154impl<'a> From<&'a str> for Cell {
155 fn from(string: &'a str) -> Self {
156 Self {
157 width: UnicodeWidthStr::width(&*string),
158 contents: string.into(),
159 alignment: Alignment::Left,
160 }
161 }
162}
163
164
165#[derive(PartialEq, Debug, Copy, Clone)]
167pub enum Direction {
168
169 LeftToRight,
172
173 TopToBottom,
176}
177
178
179pub type Width = usize;
181
182
183#[derive(PartialEq, Debug)]
186pub enum Filling {
187
188 Spaces(Width),
190
191 Text(String),
194}
195
196impl Filling {
197 fn width(&self) -> Width {
198 match *self {
199 Filling::Spaces(w) => w,
200 Filling::Text(ref t) => UnicodeWidthStr::width(&t[..]),
201 }
202 }
203}
204
205#[derive(PartialEq, Debug)]
208pub struct GridOptions {
209
210 pub direction: Direction,
213
214 pub filling: Filling,
216}
217
218
219#[derive(PartialEq, Debug)]
220struct Dimensions {
221
222 num_lines: Width,
224
225 widths: Vec<Width>,
228}
229
230impl Dimensions {
231 fn total_width(&self, separator_width: Width) -> Width {
232 if self.widths.is_empty() {
233 0
234 }
235 else {
236 let values = self.widths.iter().sum::<Width>();
237 let separators = separator_width * (self.widths.len() - 1);
238 values + separators
239 }
240 }
241}
242
243
244#[derive(PartialEq, Debug)]
248pub struct Grid {
249 options: GridOptions,
250 cells: Vec<Cell>,
251 widest_cell_length: Width,
252 width_sum: Width,
253 cell_count: usize,
254}
255
256impl Grid {
257
258 pub fn new(options: GridOptions) -> Self {
260 let cells = Vec::new();
261 Self { options, cells, widest_cell_length: 0,
262 width_sum: 0, cell_count: 0 }
263 }
264
265 pub fn reserve(&mut self, additional: usize) {
268 self.cells.reserve(additional)
269 }
270
271 pub fn add(&mut self, cell: Cell) {
273 if cell.width > self.widest_cell_length {
274 self.widest_cell_length = cell.width;
275 }
276 self.width_sum += cell.width;
277 self.cell_count += 1;
278 self.cells.push(cell)
279 }
280
281 pub fn fit_into_width(&self, maximum_width: Width) -> Option<Display<'_>> {
287 self.width_dimensions(maximum_width)
288 .map(|dims| Display {
289 grid: self,
290 dimensions: dims,
291 })
292 }
293
294 pub fn fit_into_columns(&self, num_columns: usize) -> Display<'_> {
297 Display {
298 grid: self,
299 dimensions: self.columns_dimensions(num_columns),
300 }
301 }
302
303 fn columns_dimensions(&self, num_columns: usize) -> Dimensions {
304 let mut num_lines = self.cells.len() / num_columns;
305 if self.cells.len() % num_columns != 0 {
306 num_lines += 1;
307 }
308
309 self.column_widths(num_lines, num_columns)
310 }
311
312 fn column_widths(&self, num_lines: usize, num_columns: usize) -> Dimensions {
313 let mut widths: Vec<Width> = repeat(0).take(num_columns).collect();
314 for (index, cell) in self.cells.iter().enumerate() {
315 let index = match self.options.direction {
316 Direction::LeftToRight => index % num_columns,
317 Direction::TopToBottom => index / num_lines,
318 };
319 widths[index] = max(widths[index], cell.width);
320 }
321
322 Dimensions { num_lines, widths }
323 }
324
325 fn theoretical_max_num_lines(&self, maximum_width: usize) -> usize {
326 let mut theoretical_min_num_cols = 0;
328 let mut col_total_width_so_far = 0;
329
330 let mut cells = self.cells.clone();
331 cells.sort_unstable_by(|a, b| b.width.cmp(&a.width)); for cell in &cells {
334 if cell.width + col_total_width_so_far <= maximum_width {
335 theoretical_min_num_cols += 1;
336 col_total_width_so_far += cell.width;
337 } else {
338 let mut theoretical_max_num_lines = self.cell_count / theoretical_min_num_cols;
339 if self.cell_count % theoretical_min_num_cols != 0 {
340 theoretical_max_num_lines += 1;
341 }
342 return theoretical_max_num_lines;
343 }
344 col_total_width_so_far += self.options.filling.width()
345 }
346
347 1
351 }
352
353 fn width_dimensions(&self, maximum_width: Width) -> Option<Dimensions> {
354 if self.widest_cell_length > maximum_width {
355 return None;
357 }
358
359 if self.cell_count == 0 {
360 return Some(Dimensions { num_lines: 0, widths: Vec::new() });
361 }
362
363 if self.cell_count == 1 {
364 let the_cell = &self.cells[0];
365 return Some(Dimensions { num_lines: 1, widths: vec![ the_cell.width ] });
366 }
367
368 let theoretical_max_num_lines = self.theoretical_max_num_lines(maximum_width);
369 if theoretical_max_num_lines == 1 {
370 return Some(Dimensions {
373 num_lines: 1,
374 widths: self.cells.clone().into_iter().map(|cell| cell.width).collect()
378 });
379 }
380 let mut smallest_dimensions_yet = None;
383 for num_lines in (1 .. theoretical_max_num_lines).rev() {
384
385 let mut num_columns = self.cell_count / num_lines;
388 if self.cell_count % num_lines != 0 {
389 num_columns += 1;
390 }
391
392 let total_separator_width = (num_columns - 1) * self.options.filling.width();
399 if maximum_width < total_separator_width {
400 continue;
401 }
402
403 let adjusted_width = maximum_width - total_separator_width;
405
406 let potential_dimensions = self.column_widths(num_lines, num_columns);
407 if potential_dimensions.widths.iter().sum::<Width>() < adjusted_width {
408 smallest_dimensions_yet = Some(potential_dimensions);
409 } else {
410 return smallest_dimensions_yet;
411 }
412 }
413
414 None
415 }
416}
417
418
419#[derive(PartialEq, Debug)]
424pub struct Display<'grid> {
425
426 grid: &'grid Grid,
428
429 dimensions: Dimensions,
431}
432
433impl Display<'_> {
434
435 pub fn width(&self) -> Width {
438 self.dimensions.total_width(self.grid.options.filling.width())
439 }
440
441 pub fn row_count(&self) -> usize {
443 self.dimensions.num_lines
444 }
445
446 pub fn is_complete(&self) -> bool {
454 self.dimensions.widths.iter().all(|&x| x > 0)
455 }
456}
457
458impl fmt::Display for Display<'_> {
459 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
460 for y in 0 .. self.dimensions.num_lines {
461 for x in 0 .. self.dimensions.widths.len() {
462 let num = match self.grid.options.direction {
463 Direction::LeftToRight => y * self.dimensions.widths.len() + x,
464 Direction::TopToBottom => y + self.dimensions.num_lines * x,
465 };
466
467 if num >= self.grid.cells.len() {
469 continue;
470 }
471
472 let cell = &self.grid.cells[num];
473 if x == self.dimensions.widths.len() - 1 {
474 match cell.alignment {
475 Alignment::Left => {
476 write!(f, "{}", cell.contents)?;
479 },
480 Alignment::Right => {
481 let extra_spaces = self.dimensions.widths[x] - cell.width;
482 write!(f, "{}", pad_string(&cell.contents, extra_spaces, Alignment::Right))?;
483 }
484 }
485 }
486 else {
487 assert!(self.dimensions.widths[x] >= cell.width);
488 match (&self.grid.options.filling, cell.alignment) {
489 (Filling::Spaces(n), Alignment::Left) => {
490 let extra_spaces = self.dimensions.widths[x] - cell.width + n;
491 write!(f, "{}", pad_string(&cell.contents, extra_spaces, cell.alignment))?;
492 },
493 (Filling::Spaces(n), Alignment::Right) => {
494 let s = spaces(*n);
495 let extra_spaces = self.dimensions.widths[x] - cell.width;
496 write!(f, "{}{}", pad_string(&cell.contents, extra_spaces, cell.alignment), s)?;
497 },
498 (Filling::Text(ref t), _) => {
499 let extra_spaces = self.dimensions.widths[x] - cell.width;
500 write!(f, "{}{}", pad_string(&cell.contents, extra_spaces, cell.alignment), t)?;
501 },
502 }
503 }
504 }
505
506 writeln!(f)?;
507 }
508
509 Ok(())
510 }
511}
512
513
514fn spaces(length: usize) -> String {
516 repeat(" ").take(length).collect()
517}
518
519fn pad_string(string: &str, padding: usize, alignment: Alignment) -> String {
524 if alignment == Alignment::Left {
525 format!("{}{}", string, spaces(padding))
526 }
527 else {
528 format!("{}{}", spaces(padding), string)
529 }
530}
531
532
533#[cfg(test)]
534mod test {
535 use super::*;
536
537 #[test]
538 fn no_items() {
539 let grid = Grid::new(GridOptions {
540 direction: Direction::TopToBottom,
541 filling: Filling::Spaces(2),
542 });
543
544 let display = grid.fit_into_width(40).unwrap();
545
546 assert_eq!(display.dimensions.num_lines, 0);
547 assert!(display.dimensions.widths.is_empty());
548
549 assert_eq!(display.width(), 0);
550 }
551
552 #[test]
553 fn one_item() {
554 let mut grid = Grid::new(GridOptions {
555 direction: Direction::TopToBottom,
556 filling: Filling::Spaces(2),
557 });
558
559 grid.add(Cell::from("1"));
560
561 let display = grid.fit_into_width(40).unwrap();
562
563 assert_eq!(display.dimensions.num_lines, 1);
564 assert_eq!(display.dimensions.widths, vec![ 1 ]);
565
566 assert_eq!(display.width(), 1);
567 }
568
569 #[test]
570 fn one_item_exact_width() {
571 let mut grid = Grid::new(GridOptions {
572 direction: Direction::TopToBottom,
573 filling: Filling::Spaces(2),
574 });
575
576 grid.add(Cell::from("1234567890"));
577
578 let display = grid.fit_into_width(10).unwrap();
579
580 assert_eq!(display.dimensions.num_lines, 1);
581 assert_eq!(display.dimensions.widths, vec![ 10 ]);
582
583 assert_eq!(display.width(), 10);
584 }
585
586 #[test]
587 fn one_item_just_over() {
588 let mut grid = Grid::new(GridOptions {
589 direction: Direction::TopToBottom,
590 filling: Filling::Spaces(2),
591 });
592
593 grid.add(Cell::from("1234567890!"));
594
595 assert_eq!(grid.fit_into_width(10), None);
596 }
597
598 #[test]
599 fn two_small_items() {
600 let mut grid = Grid::new(GridOptions {
601 direction: Direction::TopToBottom,
602 filling: Filling::Spaces(2),
603 });
604
605 grid.add(Cell::from("1"));
606 grid.add(Cell::from("2"));
607
608 let display = grid.fit_into_width(40).unwrap();
609
610 assert_eq!(display.dimensions.num_lines, 1);
611 assert_eq!(display.dimensions.widths, vec![ 1, 1 ]);
612
613 assert_eq!(display.width(), 1 + 2 + 1);
614 }
615
616 #[test]
617 fn two_medium_size_items() {
618 let mut grid = Grid::new(GridOptions {
619 direction: Direction::TopToBottom,
620 filling: Filling::Spaces(2),
621 });
622
623 grid.add(Cell::from("hello there"));
624 grid.add(Cell::from("how are you today?"));
625
626 let display = grid.fit_into_width(40).unwrap();
627
628 assert_eq!(display.dimensions.num_lines, 1);
629 assert_eq!(display.dimensions.widths, vec![ 11, 18 ]);
630
631 assert_eq!(display.width(), 11 + 2 + 18);
632 }
633
634 #[test]
635 fn two_big_items() {
636 let mut grid = Grid::new(GridOptions {
637 direction: Direction::TopToBottom,
638 filling: Filling::Spaces(2),
639 });
640
641 grid.add(Cell::from("nuihuneihsoenhisenouiuteinhdauisdonhuisudoiosadiuohnteihaosdinhteuieudi"));
642 grid.add(Cell::from("oudisnuthasuouneohbueobaugceoduhbsauglcobeuhnaeouosbubaoecgueoubeohubeo"));
643
644 assert_eq!(grid.fit_into_width(40), None);
645 }
646
647 #[test]
648 fn that_example_from_earlier() {
649 let mut grid = Grid::new(GridOptions {
650 filling: Filling::Spaces(1),
651 direction: Direction::LeftToRight,
652 });
653
654 for s in &["one", "two", "three", "four", "five", "six", "seven",
655 "eight", "nine", "ten", "eleven", "twelve"]
656 {
657 grid.add(Cell::from(*s));
658 }
659
660 let bits = "one two three four\nfive six seven eight\nnine ten eleven twelve\n";
661 assert_eq!(grid.fit_into_width(24).unwrap().to_string(), bits);
662 assert_eq!(grid.fit_into_width(24).unwrap().row_count(), 3);
663 }
664
665 #[test]
666 fn number_grid_with_pipe() {
667 let mut grid = Grid::new(GridOptions {
668 filling: Filling::Text("|".into()),
669 direction: Direction::LeftToRight,
670 });
671
672 for s in &["one", "two", "three", "four", "five", "six", "seven",
673 "eight", "nine", "ten", "eleven", "twelve"]
674 {
675 grid.add(Cell::from(*s));
676 }
677
678 let bits = "one |two|three |four\nfive|six|seven |eight\nnine|ten|eleven|twelve\n";
679 assert_eq!(grid.fit_into_width(24).unwrap().to_string(), bits);
680 assert_eq!(grid.fit_into_width(24).unwrap().row_count(), 3);
681 }
682
683 #[test]
684 fn numbers_right() {
685 let mut grid = Grid::new(GridOptions {
686 filling: Filling::Spaces(1),
687 direction: Direction::LeftToRight,
688 });
689
690 for s in &["one", "two", "three", "four", "five", "six", "seven",
691 "eight", "nine", "ten", "eleven", "twelve"]
692 {
693 let mut cell = Cell::from(*s);
694 cell.alignment = Alignment::Right;
695 grid.add(cell);
696 }
697
698 let bits = " one two three four\nfive six seven eight\nnine ten eleven twelve\n";
699 assert_eq!(grid.fit_into_width(24).unwrap().to_string(), bits);
700 assert_eq!(grid.fit_into_width(24).unwrap().row_count(), 3);
701 }
702
703 #[test]
704 fn numbers_right_pipe() {
705 let mut grid = Grid::new(GridOptions {
706 filling: Filling::Text("|".into()),
707 direction: Direction::LeftToRight,
708 });
709
710 for s in &["one", "two", "three", "four", "five", "six", "seven",
711 "eight", "nine", "ten", "eleven", "twelve"]
712 {
713 let mut cell = Cell::from(*s);
714 cell.alignment = Alignment::Right;
715 grid.add(cell);
716 }
717
718 let bits = " one|two| three| four\nfive|six| seven| eight\nnine|ten|eleven|twelve\n";
719 assert_eq!(grid.fit_into_width(24).unwrap().to_string(), bits);
720 assert_eq!(grid.fit_into_width(24).unwrap().row_count(), 3);
721 }
722
723 #[test]
724 fn huge_separator() {
725 let mut grid = Grid::new(GridOptions {
726 filling: Filling::Spaces(100),
727 direction: Direction::LeftToRight,
728 });
729
730 grid.add("a".into());
731 grid.add("b".into());
732
733 assert_eq!(grid.fit_into_width(99), None);
734 }
735
736 #[test]
737 fn huge_yet_unused_separator() {
738 let mut grid = Grid::new(GridOptions {
739 filling: Filling::Spaces(100),
740 direction: Direction::LeftToRight,
741 });
742
743 grid.add("abcd".into());
744
745 let display = grid.fit_into_width(99).unwrap();
746
747 assert_eq!(display.dimensions.num_lines, 1);
748 assert_eq!(display.dimensions.widths, vec![ 4 ]);
749
750 assert_eq!(display.width(), 4);
751 }
752}