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