File Coverage

blib/lib/Perinci/Sub/Property/result/table.pm
Criterion Covered Total %
statement 29 51 56.8
branch 1 16 6.2
condition 2 15 13.3
subroutine 7 8 87.5
pod n/a
total 39 90 43.3


line stmt bran cond sub pod time code
1             package Perinci::Sub::Property::result::table;
2              
3             our $DATE = '2015-09-04'; # DATE
4             our $VERSION = '0.07'; # VERSION
5              
6 1     1   4078 use 5.010001;
  1         3  
7 1     1   4 use strict;
  1         2  
  1         18  
8 1     1   4 use warnings;
  1         1  
  1         28  
9             #use Log::Any '$log';
10              
11 1     1   712 use Locale::TextDomain::UTF8 'Perinci-Sub-Property-result-table';
  1         16963  
  1         9  
12 1     1   14196 use Perinci::Object::Metadata;
  1         2246  
  1         33  
13 1     1   760 use Perinci::Sub::PropertyUtil qw(declare_property);
  1         770  
  1         598  
14              
15             declare_property(
16             name => 'result/table',
17             type => 'function',
18             schema => ['hash*'],
19             wrapper => {
20             meta => {
21             v => 2,
22             prio => 50,
23             },
24             handler => sub {
25 2     2   32069 my ($self, %args) = @_;
26 2   33     15 my $v = $args{new} // $args{value} // {};
      50        
27 2         3 my $meta = $args{meta};
28              
29             # add format_options
30             {
31 2 50       4 last if $meta->{result_naked};
  2         10  
32 2         8 $self->select_section('after_call_after_res_validation');
33 2         24 $self->push_lines('# add format_options from result/table hints');
34 2         53 $self->push_lines('{');
35 2         28 $self->indent;
36             $self->push_lines(
37             # we are in a local block, so no need to use _w_ prefixes
38             # for vars or even use add_var()
39             'last unless ref($_w_res->[2]) eq "ARRAY";',
40             'my $firstrow = $_w_res->[2][0] or last;', # deduce type from first row
41 2         24 'my $tablespec = '.$self->{_args}{meta_name}.'->{result}{table}{spec} or last;',
42             'my $tct = {};',
43             'my $tco;',
44             'if (ref($firstrow) eq "ARRAY" && $_w_res->[3]{"table.fields"}) {',
45             ' my $field_names = $_w_res->[3]{"table.fields"};', # map column\d to field names
46             ' for (0..@$field_names-1) {',
47             ' next if defined($tct->{$_});',
48             ' my $sch = $tablespec->{fields}{$field_names->[$_]}{schema} or next;', # field is unknown in table spec
49             ' my $type = ref($sch) eq "ARRAY" ? $sch->[0] : $sch;',
50             ' $type =~ s/\\*$//;',
51             ' $tct->{"column$_"} = $type;',
52             ' }',
53             '} elsif (ref($firstrow) eq "HASH") {',
54             ' my $fields = [keys %$firstrow];', # XXX should we check from several/all rows to collect more complete keys?
55             ' $tco = [sort {($tablespec->{fields}{$a}{pos} // $tablespec->{fields}{$a}{index} // 9999) <=> ($tablespec->{fields}{$b}{pos} // $tablespec->{fields}{$b}{index} // 9999)} @$fields];',
56             ' for (@$fields) {',
57             ' my $sch = $tablespec->{fields}{$_}{schema} or next;', # field is unknown in table spec
58             ' my $type = ref($sch) eq "ARRAY" ? $sch->[0] : $sch;',
59             ' $type =~ s/\\*$//;',
60             ' $tct->{$_} = $type;',
61             ' }',
62             '} else {',
63             ' last;',
64             '}',
65             'my $rfo = {};',
66             '$rfo->{table_column_types} = [$tct] if $tct;',
67             '$rfo->{table_column_orders} = [$tco] if $tco;',
68             '$_w_res->[3]{format_options} //= {};',
69             '$_w_res->[3]{format_options}{any} //= $rfo;',
70             );
71 2         137 $self->unindent;
72 2         17 $self->push_lines('}');
73             }
74              
75             # TODO validate table data, if requested
76             },
77             },
78             cmdline_help => {
79             meta => {
80             prio => 50,
81             },
82             handler => sub {
83 0     0     my ($self, $r) = @_;
84 0           my $meta = $r->{_help_meta};
85             my $table_spec = $meta->{result}{table}{spec}
86 0 0         or return undef;
87 0           my $text = __("Returns table data. Table fields are as follow:");
88 0           $text .= "\n\n";
89 0           my $ff = $table_spec->{fields};
90             # reminder: index property is for older spec, will be removed
91             # someday
92 0           for my $fn (sort {($ff->{$a}{pos}//$ff->{$a}{index}//0) <=>
93 0   0       ($ff->{$b}{pos}//$ff->{$b}{index}//0)}
      0        
      0        
      0        
94             keys %$ff) {
95 0           my $f = $ff->{$fn};
96 0           my $fo = Perinci::Object::Metadata->new($f);
97 0           my $sum = $fo->langprop("summary");
98 0           my $type;
99 0 0         if ($f->{schema}) {
100             $type = ref($f->{schema}) eq 'ARRAY' ?
101 0 0         $f->{schema}[0] : $f->{schema};
102 0           $type =~ s/\*$//;
103             }
104             $text .=
105             join("",
106             " - *$fn*",
107             ($type ? " ($type)" : ""),
108 0 0         $table_spec->{pk} eq $fn ?
    0          
    0          
109             " (".__x("ID field").")":"",
110             $sum ? ": $sum" : "",
111             "\n\n");
112 0           my $desc = $fo->langprop("description");
113 0 0         if ($desc) {
114 0           $desc =~ s/(\r?\n)+\z//;
115 0           $desc =~ s/^/ /mg;
116 0           $text .= "$desc\n\n";
117             }
118             }
119 0           $text;
120             },
121             }, # cmdline_help
122             );
123              
124              
125             1;
126             # ABSTRACT: Specify table data in result
127              
128             __END__
129              
130             =pod
131              
132             =encoding UTF-8
133              
134             =head1 NAME
135              
136             Perinci::Sub::Property::result::table - Specify table data in result
137              
138             =head1 VERSION
139              
140             This document describes version 0.07 of Perinci::Sub::Property::result::table (from Perl distribution Perinci-Sub-Property-result-table), released on 2015-09-04.
141              
142             =head1 SYNOPSIS
143              
144             In function L<Rinci> metadata:
145              
146             result => {
147             table => {
148             spec => {
149             summary => "Employee's' current salary",
150             fields => {
151             name => {
152             summary => "Employee's name",
153             schema => 'str*',
154             pos => 0,
155             },
156             position => {
157             summary => "Employee's current position",
158             schema => 'str*',
159             pos => 1,
160             },
161             salary => {
162             summary => "Employee's current monthly salary",
163             schema => 'float*',
164             pos => 2,
165             },
166             },
167             pk => 'name',
168             },
169             # allow_extra_fields => 0,
170             # allow_underscore_fields => 0,
171             },
172             ...
173             }
174              
175             =head1 DESCRIPTION
176              
177             If your function returns table data, either in the form of array (single-column
178             rows):
179              
180             ["andi", "budi", "cinta", ...]
181              
182             or array of arrays (CSV-like):
183              
184             [
185             ["andi" , "manager", 12_000_000],
186             ["budi" , "staff", 5_000_000],
187             ["cinta", "junior manager", 7_500_000],
188             # ...
189             ]
190              
191             or array of hashes (with field names):
192              
193             [
194             {name=>"andi" , position=>"manager", salary=>12_000_000},
195             {name=>"budi" , position=>"staff", salary=> 5_000_000},
196             {name=>"cinta", position=>"junior manager", salary=> 7_500_000},
197             # ...
198             ]
199              
200             then you might want to add a C<table> property inside your C<result> property of
201             your function metadata. This module offers several things:
202              
203             =over
204              
205             =item *
206              
207             When your function is run under L<Perinci::CmdLine>, your tables will look
208             prettier. This is done via adding C<format_options> property to your function
209             result metadata, giving hints to the L<Data::Format::Pretty> formatter.
210              
211             Also when you use --help (--verbose), the table structure is described in the
212             Result section.
213              
214             =item *
215              
216             (NOT YET IMPLEMENTED) When you generate documentation, the table specification
217             is also included in the documentation.
218              
219             =item *
220              
221             (NOT YET IMPLEMENTED, IDEA) The user can also perhaps request the table
222             specification, e.g. C<yourfunc --help=result-table-spec>, C<yourfunc
223             --result-table-spec>.
224              
225             =item *
226              
227             (NOT YET IMPLEMENTED) The wrapper code can optionally validate your function
228             result, making sure that your resulting table conforms to the table
229             specification.
230              
231             =item *
232              
233             (NOT YET IMPLEMENTED, IDEA) The wrapper code can optionally filter, summarize,
234             or sort the table on the fly before returning the final result to the user.
235              
236             (Alternatively, you can pipe the output to another tool like B<jq>, just like a
237             la Unix toolbox philosophy).
238              
239             =back
240              
241             =head1 SPECIFICATION
242              
243             The value of the C<table> property should be a L<DefHash>. Known properties:
244              
245             =over
246              
247             =item * spec => DEFHASH
248              
249             Required. Table data specification, specified using L<TableDef>.
250              
251             =item * allow_extra_fields => BOOL (default: 0)
252              
253             Whether to allow the function to return extra fields other than the ones
254             specified in C<spec>. This is only relevant when function returns array of
255             hashes (i.e. when the field names are present). And this is only relevant when
256             validating the table data.
257              
258             =item * allow_underscore_fields => BOOL (default: 0)
259              
260             Like C<allow_extra_fields>, but regulates whether to allow any extra fields
261             prefixed by an underscore. Underscore-prefixed keys is the DefHash's convention
262             of extra keys that can be ignored.
263              
264             =back
265              
266             =head1 NOTES
267              
268             If you return an array or array of arrays (i.e. no field names), you might want
269             to add C<table.fields> result metadata so the wrapper code can know which
270             element belongs to which field. Example:
271              
272             my $table = [];
273             push @$table, ["andi", 1];
274             push @$table, ["budi", 2];
275             return [200, "OK", $table, {"table.fields"=>[qw/name id/]}];
276              
277             This is not needed if you return array of hashes, since the field names are
278             present as hash keys:
279              
280             my $table = [];
281             push @$table, {name=>"andi", id=>1};
282             push @$table, {name=>"budi", id=>2};
283             return [200, "OK", $table];
284              
285             =head1 RESULT METADATA
286              
287             =over
288              
289             =item * attribute: table.fields => ARRAY OF STR
290              
291             =back
292              
293             =head1 FAQ
294              
295             =head2 Why not use the C<schema> property in the C<result> property?
296              
297             That is, in your function metadata:
298              
299             result => {
300             schema => ['array*', of => ['hash*' => keys => {
301             name => 'str*',
302             position => 'str',
303             salary => ['float*', min => 0],
304             ...
305             }]],
306             },
307              
308             First of all, table data can come in several forms, either a 1-dimensional
309             array, an array of arrays, or an array of hashes. Moreover, when returning an
310             array of arrays, the order of fields can sometimes be changed. The above schema
311             will become more complex if it has to handle all those cases.
312              
313             With the C<table> property, the intent becomes clearer that we want to return
314             table data. We can also specify more aspects aside from just the schema.
315              
316             =head1 HOMEPAGE
317              
318             Please visit the project's homepage at L<https://metacpan.org/release/Perinci-Sub-Property-result-table>.
319              
320             =head1 SOURCE
321              
322             Source repository is at L<https://github.com/perlancar/perl-Perinci-Sub-Property-result-table>.
323              
324             =head1 BUGS
325              
326             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Perinci-Sub-Property-result-table>
327              
328             When submitting a bug or request, please include a test-file or a
329             patch to an existing test-file that illustrates the bug or desired
330             feature.
331              
332             =head1 AUTHOR
333              
334             perlancar <perlancar@cpan.org>
335              
336             =head1 COPYRIGHT AND LICENSE
337              
338             This software is copyright (c) 2015 by perlancar@cpan.org.
339              
340             This is free software; you can redistribute it and/or modify it under
341             the same terms as the Perl 5 programming language system itself.
342              
343             =cut