File Coverage

blib/lib/Text/Table/Tiny.pm
Criterion Covered Total %
statement 107 107 100.0
branch 52 56 92.8
condition 14 15 93.3
subroutine 18 18 100.0
pod 1 1 100.0
total 192 197 97.4


line stmt bran cond sub pod time code
1             package Text::Table::Tiny;
2             $Text::Table::Tiny::VERSION = '1.03';
3 11     11   847097 use 5.008;
  11         125  
4 11     11   45 use strict;
  11         15  
  11         177  
5 11     11   37 use warnings;
  11         15  
  11         220  
6 11     11   4748 use utf8;
  11         114  
  11         43  
7 11     11   3557 use parent 'Exporter';
  11         2334  
  11         42  
8 11     11   453 use Carp qw/ croak /;
  11         15  
  11         510  
9 11     11   3957 use Ref::Util 0.202 qw/ is_arrayref /;
  11         13251  
  11         634  
10 11     11   3434 use String::TtyLength 0.02 qw/ tty_width /;
  11         187378  
  11         11336  
11              
12             our @EXPORT_OK = qw/ generate_table /;
13              
14             # Legacy package globals, that can be used to customise the look.
15             # These are only used in the "classic" style.
16             # I wish I could drop them, but I don't want to break anyone's code.
17             our $COLUMN_SEPARATOR = '|';
18             our $ROW_SEPARATOR = '-';
19             our $CORNER_MARKER = '+';
20             our $HEADER_ROW_SEPARATOR = '=';
21             our $HEADER_CORNER_MARKER = 'O';
22              
23             my %arguments = (
24             rows => "the rows, including a possible header row, of the table",
25             header_row => "if true, indicates that the first row is a header row",
26             separate_rows => "if true, a separate rule will be drawn between each row",
27             top_and_tail => "if true, miss out top and bottom edges of table",
28             align => "either single alignment, or an array per of alignments per col",
29             style => "styling of table, one of classic, boxrule, or norule",
30             indent => "indent every row of the table a certain number of spaces",
31             compact => "narrow columns (no space either side of content)",
32             header => "array ref with header columns",
33             );
34              
35             my %charsets = (
36             classic => { TLC => '+', TT => '+', TRC => '+', HR => '-', VR => '|', FHR => '=', LT => '+', RT => '+', FLT => 'O', FRT => 'O', HC => '+', FHC => 'O', BLC => '+', BT => '+', BRC => '+' },
37             boxrule => { TLC => '┌', TT => '┬', TRC => '┐', HR => '─', VR => '│', FHR => '═', LT => '├', RT => '┤', FLT => '╞', FRT => '╡', HC => '┼', FHC => '╪', BLC => '└', BT => '┴', BRC => '┘' },
38             norule => { TLC => ' ', TT => ' ', TRC => ' ', HR => ' ', VR => ' ', FHR => ' ', LT => ' ', RT => ' ', FLT => ' ', FRT => ' ', HC => ' ', FHC => ' ', BLC => ' ', BT => ' ', BRC => ' ' },
39             );
40              
41             sub generate_table
42             {
43 32     32 1 14151 my %param = @_;
44              
45 32         95 foreach my $arg (keys %param) {
46 86 100       427 croak "unknown argument '$arg'" if not exists $arguments{$arg};
47             }
48              
49 31 100       277 my $rows = $param{rows} or croak "you must pass the 'rows' argument!";
50 29         56 my @rows = @$rows;
51              
52 29 100 100     72 if (exists($param{header}) && exists($param{header_row})) {
53 1         122 croak "you can't pass both header and header_row arguments";
54             }
55              
56 28 100       60 if (exists $param{header}) {
57 2 100       6 if (not is_arrayref($param{header})) {
58 1         65 croak "the 'header' argument expects an arrayref\n"
59             }
60 1         2 unshift(@rows, $param{header});
61 1         2 $param{header_row} = 1;
62             }
63 27         63 my @widths = _calculate_widths(\@rows);
64              
65 27 100       77 $param{style} = 'classic' if not defined $param{style};
66              
67 27 100       69 $param{indent} = '' if not defined $param{indent};
68 27 100       64 $param{indent} = ' ' x $param{indent} if $param{indent} =~ /^[0-9]+$/;
69              
70 27         38 my $style = $param{style};
71 27 50       59 croak "unknown style '$style'" if not exists($charsets{ $style });
72 27         44 my $char = $charsets{$style};
73              
74 27 100       58 if ($style eq 'classic') {
75 23         113 $char->{TLC} = $char->{TRC} = $char->{TT} = $char->{LT} = $char->{RT} = $char->{HC} = $char->{BLC} = $char->{BT} = $char->{BRC} = $CORNER_MARKER;
76 23         35 $char->{HR} = $ROW_SEPARATOR;
77 23         30 $char->{VR} = $COLUMN_SEPARATOR;
78 23         48 $char->{FLT} = $char->{FRT} = $char->{FHC} = $HEADER_CORNER_MARKER;
79 23         36 $char->{FHR} = $HEADER_ROW_SEPARATOR;
80             }
81              
82 27         40 my $header;
83             my @align;
84 27 100       65 if (defined $param{align}) {
85             @align = is_arrayref($param{align})
86 3         7 ? @{ $param{align} }
87 9 100       34 : ($param{align}) x int(@widths)
88             ;
89             }
90             else {
91 18         48 @align = ('l') x int(@widths);
92             }
93              
94 27 100       60 $header = shift @rows if $param{header_row};
95              
96 27         60 my $table = _top_border(\%param, \@widths, $char)
97             ._header_row(\%param, $header, \@widths, \@align, $char)
98             ._header_rule(\%param, \@widths, $char)
99             ._body(\%param, \@rows, \@widths, \@align, $char)
100             ._bottom_border(\%param, \@widths, $char);
101 27         70 chop($table);
102              
103 27         93 return $table;
104             }
105              
106             sub _top_border
107             {
108 27     27   48 my ($param, $widths, $char) = @_;
109              
110 27 100       59 return '' if $param->{top_and_tail};
111 23         73 return _rule_row($param, $widths, $char->{TLC}, $char->{HR}, $char->{TT}, $char->{TRC});
112             }
113              
114             sub _bottom_border
115             {
116 27     27   49 my ($param, $widths, $char) = @_;
117              
118 27 100       57 return '' if $param->{top_and_tail};
119 23         71 return _rule_row($param, $widths, $char->{BLC}, $char->{HR}, $char->{BT}, $char->{BRC});
120             }
121              
122             sub _rule_row
123             {
124 73     73   119 my ($param, $widths, $le, $hr, $cross, $re) = @_;
125 73 50       120 my $pad = $param->{compact} ? '' : $hr;
126              
127             return $param->{indent}
128             .$le
129 73         130 .join($cross, map { $pad.($hr x $_).$pad } @$widths)
  253         578  
130             .$re
131             ."\n"
132             ;
133             }
134              
135             sub _header_row
136             {
137 27     27   53 my ($param, $row, $widths, $align, $char) = @_;
138 27 100       78 return '' unless $param->{header_row};
139              
140 18         40 return _text_row($param, $row, $widths, $align, $char);
141             }
142              
143             sub _header_rule
144             {
145 27     27   48 my ($param, $widths, $char) = @_;
146 27 100       82 return '' unless $param->{header_row};
147 18 100       41 my $fancy = $param->{separate_rows} ? 'F' : '';
148              
149 18         75 return _rule_row($param, $widths, $char->{"${fancy}LT"}, $char->{"${fancy}HR"}, $char->{"${fancy}HC"}, $char->{"${fancy}RT"});
150             }
151              
152             sub _body
153             {
154 27     27   63 my ($param, $rows, $widths, $align, $char) = @_;
155 27 100       72 my $divider = $param->{separate_rows} ? _rule_row($param, $widths, $char->{LT}, $char->{HR}, $char->{HC}, $char->{RT}) : '';
156              
157 27         40 return join($divider, map { _text_row($param, $_, $widths, $align, $char) } @$rows);
  72         105  
158             }
159              
160             sub _text_row
161             {
162 90     90   115 my ($param, $row, $widths, $align, $char) = @_;
163 90         132 my @columns = @$row;
164 90         137 my $text = $param->{indent}.$char->{VR};
165              
166 90         155 for (my $i = 0; $i < @$widths; $i++) {
167 307         447 $text .= _format_column($columns[$i], $widths->[$i], $align->[$i], $param, $char);
168 307         2869 $text .= $char->{VR};
169             }
170 90         101 $text .= "\n";
171              
172 90         244 return $text;
173             }
174              
175             sub _format_column
176             {
177 307     307   392 my ($text, $width, $align, $param, $char) = @_;
178 307 100       419 $text = '' if not defined $text;
179 307 50       354 $align = 'l' if not defined $align;
180              
181 307 50       388 my $pad = $param->{compact} ? '' : ' ';
182              
183 307 100 100     1048 if ($align eq 'r' || $align eq 'right') {
    100 100        
      66        
184 12         18 return $pad.' ' x ($width - tty_width($text)).$text.$pad;
185             }
186             elsif ($align eq 'c' || $align eq 'center' || $align eq 'centre') {
187 24         31 my $total_spaces = $width - tty_width($text);
188 24         219 my $left_spaces = int($total_spaces / 2);
189 24         22 my $right_spaces = $left_spaces;
190 24 100       41 $right_spaces++ if $total_spaces % 2 == 1;
191 24         56 return $pad.(' ' x $left_spaces).$text.(' ' x $right_spaces).$pad;
192             }
193             else {
194 271         430 return $pad.$text.' ' x ($width - tty_width($text)).$pad;
195             }
196             }
197              
198             sub _calculate_widths
199             {
200 27     27   38 my $rows = shift;
201 27         30 my @widths;
202 27         43 foreach my $row (@$rows) {
203 90         161 my @columns = @$row;
204 90         150 for (my $i = 0; $i < @columns; $i++) {
205 260 100       361 next unless defined($columns[$i]);
206              
207 213         320 my $width = tty_width($columns[$i]);
208              
209 213 100 100     13897 $widths[$i] = $width if !defined($widths[$i])
210             || $width > $widths[$i];
211             }
212             }
213 27         56 return @widths;
214             }
215              
216             # Back-compat: 'table' is an alias for 'generate_table', but isn't exported
217             *table = \&generate_table;
218              
219             1;
220              
221             __END__