File Coverage

blib/lib/Mojolicious/Plugin/ReplyTable.pm
Criterion Covered Total %
statement 68 68 100.0
branch 16 20 80.0
condition 6 7 85.7
subroutine 13 13 100.0
pod 1 2 50.0
total 104 110 94.5


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::ReplyTable;
2              
3 1     1   550 use Mojo::Base 'Mojolicious::Plugin';
  1         1  
  1         6  
4              
5             our $VERSION = '0.09';
6             $VERSION = eval $VERSION;
7              
8 1     1   176 use Mojo::Util;
  1         1  
  1         759  
9              
10             sub register {
11 1     1 1 34 my ($plugin, $app, $config) = @_;
12 1         2 $plugin->setup_types($app);
13 1         6 push @{$app->renderer->classes}, __PACKAGE__;
  1         4  
14 1         15 $app->helper( 'reply.table' => \&_reply_table );
15             }
16              
17             sub _reply_table {
18 17     17   280075 my $c = shift;
19 17 100       39 my $default = ref $_[0] ? undef : shift;
20 17   50     41 my $data = shift || die 'table data is required';
21             my %respond = (
22             json => { json => $data },
23             html => { template => 'reply_table', 'reply_table.table' => $data },
24 2     2   453 csv => sub { $_[0]->render(text => _to_csv($_[0], $data)) },
25 3     3   695 txt => sub { $_[0]->render(text => _to_txt($_[0], $data)) },
26 2     2   544 xls => sub { $_[0]->render(data => _to_xls($_[0], $data)) },
27 2     2   578 xlsx => sub { $_[0]->render(data => _to_xlsx($_[0], $data)) },
28             @_
29 17         185 );
30 17 100       40 if ($default) {
31 3 100       2 $c->stash(format => $default) unless @{$c->accepts};
  3         16  
32             }
33 17         923 $c->respond_to(%respond);
34             }
35              
36             sub _to_csv {
37 2     2   3 my ($c, $data) = @_;
38 2         9 require Text::CSV;
39 2   100     4 my $csv_options = $c->stash('reply_table.csv_options') || {};
40 2 50       21 $csv_options->{binary} = 1 unless exists $csv_options->{binary};
41 2         11 my $csv = Text::CSV->new($csv_options);
42 2         180 my $string = '';
43 2         4 for my $row (@$data) {
44 6 50       29 $csv->combine(@$row) || die $csv->error_diag;
45 6         98 $string .= $csv->string . "\n";
46             }
47 2         22 return $string;
48             }
49              
50             sub _to_txt {
51 3     3   4 my ($c, $data) = @_;
52 3 100 100     7 if (!$c->stash('reply_table.tablify') && eval{ require Text::Table::Tiny; 1 }) {
  2         28  
  1         4  
53 1         4 return Text::Table::Tiny::table(
54             rows => $data,
55             header_row => $c->stash('reply_table.header_row'),
56             separate_rows => $c->stash('reply_table.separate_rows'),
57             );
58             } else {
59 2         1079 return Mojo::Util::tablify($data);
60             }
61             }
62              
63             sub _to_xls {
64 2     2   3 my ($c, $data) = @_;
65 2 100       4 unless (eval{ require Spreadsheet::WriteExcel; 1 }) {
  2         16  
  1         3  
66 1         968 $c->rendered(406);
67 1         161 return '';
68             }
69 1 50       25 open my $xfh, '>', \my $fdata or die "Failed to open filehandle: $!";
70 1         6 my $workbook = Spreadsheet::WriteExcel->new( $xfh );
71 1         6117 my $worksheet = $workbook->add_worksheet();
72 1         442 $worksheet->write_col('A1', $data);
73 1         2788 $workbook->close();
74 1         3687 return $fdata;
75             };
76              
77             sub _to_xlsx {
78 2     2   4 my ($c, $data) = @_;
79 2 100       2 unless (eval{ require Excel::Writer::XLSX; 1 }) {
  2         17  
  1         4  
80 1         947 $c->rendered(406);
81 1         114 return '';
82             }
83 1 50       18 open my $xfh, '>', \my $fdata or die "Failed to open filehandle: $!";
84 1         6 my $workbook = Excel::Writer::XLSX->new( $xfh );
85 1         195 my $worksheet = $workbook->add_worksheet();
86 1         204 $worksheet->write_col('A1', $data);
87 1         505 $workbook->close();
88 1         18625 return $fdata;
89             };
90              
91             sub setup_types {
92 1     1 0 2 my ($plugin, $app) = @_;
93 1         5 my $types = $app->types;
94 1         18 $types->type(csv => [qw{text/csv application/csv}]);
95 1         40 $types->type(xls => [qw{
96             application/vnd.ms-excel application/msexcel application/x-msexcel application/x-ms-excel
97             application/x-excel application/x-dos_ms_excel application/xls
98             }]);
99 1         7 $types->type(xlsx => ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']);
100             }
101              
102             1;
103              
104             =encoding utf8
105              
106             =head1 NAME
107              
108             Mojolicious::Plugin::ReplyTable - Easily render rectangular data in many formats using Mojolicious
109              
110             =head1 SYNOPSIS
111              
112             use Mojolicious::Lite;
113             plugin 'ReplyTable';
114              
115             any '/table' => sub {
116             my $c = shift;
117             my $data = [
118             [qw/a b c d/],
119             [qw/e f g h/],
120             ];
121             $c->reply->table($data);
122             };
123              
124             app->start;
125              
126             =head1 DESCRIPTION
127              
128             L adds the C<< reply->table >> helper which can render a table of data in one of several user-selected formats.
129             The format is selected by the client via the usual Mojolicious L mechanisms.
130              
131             Loading the plugin also sets up several MIME types (using L, see L), and appends the module to the list of rendering classes (See L).
132              
133             =head1 HELPERS
134              
135             =head2 reply->table
136              
137             $c->reply->table([[...], [...], ... ]]);
138             $c->reply->table($default => $data, html => sub { ... });
139              
140             Renders an arrayref of arrayrefs (the inner arrayref being a row) in one of several formats listed below.
141             An optional leading argument is used as the default format when one is not otherwise requested.
142             Optional trailing key-value pairs are merged into the arguments to L.
143              
144             Any additional options, particularly those governing formatting details, are via stash keys prefixed by C.
145             Note that the prefix C is reserved for internal use.
146              
147             The formats currently include:
148              
149             =head3 csv
150              
151              
152             Implemented via L using the default values with C enabled.
153             To override these defaults set the stash key C to a hashref containing attributes to pass to Text::CSV.
154             For example, to create a PSV (pipe delimited) file:
155              
156             $c->stash('reply_table.csv_options' => { sep_char => "|" });
157              
158             See L for available options.
159              
160             =head3 html
161              
162             Implemented via the standard L rendering functionality and a template named C.
163             Setting the stash key C to a true value will cause the default template to use the first row as header values.
164             This default template may be overloaded to change the formatting, the table is available to the template via the stash key C.
165              
166             =head3 json
167              
168             Implemented via the standard L handling.
169              
170             =head3 txt
171              
172             A textual representation of the table.
173             This format is intended for human consumption and the specific formatting should not be relied upon.
174              
175             If L is available, it will be used to format the data (can be overridden with C).
176             It can be controlled via the stash keys C and C as noted in that module's documentation.
177             Otherwise it is generated via L.
178              
179             =head3 xls
180              
181             Binary Microsoft Excel format (for older editions of Excel), provided by optional module L.
182             If that module is not installed, the client will receive an error status 406.
183              
184             =head3 xlsx
185              
186             XML Microsoft Excel format (for newer editions of Excel), provided by optional module L.
187             If that module is not installed, the client will receive an error status 406.
188              
189             =head1 METHODS
190              
191             This module inherits all the methods from L and implements the following new ones
192              
193             =head2 register
194              
195             The typical mechanism of loading a L.
196             No pass-in options are currently available.
197              
198             =head1 FUTURE WORK
199              
200             Beyond what is mentioned in the specifc formats above, the following work is planned.
201             If any of it tickles your fancy, pull-requests are always welcome.
202              
203             =over
204              
205             =item *
206              
207             Better tests for generated Excel documents.
208              
209             =item *
210              
211             Exposing the formatters so that they can be used directly.
212              
213             =item *
214              
215             Add additional formats, like OpenOffice/LibreOffice.
216             If needed these can be appended via additional handlers to the helper.
217              
218             =back
219              
220             =head1 SEE ALSO
221              
222             =over
223              
224             =item L
225              
226             =item L
227              
228             =back
229              
230             =head1 SOURCE REPOSITORY
231              
232             L
233              
234             =head1 SPECIAL THANKS
235              
236             Pharmetika Software, L
237              
238             =head1 AUTHOR
239              
240             Joel Berger, Ejoel.a.berger@gmail.comE
241              
242             =head1 CONTRIBUTORS
243              
244             =over
245              
246             Nils Diewald (Akron)
247              
248             Красимир Беров (kberov)
249              
250             Ryan Perry
251              
252             =back
253              
254             =head1 COPYRIGHT AND LICENSE
255              
256             Copyright (C) 2015 by L and L.
257             This library is free software; you can redistribute it and/or modify
258             it under the same terms as Perl itself.
259              
260             =cut
261              
262             __DATA__