2021-07-15 00:22:38 -05:00
fn lpad ( s : & str , len : usize ) -> String {
let padding = " " . repeat ( len . saturating_sub ( s . chars ( ) . count ( ) ) ) ;
padding + s
}
fn rpad ( s : & str , len : usize ) -> String {
let padding = " " . repeat ( len . saturating_sub ( s . chars ( ) . count ( ) ) ) ;
s . to_string ( ) + & padding
}
#[ derive(Copy, Clone) ]
pub enum Align {
Left ,
Right ,
}
use Align ::* ;
#[ derive(Copy, Clone) ]
pub struct Col {
min_width : usize ,
align : Align ,
}
impl Col {
pub fn min_width ( size : usize ) -> Col {
Col {
min_width : size ,
align : Align ::Left ,
}
}
pub fn and_alignment ( self , align : Align ) -> Col {
Col {
align ,
.. self
}
}
}
enum DataOrSep {
Data ( Vec < String > ) ,
Sep ( char ) ,
}
pub struct Tabulate {
cols : Vec < Col > ,
widths : Vec < usize > ,
data : Vec < DataOrSep > ,
}
impl Tabulate {
pub fn with_columns ( cols : Vec < Col > ) -> Tabulate {
Tabulate {
widths : cols . iter ( ) . map ( | c | c . min_width ) . collect ( ) ,
cols ,
data : Vec ::new ( ) ,
}
}
pub fn feed ( & mut self , data : Vec < String > ) {
for ( w , d ) in self . widths . iter_mut ( ) . zip ( data . iter ( ) ) {
let count = d . chars ( ) . count ( ) ;
if count > * w {
* w = count ;
}
}
self . data . push ( DataOrSep ::Data ( data ) ) ;
}
pub fn separator ( & mut self , c : char ) {
self . data . push ( DataOrSep ::Sep ( c ) ) ;
}
pub fn print ( self ) -> String {
let widths = self . widths ;
let cols = self . cols ;
self . data . into_iter ( ) . map ( | row | match row {
DataOrSep ::Sep ( c ) = > {
c . to_string ( ) . repeat ( widths . iter ( ) . sum ::< usize > ( ) + widths . len ( ) - 1 ) + " \n "
} ,
DataOrSep ::Data ( d ) = > {
d . into_iter ( ) . zip ( widths . iter ( ) ) . zip ( cols . iter ( ) ) . map ( | ( ( d , & w ) , & c ) | {
match c . align {
Left = > rpad ( & d , w ) ,
Right = > lpad ( & d , w ) ,
}
} ) . collect ::< Vec < _ > > ( ) . join ( " " ) . trim_end ( ) . to_string ( ) + " \n "
} ,
} ) . collect ::< Vec < _ > > ( ) . join ( " " )
}
}
#[ cfg(test) ]
mod tests {
use pretty_assertions ::assert_eq ;
use crate ::test_utils ::PrettyString ;
use super ::* ;
const LONG_NOTE : & 'static str = " chatting with bob about upcoming task, district sharing of images, how the user settings currently works etc. Discussing the fingerprinting / cache busting issue with CKEDITOR, suggesting perhaps looking into forking the rubygem and seeing if we can work in our own changes, however hard that might be. " ;
#[ test ]
fn test_text_output ( ) {
let mut tabs = Tabulate ::with_columns ( vec! [
2021-07-15 00:33:58 -05:00
Col ::min_width ( " Fri Oct 03, 2008 " . len ( ) ) . and_alignment ( Left ) ,
Col ::min_width ( " 12:00:00 - 14:00:00 " . len ( ) ) . and_alignment ( Left ) ,
2021-07-15 00:22:38 -05:00
Col ::min_width ( " Duration " . len ( ) ) . and_alignment ( Right ) ,
Col ::min_width ( " Notes " . len ( ) ) . and_alignment ( Left ) ,
] ) ;
tabs . feed ( vec! [ " Day " . into ( ) , " Start End " . into ( ) , " Duration " . into ( ) , " Notes " . into ( ) ] ) ;
tabs . feed ( vec! [ " Fri Oct 03, 2008 " . into ( ) , " 12:00:00 - 14:00:00 " . into ( ) , " 2:00:00 " . into ( ) , " entry 1 " . into ( ) ] ) ;
tabs . feed ( vec! [ " " . into ( ) , " 16:00:00 - 18:00:00 " . into ( ) , " 2:00:00 " . into ( ) , " entry 2 " . into ( ) ] ) ;
tabs . feed ( vec! [ " " . into ( ) , " " . into ( ) , " 4:00:00 " . into ( ) , " " . into ( ) ] ) ;
tabs . feed ( vec! [ " Sun Oct 05, 2008 " . into ( ) , " 16:00:00 - 18:00:00 " . into ( ) , " 2:00:00 " . into ( ) , " entry 3 " . into ( ) ] ) ;
tabs . feed ( vec! [ " " . into ( ) , " 18:00:00 - " . into ( ) , " 2:00:00 " . into ( ) , " entry 4 " . into ( ) ] ) ;
tabs . feed ( vec! [ " " . into ( ) , " " . into ( ) , " 4:00:00 " . into ( ) , " " . into ( ) ] ) ;
tabs . separator ( '-' ) ;
tabs . feed ( vec! [ " Total " . into ( ) , " " . into ( ) , " 8:00:00 " . into ( ) , " " . into ( ) ] ) ;
assert_eq! ( PrettyString ( & tabs . print ( ) ) , PrettyString ( " \
Day Start End Duration Notes
Fri Oct 03 , 2008 12 :00 :00 - 14 :00 :00 2 :00 :00 entry 1
16 :00 :00 - 18 :00 :00 2 :00 :00 entry 2
4 :00 :00
Sun Oct 05 , 2008 16 :00 :00 - 18 :00 :00 2 :00 :00 entry 3
18 :00 :00 - 2 :00 :00 entry 4
4 :00 :00
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Total 8 :00 :00
" ));
}
#[ test ]
fn test_text_output_long_duration ( ) {
let mut tabs = Tabulate ::with_columns ( vec! [
Col ::min_width ( " Fri Oct 03, 2008 " . len ( ) ) . and_alignment ( Left ) ,
Col ::min_width ( " 12:00:00 - 14:00:00 " . len ( ) ) . and_alignment ( Left ) ,
Col ::min_width ( " Duration " . len ( ) ) . and_alignment ( Right ) ,
Col ::min_width ( " Notes " . len ( ) ) . and_alignment ( Left ) ,
] ) ;
tabs . feed ( vec! [ " Day " . into ( ) , " Start End " . into ( ) , " Duration " . into ( ) , " Notes " . into ( ) ] ) ;
tabs . feed ( vec! [ " Wed Oct 01, 2008 " . into ( ) , " 12:00:00 - 14:00:00+2d " . into ( ) , " 50:00:00 " . into ( ) , " entry 1 " . into ( ) ] ) ;
tabs . feed ( vec! [ " " . into ( ) , " " . into ( ) , " 50:00:00 " . into ( ) , " " . into ( ) ] ) ;
tabs . feed ( vec! [ " Fri Oct 03, 2008 " . into ( ) , " 12:00:00 - 14:00:00 " . into ( ) , " 2:00:00 " . into ( ) , " entry 2 " . into ( ) ] ) ;
tabs . feed ( vec! [ " " . into ( ) , " " . into ( ) , " 2:00:00 " . into ( ) , " " . into ( ) ] ) ;
tabs . separator ( '-' ) ;
tabs . feed ( vec! [ " Total " . into ( ) , " " . into ( ) , " 52:00:00 " . into ( ) , " " . into ( ) ] ) ;
assert_eq! ( PrettyString ( & tabs . print ( ) ) , PrettyString ( " \
Day Start End Duration Notes
Wed Oct 01 , 2008 12 :00 :00 - 14 :00 :00 + 2 d 50 :00 :00 entry 1
50 :00 :00
Fri Oct 03 , 2008 12 :00 :00 - 14 :00 :00 2 :00 :00 entry 2
2 :00 :00
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Total 52 :00 :00
" ));
}
#[ test ]
fn test_text_output_with_ids ( ) {
let mut tabs = Tabulate ::with_columns ( vec! [
Col ::min_width ( 3 ) . and_alignment ( Right ) ,
Col ::min_width ( " Fri Oct 03, 2008 " . len ( ) ) . and_alignment ( Left ) ,
Col ::min_width ( " 12:00:00 - 14:00:00 " . len ( ) ) . and_alignment ( Left ) ,
Col ::min_width ( " Duration " . len ( ) ) . and_alignment ( Right ) ,
Col ::min_width ( " Notes " . len ( ) ) . and_alignment ( Left ) ,
] ) ;
tabs . feed ( vec! [ " ID " . into ( ) , " Day " . into ( ) , " Start End " . into ( ) , " Duration " . into ( ) , " Notes " . into ( ) ] ) ;
tabs . feed ( vec! [ " 1 " . into ( ) , " Fri Oct 03, 2008 " . into ( ) , " 12:00:00 - 14:00:00 " . into ( ) , " 2:00:00 " . into ( ) , " entry 1 " . into ( ) ] ) ;
tabs . feed ( vec! [ " 2 " . into ( ) , " " . into ( ) , " 16:00:00 - 18:00:00 " . into ( ) , " 2:00:00 " . into ( ) , " entry 2 " . into ( ) ] ) ;
tabs . feed ( vec! [ " " . into ( ) , " " . into ( ) , " " . into ( ) , " 4:00:00 " . into ( ) , " " . into ( ) ] ) ;
tabs . feed ( vec! [ " 3 " . into ( ) , " Sun Oct 05, 2008 " . into ( ) , " 16:00:00 - 18:00:00 " . into ( ) , " 2:00:00 " . into ( ) , " entry 3 " . into ( ) ] ) ;
tabs . feed ( vec! [ " 4 " . into ( ) , " " . into ( ) , " 18:00:00 - " . into ( ) , " 2:00:00 " . into ( ) , " entry 4 " . into ( ) ] ) ;
tabs . feed ( vec! [ " " . into ( ) , " " . into ( ) , " " . into ( ) , " 4:00:00 " . into ( ) , " " . into ( ) ] ) ;
tabs . separator ( '-' ) ;
tabs . feed ( vec! [ " " . into ( ) , " Total " . into ( ) , " " . into ( ) , " 8:00:00 " . into ( ) ] ) ;
assert_eq! ( PrettyString ( & tabs . print ( ) ) , PrettyString ( " ID Day Start End Duration Notes
1 Fri Oct 03 , 2008 12 :00 :00 - 14 :00 :00 2 :00 :00 entry 1
2 16 :00 :00 - 18 :00 :00 2 :00 :00 entry 2
4 :00 :00
3 Sun Oct 05 , 2008 16 :00 :00 - 18 :00 :00 2 :00 :00 entry 3
4 18 :00 :00 - 2 :00 :00 entry 4
4 :00 :00
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Total 8 :00 :00
" ));
}
#[ test ]
fn test_text_output_long_note_default_with ( ) {
let mut tabs = Tabulate ::with_columns ( vec! [
2021-07-15 00:33:58 -05:00
Col ::min_width ( " Fri Oct 03, 2008 " . len ( ) ) . and_alignment ( Left ) ,
Col ::min_width ( " 12:00:00 - 14:00:00 " . len ( ) ) . and_alignment ( Left ) ,
2021-07-15 00:22:38 -05:00
Col ::min_width ( " Duration " . len ( ) ) . and_alignment ( Right ) ,
Col ::min_width ( " Notes " . len ( ) ) . and_alignment ( Left ) ,
] ) ;
tabs . feed ( vec! [ " Day " . into ( ) , " Start End " . into ( ) , " Duration " . into ( ) , " Notes " . into ( ) ] ) ;
2021-07-15 00:33:58 -05:00
tabs . feed ( vec! [ " Sun Oct 05, 2008 " . into ( ) , " 16:00:00 - 18:00:00 " . into ( ) , " 2:00:00 " . into ( ) , LONG_NOTE . into ( ) ] ) ;
2021-07-15 00:22:38 -05:00
tabs . feed ( vec! [ " " . into ( ) , " " . into ( ) , " 2:00:00 " . into ( ) , " " . into ( ) ] ) ;
tabs . separator ( '-' ) ;
2021-07-15 00:33:58 -05:00
tabs . feed ( vec! [ " Total " . into ( ) , " " . into ( ) , " 2:00:00 " . into ( ) , " " . into ( ) ] ) ;
2021-07-15 00:22:38 -05:00
assert_eq! ( PrettyString ( & tabs . print ( ) ) , PrettyString ( " \
2021-07-15 00:33:58 -05:00
Day Start End Duration Notes
Sun Oct 05 , 2008 16 :00 :00 - 18 :00 :00 2 :00 :00 chatting with bob about upcoming task ,
district sharing of images , how the user
settings currently works etc . Discussing the
fingerprinting / cache busting issue with
CKEDITOR , suggesting perhaps looking into
forking the rubygem and seeing if we can work
in our own changes , however hard that might
be .
2 :00 :00
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Total 2 :00 :00
2021-07-15 00:22:38 -05:00
" ));
}
#[ test ]
fn test_text_output_long_note_with_ids ( ) {
let mut tabs = Tabulate ::with_columns ( vec! [
2021-07-15 00:33:58 -05:00
Col ::min_width ( 2 ) . and_alignment ( Right ) ,
Col ::min_width ( " Fri Oct 03, 2008 " . len ( ) ) . and_alignment ( Left ) ,
Col ::min_width ( " 12:00:00 - 14:00:00 " . len ( ) ) . and_alignment ( Left ) ,
2021-07-15 00:22:38 -05:00
Col ::min_width ( " Duration " . len ( ) ) . and_alignment ( Right ) ,
Col ::min_width ( " Notes " . len ( ) ) . and_alignment ( Left ) ,
] ) ;
tabs . feed ( vec! [ " ID " . into ( ) , " Day " . into ( ) , " Start End " . into ( ) , " Duration " . into ( ) , " Notes " . into ( ) ] ) ;
tabs . feed ( vec! [ " 60000 " . into ( ) , " Sun Oct 05, 2008 " . into ( ) , " 16:00:00 - 18:00:00 " . into ( ) , " 2:00:00 " . into ( ) , LONG_NOTE . into ( ) ] ) ;
2021-07-15 00:33:58 -05:00
tabs . feed ( vec! [ " " . into ( ) , " " . into ( ) , " " . into ( ) , " 2:00:00 " . into ( ) , " " . into ( ) ] ) ;
2021-07-15 00:22:38 -05:00
tabs . separator ( '-' ) ;
2021-07-15 00:33:58 -05:00
tabs . feed ( vec! [ " " . into ( ) , " Total " . into ( ) , " " . into ( ) , " 2:00:00 " . into ( ) ] ) ;
2021-07-15 00:22:38 -05:00
2021-07-15 00:33:58 -05:00
assert_eq! ( PrettyString ( & tabs . print ( ) ) , PrettyString ( " ID Day Start End Duration Notes
2021-07-15 00:22:38 -05:00
60000 Sun Oct 05 , 2008 16 :00 :00 - 18 :00 :00 2 :00 :00 chatting with bob about upcoming task ,
district sharing of images , how the user
settings currently works etc . Discussing the
fingerprinting / cache busting issue with
CKEDITOR , suggesting perhaps looking into
forking the rubygem and seeing if we can
work in our own changes , however hard that
might be .
2 :00 :00
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Total 2 :00 :00
" ));
}
#[ test ]
fn test_text_output_note_with_line_breaks ( ) {
let mut tabs = Tabulate ::with_columns ( vec! [
2021-07-15 00:33:58 -05:00
Col ::min_width ( " Fri Oct 03, 2008 " . len ( ) ) . and_alignment ( Left ) ,
Col ::min_width ( " 12:00:00 - 14:00:00 " . len ( ) ) . and_alignment ( Left ) ,
2021-07-15 00:22:38 -05:00
Col ::min_width ( " Duration " . len ( ) ) . and_alignment ( Right ) ,
Col ::min_width ( " Notes " . len ( ) ) . and_alignment ( Left ) ,
] ) ;
tabs . feed ( vec! [ " Day " . into ( ) , " Start End " . into ( ) , " Duration " . into ( ) , " Notes " . into ( ) ] ) ;
tabs . feed ( vec! [ " Sun Oct 05, 2008 " . into ( ) , " 16:00:00 - 18:00:00 " . into ( ) , " 2:00:00 " . into ( ) , " first line \n and a second line " . into ( ) ] ) ;
tabs . feed ( vec! [ " " . into ( ) , " " . into ( ) , " 2:00:00 " . into ( ) , " " . into ( ) ] ) ;
tabs . separator ( '-' ) ;
tabs . feed ( vec! [ " Total " . into ( ) , " " . into ( ) , " 2:00:00 " . into ( ) , " " . into ( ) ] ) ;
assert_eq! ( PrettyString ( & tabs . print ( ) ) , PrettyString ( " \
2021-07-15 00:33:58 -05:00
Day Start End Duration Notes
Sun Oct 05 , 2008 16 :00 :00 - 18 :00 :00 2 :00 :00 first line
and a second line
2 :00 :00
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Total 2 :00 :00
2021-07-15 00:22:38 -05:00
" ));
}
#[ test ]
fn note_with_accents ( ) {
let mut tabs = Tabulate ::with_columns ( vec! [
Col ::min_width ( " Fri Oct 03, 2008 " . len ( ) ) . and_alignment ( Left ) ,
Col ::min_width ( " 12:00:00 - 14:00:00 " . len ( ) ) . and_alignment ( Left ) ,
Col ::min_width ( " Duration " . len ( ) ) . and_alignment ( Right ) ,
Col ::min_width ( " Notes " . len ( ) ) . and_alignment ( Left ) ,
] ) ;
tabs . feed ( vec! [ " Day " . into ( ) , " Start End " . into ( ) , " Duration " . into ( ) , " Notes " . into ( ) ] ) ;
tabs . feed ( vec! [ " Sun Oct 05, 2008 " . into ( ) , " 16:00:00 - 18:00:00 " . into ( ) , " 2:00:00 " . into ( ) , " quiúbole " . into ( ) ] ) ;
tabs . feed ( vec! [ " " . into ( ) , " " . into ( ) , " 2:00:00 " . into ( ) , " " . into ( ) ] ) ;
tabs . separator ( '-' ) ;
tabs . feed ( vec! [ " Total " . into ( ) , " " . into ( ) , " 2:00:00 " . into ( ) , " " . into ( ) ] ) ;
assert_eq! ( PrettyString ( & tabs . print ( ) ) , PrettyString ( " \
Day Start End Duration Notes
Sun Oct 05 , 2008 16 :00 :00 - 18 :00 :00 2 :00 :00 quiúbole
2 :00 :00
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Total 2 :00 :00
" ));
}
}