File Coverage

blib/lib/Text/Table/Tiny.pm
Criterion Covered Total %
statement 98 98 100.0
branch 39 42 92.8
condition 18 20 90.0
subroutine 18 18 100.0
pod 1 1 100.0
total 174 179 97.2


line stmt bran cond sub pod time code
1             package Text::Table::Tiny;
2             $Text::Table::Tiny::VERSION = '1.01_01'; # TRIAL
3              
4 10     10   1027561 $Text::Table::Tiny::VERSION = '1.0101';use 5.010;
  10         127  
5 10     10   54 use strict;
  10         18  
  10         208  
6 10     10   49 use warnings;
  10         18  
  10         290  
7 10     10   6184 use utf8;
  10         151  
  10         56  
8 10     10   4550 use parent 'Exporter';
  10         3112  
  10         56  
9 10     10   592 use Carp qw/ croak /;
  10         24  
  10         532  
10 10     10   4907 use Ref::Util 0.202 qw/ is_arrayref /;
  10         16915  
  10         835  
11 10     10   4538 use String::TtyLength 0.02 qw/ tty_width /;
  10         233351  
  10         13684  
12              
13             our @EXPORT_OK = qw/ generate_table /;
14              
15             # Legacy package globals, that can be used to customise the look.
16             # These are only used in the "classic" style.
17             # I wish I could drop them, but I don't want to break anyone's code.
18             our $COLUMN_SEPARATOR = '|';
19             our $ROW_SEPARATOR = '-';
20             our $CORNER_MARKER = '+';
21             our $HEADER_ROW_SEPARATOR = '=';
22             our $HEADER_CORNER_MARKER = 'O';
23              
24             my %arguments = (
25             rows => "the rows, including a possible header row, of the table",
26             header_row => "if true, indicates that the first row is a header row",
27             separate_rows => "if true, a separate rule will be drawn between each row",
28             top_and_tail => "if true, miss out top and bottom edges of table",
29             align => "either single alignment, or an array per of alignments per col",
30             style => "styling of table, one of classic, boxrule, or norule",
31             indent => "indent every row of the table a certain number of spaces",
32             compact => "narrow columns (no space either side of content)",
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 28     28 1 13853 my %param = @_;
44              
45 28         243 foreach my $arg (keys %param) {
46 78 100       399 croak "unknown argument '$arg'" if not exists $arguments{$arg};
47             }
48              
49 27 100       286 my $rows = $param{rows} or croak "you must pass the 'rows' argument!";
50 26         65 my @rows = @$rows;
51 26         72 my @widths = _calculate_widths($rows);
52              
53 26   100     125 $param{style} //= 'classic';
54              
55 26   100     124 $param{indent} //= '';
56 26 100       80 $param{indent} = ' ' x $param{indent} if $param{indent} =~ /^[0-9]+$/;
57              
58 26         53 my $style = $param{style};
59 26 50       73 croak "unknown style '$style'" if not exists($charsets{ $style });
60 26         52 my $char = $charsets{$style};
61              
62 26 100       69 if ($style eq 'classic') {
63 22         133 $char->{TLC} = $char->{TRC} = $char->{TT} = $char->{LT} = $char->{RT} = $char->{HC} = $char->{BLC} = $char->{BT} = $char->{BRC} = $CORNER_MARKER;
64 22         42 $char->{HR} = $ROW_SEPARATOR;
65 22         42 $char->{VR} = $COLUMN_SEPARATOR;
66 22         56 $char->{FLT} = $char->{FRT} = $char->{FHC} = $HEADER_CORNER_MARKER;
67 22         43 $char->{FHR} = $HEADER_ROW_SEPARATOR;
68             }
69              
70 26         51 my $header;
71             my @align;
72 26 100       71 if (defined $param{align}) {
73             @align = is_arrayref($param{align})
74 3         11 ? @{ $param{align} }
75 9 100       42 : ($param{align}) x int(@widths)
76             ;
77             }
78             else {
79 17         54 @align = ('l') x int(@widths);
80             }
81              
82 26 100       81 $header = shift @rows if $param{header_row};
83              
84 26         75 my $table = _top_border(\%param, \@widths, $char)
85             ._header_row(\%param, $header, \@widths, \@align, $char)
86             ._header_rule(\%param, \@widths, $char)
87             ._body(\%param, \@rows, \@widths, \@align, $char)
88             ._bottom_border(\%param, \@widths, $char);
89 26         91 chop($table);
90              
91 26         125 return $table;
92             }
93              
94             sub _top_border
95             {
96 26     26   62 my ($param, $widths, $char) = @_;
97              
98 26 100       76 return '' if $param->{top_and_tail};
99 22         74 return _rule_row($param, $widths, $char->{TLC}, $char->{HR}, $char->{TT}, $char->{TRC});
100             }
101              
102             sub _bottom_border
103             {
104 26     26   62 my ($param, $widths, $char) = @_;
105              
106 26 100       75 return '' if $param->{top_and_tail};
107 22         73 return _rule_row($param, $widths, $char->{BLC}, $char->{HR}, $char->{BT}, $char->{BRC});
108             }
109              
110             sub _rule_row
111             {
112 70     70   194 my ($param, $widths, $le, $hr, $cross, $re) = @_;
113 70 50       152 my $pad = $param->{compact} ? '' : $hr;
114              
115             return $param->{indent}
116             .$le
117 70         176 .join($cross, map { $pad.($hr x $_).$pad } @$widths)
  247         754  
118             .$re
119             ."\n"
120             ;
121             }
122              
123             sub _header_row
124             {
125 26     26   68 my ($param, $row, $widths, $align, $char) = @_;
126 26 100       98 return '' unless $param->{header_row};
127              
128 17         46 return _text_row($param, $row, $widths, $align, $char);
129             }
130              
131             sub _header_rule
132             {
133 26     26   55 my ($param, $widths, $char) = @_;
134 26 100       89 return '' unless $param->{header_row};
135 17 100       52 my $fancy = $param->{separate_rows} ? 'F' : '';
136              
137 17         86 return _rule_row($param, $widths, $char->{"${fancy}LT"}, $char->{"${fancy}HR"}, $char->{"${fancy}HC"}, $char->{"${fancy}RT"});
138             }
139              
140             sub _body
141             {
142 26     26   60 my ($param, $rows, $widths, $align, $char) = @_;
143 26 100       91 my $divider = $param->{separate_rows} ? _rule_row($param, $widths, $char->{LT}, $char->{HR}, $char->{HC}, $char->{RT}) : '';
144              
145 26         55 return join($divider, map { _text_row($param, $_, $widths, $align, $char) } @$rows);
  69         138  
146             }
147              
148             sub _text_row
149             {
150 86     86   155 my ($param, $row, $widths, $align, $char) = @_;
151 86         165 my @columns = @$row;
152 86         183 my $text = $param->{indent}.$char->{VR};
153              
154 86         190 for (my $i = 0; $i < @$widths; $i++) {
155 299   100     910 $text .= _format_column($columns[$i] // '', $widths->[$i], $align->[$i] // 'l', $param, $char);
      50        
156 299         3699 $text .= $char->{VR};
157             }
158 86         131 $text .= "\n";
159              
160 86         333 return $text;
161             }
162              
163             sub _format_column
164             {
165 299     299   521 my ($text, $width, $align, $param, $char) = @_;
166 299 50       553 my $pad = $param->{compact} ? '' : ' ';
167              
168 299 100 100     1436 if ($align eq 'r' || $align eq 'right') {
    100 100        
      66        
169 12         25 return $pad.' ' x ($width - tty_width($text)).$text.$pad;
170             }
171             elsif ($align eq 'c' || $align eq 'center' || $align eq 'centre') {
172 24         47 my $total_spaces = $width - tty_width($text);
173 24         339 my $left_spaces = int($total_spaces / 2);
174 24         29 my $right_spaces = $left_spaces;
175 24 100       50 $right_spaces++ if $total_spaces % 2 == 1;
176 24         82 return $pad.(' ' x $left_spaces).$text.(' ' x $right_spaces).$pad;
177             }
178             else {
179 263         561 return $pad.$text.' ' x ($width - tty_width($text)).$pad;
180             }
181             }
182              
183             sub _calculate_widths
184             {
185 26     26   48 my $rows = shift;
186 26         42 my @widths;
187 26         56 foreach my $row (@$rows) {
188 86         196 my @columns = @$row;
189 86         207 for (my $i = 0; $i < @columns; $i++) {
190 252 100       491 next unless defined($columns[$i]);
191              
192 205         405 my $width = tty_width($columns[$i]);
193              
194 205 100 100     16916 $widths[$i] = $width if !defined($widths[$i])
195             || $width > $widths[$i];
196             }
197             }
198 26         75 return @widths;
199             }
200              
201             # Back-compat: 'table' is an alias for 'generate_table', but isn't exported
202             *table = \&generate_table;
203              
204             1;
205              
206             __END__