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.00';
3 50     50   1111781 use 5.024;
  50         329  
4 50     50   270 use strict;
  50         102  
  50         1075  
5 50     50   254 use warnings;
  50         93  
  50         1404  
6 50     50   7539 use utf8;
  50         273  
  50         480  
7 50     50   6463 use parent 'Exporter';
  50         3328  
  50         369  
8 50     50   3803 use Carp qw/ croak /;
  50         118  
  50         3253  
9 50     50   26755 use Ref::Util 0.202 qw/ is_arrayref /;
  50         87446  
  50         4250  
10 50     50   24852 use String::TtyLength 0.02 qw/ tty_width /;
  50         349694  
  50         96672  
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 610     610 0 27307 my %param = @_;
49              
50 610         1595 foreach my $arg (keys %param) {
51 1036 100       3060 croak "unknown argument '$arg'" if not exists $arguments{$arg};
52             }
53              
54 609 100       1957 my $rows = $param{rows} or croak "you must pass the 'rows' argument!";
55 608         1775 my @rows = @$rows;
56 608         1661 my @widths = _calculate_widths($rows);
57              
58 608   100     2453 $param{style} //= 'classic';
59              
60 608   100     2837 $param{indent} //= '';
61 608 100       1541 $param{indent} = ' ' x $param{indent} if $param{indent} =~ /^[0-9]+$/;
62              
63 608         1246 my $style = $param{style};
64 608 50       1814 croak "unknown style '$style'" if not exists($charsets{ $style });
65 608         1237 my $char = $charsets{$style};
66              
67 608 100       1976 if ($style eq 'classic') {
    100          
68 281         1388 $char->{TLC} = $char->{TRC} = $char->{TT} = $char->{LT} = $char->{RT} = $char->{HC} = $char->{BLC} = $char->{BT} = $char->{BRC} = $CORNER_MARKER;
69 281         590 $char->{HR} = $ROW_SEPARATOR;
70 281         568 $char->{VR} = $COLUMN_SEPARATOR;
71 281         691 $char->{FLT} = $char->{FRT} = $char->{FHC} = $HEADER_CORNER_MARKER;
72 281         647 $char->{FHR} = $HEADER_ROW_SEPARATOR;
73             } elsif ( $style eq 'markdown') {
74 323         1013 _md_validate_data( $rows );
75 320         738 $param{'header_row'} = 1;
76 320         594 $param{'top_and_tail'} = 1;
77 320         580 $param{'separate_rows'} = 0;
78 320         571 $param{'indent'} = '';
79             }
80              
81 605         1196 my $header;
82             my @align;
83 605 100       1527 if (defined $param{align}) {
84             @align = is_arrayref($param{align})
85 6         20 ? @{ $param{align} }
86 13 100       66 : ($param{align}) x int(@widths)
87             ;
88             }
89             else {
90 592         1988 @align = ('l') x int(@widths);
91             }
92              
93 605 100       1690 $header = shift @rows if $param{header_row};
94              
95 605         1675 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 605         2218 chop($table);
101              
102 605         6474 return $table;
103             }
104              
105             sub _top_border
106             {
107 605     605   1452 my ($param, $widths, $char) = @_;
108              
109 605 100       2014 return '' if $param->{top_and_tail};
110 281         907 return _rule_row($param, $widths, $char->{TLC}, $char->{HR}, $char->{TT}, $char->{TRC});
111             }
112              
113             sub _bottom_border
114             {
115 605     605   1515 my ($param, $widths, $char) = @_;
116              
117 605 100       1843 return '' if $param->{top_and_tail};
118 281         880 return _rule_row($param, $widths, $char->{BLC}, $char->{HR}, $char->{BT}, $char->{BRC});
119             }
120              
121             sub _rule_row
122             {
123 904     904   2622 my ($param, $widths, $le, $hr, $cross, $re) = @_;
124 904 50       2194 my $pad = $param->{compact} ? '' : $hr;
125              
126             return $param->{indent}
127             .$le
128 904         2410 .join($cross, map { $pad.($hr x $_).$pad } @$widths)
  3245         11452  
129             .$re
130             ."\n"
131             ;
132             }
133              
134             sub _header_row
135             {
136 605     605   1474 my ($param, $row, $widths, $align, $char) = @_;
137 605 100       2085 return '' unless $param->{header_row};
138              
139 337         896 return _text_row($param, $row, $widths, $align, $char);
140             }
141              
142             sub _md_validate_data {
143 323     323   637 my $rows = shift @_;
144 323         495 for my $row ( @{$rows}) {
  323         695  
145 1792 100       2480 if ("@{$row}" =~ m/[^\\]\|/ ){
  1792         5155  
146 3         5 die "Unescaped | will produce invalid Markdown!\n@{$row}";
  3         38  
147             }
148             }
149             }
150              
151             sub _md_header_rule {
152 7     7   1589 my ($param, $widthref, $alignref ) = @_;
153 7 100       23 my $coladj = $param->{'compact'} ? -2 : 0;
154 7         24 my @align = @{$alignref};
  7         20  
155 7         13 my @width = @{$widthref};
  7         17  
156 7         13 my $rule = '|';
157 7         19 while ( @width) {
158 27         45 my $colwidth = $coladj + shift( @width);
159 27         40 my $colalign = shift( @align);
160 27         51 my $DASHES = '-' x ($colwidth ) ;
161 27 100       62 $rule .= ":$DASHES-|" if ( $colalign eq 'l') ;
162 27 100       59 $rule .= "-$DASHES:|" if ( $colalign eq 'r') ;
163 27 100       66 $rule .= ":$DASHES:|" if ( $colalign eq 'c') ;
164             }
165 7         34 return "$rule\n" ;
166             }
167              
168             sub _header_rule
169             {
170 605     605   1547 my ($param, $widths, $char, $align) = @_;
171 605 100       1546 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 320 100       843 return _md_header_rule($param, $widths, $align) if $param->{'align'};
176             }
177 601 100       2226 return '' unless $param->{header_row};
178 333 100       882 my $fancy = $param->{separate_rows} ? 'F' : '';
179              
180 333         1573 return _rule_row($param, $widths, $char->{"${fancy}LT"}, $char->{"${fancy}HR"}, $char->{"${fancy}HC"}, $char->{"${fancy}RT"});
181             }
182              
183             sub _body
184             {
185 605     605   1409 my ($param, $rows, $widths, $align, $char) = @_;
186 605 100       1435 my $divider = $param->{separate_rows} ? _rule_row($param, $widths, $char->{LT}, $char->{HR}, $char->{HC}, $char->{RT}) : '';
187              
188 605         1218 return join($divider, map { _text_row($param, $_, $widths, $align, $char) } @$rows);
  8325         14722  
189             }
190              
191             sub _text_row
192             {
193 8662     8662   14730 my ($param, $row, $widths, $align, $char) = @_;
194 8662         18058 my @columns = @$row;
195 8662         16311 my $text = $param->{indent}.$char->{VR};
196              
197 8662         18055 for (my $i = 0; $i < @$widths; $i++) {
198 38342   100     101586 $text .= _format_column($columns[$i] // '', $widths->[$i], $align->[$i] // 'l', $param, $char);
      50        
199 38342         502638 $text .= $char->{VR};
200             }
201 8662         12337 $text .= "\n";
202              
203 8662         24624 return $text;
204             }
205              
206             sub _format_column
207             {
208 38342     38342   67404 my ($text, $width, $align, $param, $char) = @_;
209 38342 100       65957 my $pad = $param->{compact} ? '' : ' ';
210              
211 38342 100 100     184478 if ($align eq 'r' || $align eq 'right') {
    100 100        
      66        
212 86         164 return $pad.' ' x ($width - tty_width($text)).$text.$pad;
213             }
214             elsif ($align eq 'c' || $align eq 'center' || $align eq 'centre') {
215 29         61 my $total_spaces = $width - tty_width($text);
216 29         359 my $left_spaces = int($total_spaces / 2);
217 29         44 my $right_spaces = $left_spaces;
218 29 100       67 $right_spaces++ if $total_spaces % 2 == 1;
219 29         96 return $pad.(' ' x $left_spaces).$text.(' ' x $right_spaces).$pad;
220             }
221             else {
222 38227         82435 return $pad.$text.' ' x ($width - tty_width($text)).$pad;
223             }
224             }
225              
226             sub _calculate_widths
227             {
228 608     608   1105 my $rows = shift;
229 608         1035 my @widths;
230 608         1167 foreach my $row (@$rows) {
231 8674         18111 my @columns = @$row;
232 8674         16951 for (my $i = 0; $i < @columns; $i++) {
233 35588 100       62482 next unless defined($columns[$i]);
234              
235 35541         62589 my $width = tty_width($columns[$i]);
236              
237 35541 100 100     507464 $widths[$i] = $width if !defined($widths[$i])
238             || $width > $widths[$i];
239             }
240             }
241 608         1704 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