File Coverage

blib/lib/TextTableTiny.pm
Criterion Covered Total %
statement 71 71 100.0
branch 11 20 55.0
condition 2 9 22.2
subroutine 13 13 100.0
pod 2 2 100.0
total 99 115 86.0


line stmt bran cond sub pod time code
1 9     9   126 use 5.006;
  9         26  
2 9     9   38 use strict;
  9         16  
  9         159  
3 9     9   36 use warnings;
  9         22  
  9         406  
4             package TextTableTiny;
5             $TextTableTiny::VERSION = '0.007'; # TRIAL
6 9     9   46 use parent 'Exporter';
  9         14  
  9         93  
7 9     9   648 use List::Util qw();
  9         21  
  9         191  
8 9     9   38 use Carp qw/ croak /;
  9         14  
  9         7444  
9              
10             our @EXPORT_OK = qw/ generate_table generate_markdown_table /;
11              
12             # ABSTRACT: makes simple tables from two-dimensional arrays, with limited templating options
13              
14              
15             our $COLUMN_SEPARATOR = '|';
16             our $ROW_SEPARATOR = '-';
17             our $CORNER_MARKER = '+';
18             our $HEADER_ROW_SEPARATOR = '=';
19             our $HEADER_CORNER_MARKER = 'O';
20              
21             sub generate_table {
22              
23 89     89 1 245 my %params = @_;
24 89 50       199 my $rows = $params{rows} or croak "generate_table(): you must pass the 'rows' argument!";
25              
26             # foreach col, get the biggest width
27 89         146 my $widths = _maxwidths($rows);
28 89         186 my $max_index = _max_array_index($rows);
29              
30             # use that to get the field format and separators
31 89         161 my $format = _get_format($widths);
32 89         167 my $row_sep = _get_row_separator($widths);
33 89         171 my $head_row_sep = _get_header_row_separator($widths);
34              
35             # here we go...
36 89         120 my @table;
37 89 50       178 push(@table, $row_sep) unless $params{top_and_tail};
38              
39             # if the first row's a header:
40 89         109 my $data_begins = 0;
41 89 100       155 if ( $params{header_row} ) {
42 80         98 my $header_row = $rows->[0];
43 80         86 $data_begins++;
44             push @table, sprintf(
45             $format,
46 80 50       150 map { defined($header_row->[$_]) ? $header_row->[$_] : '' } (0..$max_index)
  264         617  
47             );
48 80 50       203 push @table, $params{separate_rows} ? $head_row_sep : $row_sep;
49             }
50              
51             # then the data
52 89         178 my $row_number = 0;
53 89         125 my $last_line_number = int(@$rows);
54 89 100       159 $last_line_number-- if $params{header_row};
55 89         169 foreach my $row ( @{ $rows }[$data_begins..$#$rows] ) {
  89         166  
56 448         450 $row_number++;
57             push(@table, sprintf(
58             $format,
59 448 50       618 map { defined($row->[$_]) ? $row->[$_] : '' } (0..$max_index)
  1443         2810  
60             ));
61              
62 448 0 0     955 push(@table, $row_sep) if $params{separate_rows} && (!$params{top_and_tail} || $row_number < $last_line_number);
      33        
63              
64             }
65              
66             # this will have already done the bottom if called explicitly
67 89 50 33     326 push(@table, $row_sep) unless $params{separate_rows} || $params{top_and_tail};
68 89         149 return join("\n",grep {$_} @table);
  608         1386  
69             }
70              
71             sub generate_markdown_table {
72 89     89 1 152 $CORNER_MARKER = '|';
73 89         105 $HEADER_ROW_SEPARATOR = '-';
74 89         110 $HEADER_CORNER_MARKER = '|';
75 89         165 my @ARGS = (@_);
76 89         207 unshift @ARGS, ( header_row => 1, top_and_tail => 1 );
77 89         161 return TextTableTiny::generate_table(@ARGS);
78             }
79              
80             sub _maxwidths {
81 89     89   109 my $rows = shift;
82             # what's the longest array in this list of arrays?
83 89         158 my $max_index = _max_array_index($rows);
84 89         130 my $widths = [];
85 89         187 for my $i (0..$max_index) {
86             # go through the $i-th element of each array, find the longest
87 282 50       359 my $max = List::Util::max(map {defined $$_[$i] ? length($$_[$i]) : 0} @$rows);
  1707         2573  
88 282         444 push @$widths, $max;
89             }
90 89         126 return $widths;
91             }
92              
93             # return highest top-index from all rows in case they're different lengths
94             sub _max_array_index {
95 178     178   194 my $rows = shift;
96 178         248 return List::Util::max( map { $#$_ } @$rows );
  1056         1387  
97             }
98              
99             sub _get_format {
100 89     89   114 my $widths = shift;
101 89         185 return "$COLUMN_SEPARATOR ".join(" $COLUMN_SEPARATOR ",map { "%-${_}s" } @$widths)." $COLUMN_SEPARATOR";
  282         637  
102             }
103              
104             sub _get_row_separator {
105 89     89   143 my $widths = shift;
106 89         175 return "$CORNER_MARKER$ROW_SEPARATOR".join("$ROW_SEPARATOR$CORNER_MARKER$ROW_SEPARATOR",map { $ROW_SEPARATOR x $_ } @$widths)."$ROW_SEPARATOR$CORNER_MARKER";
  282         581  
107             }
108              
109             sub _get_header_row_separator {
110 89     89   114 my $widths = shift;
111 89         199 return "$HEADER_CORNER_MARKER$HEADER_ROW_SEPARATOR".join("$HEADER_ROW_SEPARATOR$HEADER_CORNER_MARKER$HEADER_ROW_SEPARATOR",map { $HEADER_ROW_SEPARATOR x $_ } @$widths)."$HEADER_ROW_SEPARATOR$HEADER_CORNER_MARKER";
  282         523  
112             }
113              
114             # Back-compat: 'table' is an alias for 'generate_table', but isn't exported
115             *table = \&generate_table;
116              
117             1;
118              
119             __END__
120              
121             =pod
122              
123             =head1 TextTableTiny
124              
125             This is a temporary fork of Neil Bowen's module to support a method I added to set all of the flags for markdown compatible tables. While waiting on Neil to accept or reject the pull request I wanted Vote::Count to be work on other systems.
126              
127             =head1 NAME
128              
129             Text::Table::Tiny - simple text tables from 2D arrays, with limited templating options
130              
131             =head1 SYNOPSIS
132              
133             use Text::Table::Tiny 0.04 qw/ generate_table /;
134              
135             my $rows = [
136             # header row
137             ['Name', 'Rank', 'Serial'],
138             # rows
139             ['alice', 'pvt', '123456'],
140             ['bob', 'cpl', '98765321'],
141             ['carol', 'brig gen', '8745'],
142             ];
143             print generate_table(rows => $rows, header_row => 1);
144              
145              
146             =head1 DESCRIPTION
147              
148             This module provides, C<generate_table>, which formats
149             a two-dimensional array of data as a text table.
150              
151             A second function C<generate_markdown_table>, formats the table
152             as markdown and should not be passed any other formatting directives.
153              
154             The example shown in the SYNOPSIS generates the following table:
155              
156             +-------+----------+----------+
157             | Name | Rank | Serial |
158             +-------+----------+----------+
159             | alice | pvt | 123456 |
160             | bob | cpl | 98765321 |
161             | carol | brig gen | 8745 |
162             +-------+----------+----------+
163              
164             B<NOTE>: the interface changed with version 0.04, so if you
165             use the C<generate_table()> function illustrated above,
166             then you need to require at least version 0.04 of this module,
167             as shown in the SYNOPSIS.
168              
169              
170             =head2 generate_table()
171              
172             The C<generate_table> function understands three arguments,
173             which are passed as a hash.
174              
175             =over 4
176              
177              
178             =item *
179              
180             rows
181              
182             Takes an array reference which should contain one or more rows
183             of data, where each row is an array reference.
184              
185              
186             =item *
187              
188             header_row
189              
190             If given a true value, the first row in the data will be interpreted
191             as a header row, and separated from the rest of the table with a ruled line.
192              
193              
194             =item *
195              
196             separate_rows
197              
198             If given a true value, a separator line will be drawn between every row in
199             the table,
200             and a thicker line will be used for the header separator.
201              
202             =item *
203              
204             top_and_tail
205              
206             If given a true value, then the top and bottom border lines will be skipped.
207             This reduces the vertical height of the generated table.
208              
209             =back
210              
211             =head2 generate_markdown_table()
212              
213             Calls C<generate_table()> with all of the settings and parameters
214             necessary to return a table that is valid for most markdown
215             interpreters.
216              
217             You should not pass or set any other formatting options when using
218             C<generate_markdown_table>.
219              
220             The first row in the data from rows => will be used as the header row.
221              
222             =head2 EXAMPLES
223              
224             If you just pass the data and no other options:
225              
226             generate_table(rows => $rows);
227              
228             You get minimal ruling:
229              
230             +-------+----------+----------+
231             | Name | Rank | Serial |
232             | alice | pvt | 123456 |
233             | bob | cpl | 98765321 |
234             | carol | brig gen | 8745 |
235             +-------+----------+----------+
236              
237             If you want lines between every row, and also want a separate header:
238              
239             generate_table(rows => $rows, header_row => 1, separate_rows => 1);
240              
241             You get the maximally ornate:
242              
243             +-------+----------+----------+
244             | Name | Rank | Serial |
245             O=======O==========O==========O
246             | alice | pvt | 123456 |
247             +-------+----------+----------+
248             | bob | cpl | 98765321 |
249             +-------+----------+----------+
250             | carol | brig gen | 8745 |
251             +-------+----------+----------+
252              
253             If you want your table in MarkDown compatible format:
254              
255             generate_markdown_table( rows => $rows );
256              
257             | Name | Rank | Serial | |
258             |-------|----------|----------|
259             | alice | pvt | 123456 |
260             | bob | cpl | 98765321 |
261             | carol | brig gen | 8745 |
262              
263             =head1 FORMAT VARIABLES
264              
265             You can set a number of package variables inside the C<Text::Table::Tiny> package
266             to configure the appearance of the table.
267             This interface is likely to be deprecated in the future,
268             and some other mechanism provided.
269              
270             =over 4
271              
272             =item *
273              
274             $Text::Table::Tiny::COLUMN_SEPARATOR = '|';
275              
276             =item *
277              
278             $Text::Table::Tiny::ROW_SEPARATOR = '-';
279              
280             =item *
281              
282             $Text::Table::Tiny::CORNER_MARKER = '+';
283              
284             =item *
285              
286             $Text::Table::Tiny::HEADER_ROW_SEPARATOR = '=';
287              
288             =item *
289              
290             $Text::Table::Tiny::HEADER_CORNER_MARKER = 'O';
291              
292             =back
293              
294              
295             =head1 PREVIOUS INTERFACE
296              
297             Prior to version 0.04 this module provided a function called C<table()>,
298             which wasn't available for export. It took exactly the same arguments:
299              
300             use Text::Table::Tiny;
301             my $rows = [ ... ];
302             print Text::Table::Tiny::table(rows => $rows, separate_rows => 1, header_row => 1);
303              
304             For backwards compatibility this interface is still supported.
305             The C<table()> function isn't available for export though.
306              
307              
308             =head1 SEE ALSO
309              
310             There are many modules for formatting text tables on CPAN.
311             A good number of them are listed in the
312             L<See Also|https://metacpan.org/pod/Text::Table::Manifold#See-Also>
313             section of the documentation for L<Text::Table::Manifold>.
314              
315              
316             =head1 REPOSITORY
317              
318             L<https://github.com/neilb/Text-Table-Tiny>
319              
320              
321             =head1 AUTHOR
322              
323             Creighton Higgins <chiggins@chiggins.com>
324              
325             Now maintained by Neil Bowers <neilb@cpan.org>
326              
327             =head1 COPYRIGHT AND LICENSE
328              
329             This software is copyright (c) 2012 by Creighton Higgins.
330              
331             This is free software; you can redistribute it and/or modify it under
332             the same terms as the Perl 5 programming language system itself.
333              
334             =cut
335