File Coverage

blib/lib/Vote/Count/TextTableTiny.pm
Criterion Covered Total %
statement 127 127 100.0
branch 56 58 96.5
condition 18 20 90.0
subroutine 20 20 100.0
pod 0 1 0.0
total 221 226 97.7


line stmt bran cond sub pod time code
1             package Vote::Count::TextTableTiny;
2             $Vote::Count::TextTableTiny::VERSION = '2.01'; # TRIAL
3 50     50   984963 use 5.024;
  50         263  
4 50     50   268 use strict;
  50         103  
  50         984  
5 50     50   225 use warnings;
  50         129  
  50         1191  
6 50     50   6386 use utf8;
  50         228  
  50         398  
7 50     50   6027 use parent 'Exporter';
  50         2930  
  50         407  
8 50     50   3514 use Carp qw/ croak /;
  50         111  
  50         2926  
9 50     50   24114 use Ref::Util 0.202 qw/ is_arrayref /;
  50         77645  
  50         3794  
10 50     50   21965 use String::TtyLength 0.02 qw/ tty_width /;
  50         308739  
  50         84773  
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             );
33              
34             my %charsets = (
35             classic => { TLC => '+', TT => '+', TRC => '+', HR => '-', VR => '|', FHR => '=', LT => '+', RT => '+', FLT => 'O', FRT => 'O', HC => '+', FHC => 'O', BLC => '+', BT => '+', BRC => '+' },
36             boxrule => { TLC => '┌', TT => '┬', TRC => '┐', HR => '─', VR => '│', FHR => '═', LT => '├', RT => '┤', FLT => '╞', FRT => '╡', HC => '┼', FHC => '╪', BLC => '└', BT => '┴', BRC => '┘' },
37             norule => { TLC => ' ', TT => ' ', TRC => ' ', HR => ' ', VR => ' ', FHR => ' ', LT => ' ', RT => ' ', FLT => ' ', FRT => ' ', HC => ' ', FHC => ' ', BLC => ' ', BT => ' ', BRC => ' ' },
38             markdown => {
39             TLC => '|', TT => ' ', TRC => '|', HR => '-',
40             VR => '|', FHR => ' ', LT => '|', RT => '|',
41             FLT => ' ', FRT => ' ', HC => '|', FHC => ' ',
42             BLC => '|', BT => ' ', BRC => '|',
43             },
44             );
45              
46             sub generate_table
47             {
48 714     714 0 26054 my %param = @_;
49              
50 714         1928 foreach my $arg (keys %param) {
51 1224 100       3456 croak "unknown argument '$arg'" if not exists $arguments{$arg};
52             }
53              
54 713 100       2442 my $rows = $param{rows} or croak "you must pass the 'rows' argument!";
55 712         1959 my @rows = @$rows;
56 712         1947 my @widths = _calculate_widths($rows);
57              
58 712   100     2794 $param{style} //= 'classic';
59              
60 712   100     3510 $param{indent} //= '';
61 712 100       1939 $param{indent} = ' ' x $param{indent} if $param{indent} =~ /^[0-9]+$/;
62              
63 712         1360 my $style = $param{style};
64 712 50       1954 croak "unknown style '$style'" if not exists($charsets{ $style });
65 712         1333 my $char = $charsets{$style};
66              
67 712 100       2165 if ($style eq 'classic') {
    100          
68 305         1493 $char->{TLC} = $char->{TRC} = $char->{TT} = $char->{LT} = $char->{RT} = $char->{HC} = $char->{BLC} = $char->{BT} = $char->{BRC} = $CORNER_MARKER;
69 305         614 $char->{HR} = $ROW_SEPARATOR;
70 305         619 $char->{VR} = $COLUMN_SEPARATOR;
71 305         803 $char->{FLT} = $char->{FRT} = $char->{FHC} = $HEADER_CORNER_MARKER;
72 305         655 $char->{FHR} = $HEADER_ROW_SEPARATOR;
73             } elsif ( $style eq 'markdown') {
74 403         1362 _md_validate_data( $rows );
75 400         941 $param{'header_row'} = 1;
76 400         757 $param{'top_and_tail'} = 1;
77 400         716 $param{'separate_rows'} = 0;
78 400         748 $param{'indent'} = '';
79             }
80              
81 709         1297 my $header;
82             my @align;
83 709 100       1627 if (defined $param{align}) {
84             @align = is_arrayref($param{align})
85 6         22 ? @{ $param{align} }
86 13 100       56 : ($param{align}) x int(@widths)
87             ;
88             }
89             else {
90 696         2426 @align = ('l') x int(@widths);
91             }
92              
93 709 100       2046 $header = shift @rows if $param{header_row};
94              
95 709         2014 my $table = _top_border(\%param, \@widths, $char)
96             ._header_row(\%param, $header, \@widths, \@align, $char)
97             ._header_rule(\%param, \@widths, $char, \@align)
98             ._body(\%param, \@rows, \@widths, \@align, $char)
99             ._bottom_border(\%param, \@widths, $char);
100 709         2458 chop($table);
101              
102 709         8395 return $table;
103             }
104              
105             sub _top_border
106             {
107 709     709   1565 my ($param, $widths, $char) = @_;
108              
109 709 100       2525 return '' if $param->{top_and_tail};
110 305         1087 return _rule_row($param, $widths, $char->{TLC}, $char->{HR}, $char->{TT}, $char->{TRC});
111             }
112              
113             sub _bottom_border
114             {
115 709     709   1693 my ($param, $widths, $char) = @_;
116              
117 709 100       2279 return '' if $param->{top_and_tail};
118 305         1016 return _rule_row($param, $widths, $char->{BLC}, $char->{HR}, $char->{BT}, $char->{BRC});
119             }
120              
121             sub _rule_row
122             {
123 1032     1032   2703 my ($param, $widths, $le, $hr, $cross, $re) = @_;
124 1032 50       2288 my $pad = $param->{compact} ? '' : $hr;
125              
126             return $param->{indent}
127             .$le
128 1032         2727 .join($cross, map { $pad.($hr x $_).$pad } @$widths)
  3681         13365  
129             .$re
130             ."\n"
131             ;
132             }
133              
134             sub _header_row
135             {
136 709     709   1684 my ($param, $row, $widths, $align, $char) = @_;
137 709 100       2531 return '' unless $param->{header_row};
138              
139 417         1093 return _text_row($param, $row, $widths, $align, $char);
140             }
141              
142             sub _md_validate_data {
143 403     403   766 my $rows = shift @_;
144 403         626 for my $row ( @{$rows}) {
  403         864  
145 2170 100       2819 if ("@{$row}" =~ m/[^\\]\|/ ){
  2170         6042  
146 3         4 die "Unescaped | will produce invalid Markdown!\n@{$row}";
  3         31  
147             }
148             }
149             }
150              
151             sub _md_header_rule {
152 7     7   1397 my ($param, $widthref, $alignref ) = @_;
153 7 100       19 my $coladj = $param->{'compact'} ? -2 : 0;
154 7         26 my @align = @{$alignref};
  7         19  
155 7         12 my @width = @{$widthref};
  7         13  
156 7         13 my $rule = '|';
157 7         19 while ( @width) {
158 27         38 my $colwidth = $coladj + shift( @width);
159 27         32 my $colalign = shift( @align);
160 27         43 my $DASHES = '-' x ($colwidth ) ;
161 27 100       58 $rule .= ":$DASHES-|" if ( $colalign eq 'l') ;
162 27 100       62 $rule .= "-$DASHES:|" if ( $colalign eq 'r') ;
163 27 100       61 $rule .= ":$DASHES:|" if ( $colalign eq 'c') ;
164             }
165 7         34 return "$rule\n" ;
166             }
167              
168             sub _header_rule
169             {
170 709     709   1755 my ($param, $widths, $char, $align) = @_;
171 709 100       1884 if ( $param->{'style'} eq 'markdown' ) {
172             # the default unaligned markdown header_rule
173             # is similar to other styles. the aligned
174             # header_rule is unique.
175 400 100       975 return _md_header_rule($param, $widths, $align) if $param->{'align'};
176             }
177 705 100       2342 return '' unless $param->{header_row};
178 413 100       1036 my $fancy = $param->{separate_rows} ? 'F' : '';
179              
180 413         1803 return _rule_row($param, $widths, $char->{"${fancy}LT"}, $char->{"${fancy}HR"}, $char->{"${fancy}HC"}, $char->{"${fancy}RT"});
181             }
182              
183             sub _body
184             {
185 709     709   1679 my ($param, $rows, $widths, $align, $char) = @_;
186 709 100       1821 my $divider = $param->{separate_rows} ? _rule_row($param, $widths, $char->{LT}, $char->{HR}, $char->{HC}, $char->{RT}) : '';
187              
188 709         1452 return join($divider, map { _text_row($param, $_, $widths, $align, $char) } @$rows);
  9873         16575  
189             }
190              
191             sub _text_row
192             {
193 10290     10290   17032 my ($param, $row, $widths, $align, $char) = @_;
194 10290         20472 my @columns = @$row;
195 10290         18379 my $text = $param->{indent}.$char->{VR};
196              
197 10290         20263 for (my $i = 0; $i < @$widths; $i++) {
198 45567   100     115268 $text .= _format_column($columns[$i] // '', $widths->[$i], $align->[$i] // 'l', $param, $char);
      50        
199 45567         565479 $text .= $char->{VR};
200             }
201 10290         13832 $text .= "\n";
202              
203 10290         27394 return $text;
204             }
205              
206             sub _format_column
207             {
208 45567     45567   74696 my ($text, $width, $align, $param, $char) = @_;
209 45567 100       73622 my $pad = $param->{compact} ? '' : ' ';
210              
211 45567 100 100     206647 if ($align eq 'r' || $align eq 'right') {
    100 100        
      66        
212 86         144 return $pad.' ' x ($width - tty_width($text)).$text.$pad;
213             }
214             elsif ($align eq 'c' || $align eq 'center' || $align eq 'centre') {
215 29         47 my $total_spaces = $width - tty_width($text);
216 29         289 my $left_spaces = int($total_spaces / 2);
217 29         34 my $right_spaces = $left_spaces;
218 29 100       53 $right_spaces++ if $total_spaces % 2 == 1;
219 29         79 return $pad.(' ' x $left_spaces).$text.(' ' x $right_spaces).$pad;
220             }
221             else {
222 45452         91715 return $pad.$text.' ' x ($width - tty_width($text)).$pad;
223             }
224             }
225              
226             sub _calculate_widths
227             {
228 712     712   1265 my $rows = shift;
229 712         1084 my @widths;
230 712         1471 foreach my $row (@$rows) {
231 10302         20746 my @columns = @$row;
232 10302         18435 for (my $i = 0; $i < @columns; $i++) {
233 42409 100       69839 next unless defined($columns[$i]);
234              
235 42362         69682 my $width = tty_width($columns[$i]);
236              
237 42362 100 100     560061 $widths[$i] = $width if !defined($widths[$i])
238             || $width > $widths[$i];
239             }
240             }
241 712         1814 return @widths;
242             }
243              
244             # Back-compat: 'table' is an alias for 'generate_table', but isn't exported
245             *table = \&generate_table;
246              
247             1;
248              
249             __END__
250              
251             =pod
252              
253             =encoding utf8
254              
255             =head1 NAME
256              
257             Vote::Count::TextTableTiny
258              
259             =head1 SYNOPSIS
260              
261             Don't use this module. It is a fork from a pending Pull Request, and will be withdrawn when the PR merges.
262              
263             =head1 REPOSITORY
264              
265             L<https://github.com/neilb/Text-Table-Tiny>
266              
267             =head1 AUTHOR
268              
269             Neil Bowers <neilb@cpan.org>
270              
271             The original version was written by Creighton Higgins <chiggins@chiggins.com>,
272             but the module was entirely rewritten for 0.05_01.
273              
274             =head1 COPYRIGHT AND LICENSE
275              
276             This software is copyright (c) 2020 by Neil Bowers.
277              
278             This is free software; you can redistribute it and/or modify it under
279             the same terms as the Perl 5 programming language system itself.
280              
281             =cut
282              
283             #FOOTER
284              
285             =pod
286              
287             BUG TRACKER
288              
289             L<https://github.com/brainbuz/Vote-Count/issues>
290              
291             AUTHOR
292              
293             John Karr (BRAINBUZ) brainbuz@cpan.org
294              
295             CONTRIBUTORS
296              
297             Copyright 2019-2021 by John Karr (BRAINBUZ) brainbuz@cpan.org.
298              
299             LICENSE
300              
301             This module is released under the GNU Public License Version 3. See license file for details. For more information on this license visit L<http://fsf.org>.
302              
303             SUPPORT
304              
305             This software is provided as is, per the terms of the GNU Public License. Professional support and customisation services are available from the author.
306              
307             =cut
308