File Coverage

blib/lib/Fsdb/Filter/dbroweval.pm
Criterion Covered Total %
statement 18 111 16.2
branch 0 40 0.0
condition n/a
subroutine 6 18 33.3
pod 7 7 100.0
total 31 176 17.6


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             #
4             # dbroweval.pm
5             # Copyright (C) 1991-2015 by John Heidemann
6             # $Id: 8c28f7b692255da8ba7895ca3d5c79edcf00f160 $
7             #
8             # This program is distributed under terms of the GNU general
9             # public license, version 2. See the file COPYING
10             # in $dblibdir for details.
11             #
12              
13             package Fsdb::Filter::dbroweval;
14              
15             =head1 NAME
16              
17             dbroweval - evaluate code for each row of a fsdb file
18              
19             =head1 SYNOPSIS
20              
21             dbroweval [-f CodeFile] code [code...]
22              
23             =head1 DESCRIPTION
24              
25             Evaluate code for each row of the data.
26              
27             Typical actions are things like reformatting
28             and other data transformations.
29              
30             Code can include embedded column names preceded by underscores;
31             these result in the value of that column for the current row.
32              
33             The values of the last row's columns are retrieved with _last_foo
34             where foo is the column name.
35              
36             Even more perverse, _columname(N) is the value of the
37             Nth column after columnname [so _columnname(0) is the also
38             the column's value.
39              
40              
41             =head1 OPTIONS
42              
43             =over 4
44              
45             =item B<-b CODE>
46              
47             Run CODE before reading any data (like awk BEGIN blocks).
48              
49             =item B<-e CODE>
50              
51             Run CODE at the end of all data (like awk END blocks).
52              
53             =item B<-f FILE>
54              
55             Read code from the FILE.
56              
57             =item B<-n> or B<--no-output>
58              
59             no output except for comments and what is in the provided code
60              
61             =item B<-N> or B<--no-output-even-comments>
62              
63             no output at all, except for what is in the provided code
64              
65             =item B<-m> or B<--manual-output>
66              
67             The user must setup output,
68             allowing arbitrary comments.
69             See example 2 below for details.
70              
71             =item B<-w> or B<--warnings>
72              
73             Enable warnings in user supplied code.
74              
75             =item B<--saveoutput $OUT_REF>
76              
77             Save output writer (for integration with other fsdb filters).
78              
79             =back
80              
81             =for comment
82             begin_standard_fsdb_options
83              
84             This module also supports the standard fsdb options:
85              
86             =over 4
87              
88             =item B<-d>
89              
90             Enable debugging output.
91              
92             =item B<-i> or B<--input> InputSource
93              
94             Read from InputSource, typically a file name, or C<-> for standard input,
95             or (if in Perl) a IO::Handle, Fsdb::IO or Fsdb::BoundedQueue objects.
96              
97             =item B<-o> or B<--output> OutputDestination
98              
99             Write to OutputDestination, typically a file name, or C<-> for standard output,
100             or (if in Perl) a IO::Handle, Fsdb::IO or Fsdb::BoundedQueue objects.
101              
102             =item B<--autorun> or B<--noautorun>
103              
104             By default, programs process automatically,
105             but Fsdb::Filter objects in Perl do not run until you invoke
106             the run() method.
107             The C<--(no)autorun> option controls that behavior within Perl.
108              
109             =item B<--help>
110              
111             Show help.
112              
113             =item B<--man>
114              
115             Show full manual.
116              
117             =back
118              
119             =for comment
120             end_standard_fsdb_options
121              
122              
123             =head1 ADVANCED USAGE
124              
125             Typically L outputs a line in the same schema for each input line.
126             For advanced usage, one can violate each of these assumptions.
127              
128             Some fun:
129              
130             =over 4
131              
132             =item B
133              
134             Add the code C
135              
136             =item B
137              
138             Call C<&$write_fastpath_sub($fref)>.
139             You may find C<$fref>, the input row, useful.
140              
141             =item B
142              
143             See the examples below in L
144              
145             =back
146              
147              
148              
149             =head1 SAMPLE USAGE
150              
151             =head2 Input:
152              
153             #fsdb size mean stddev pct_rsd
154             1024 1.4962e+06 2.8497e+05 19.047
155             10240 5.0286e+06 6.0103e+05 11.952
156             102400 4.9216e+06 3.0939e+05 6.2863
157             # | dbsetheader size bw
158             # | /home/johnh/BIN/DB/dbmultistats size bw
159             # | /home/johnh/BIN/DB/dbcol size mean stddev pct_rsd
160              
161             =head2 Command:
162              
163             cat data.fsdb | dbroweval '_mean = sprintf("%8.0f", _mean); _stddev = sprintf("%8.0f", _stddev);'
164              
165             =head2 Output:
166              
167             #fsdb size mean stddev pct_rsd
168             1024 1496200 284970 19.047
169             10240 5028600 601030 11.952
170             102400 4921600 309390 6.2863
171             # | dbsetheader size bw
172             # | /home/johnh/BIN/DB/dbmultistats size bw
173             # | /home/johnh/BIN/DB/dbcol size mean stddev pct_rsd
174             # | /home/johnh/BIN/DB/dbroweval { _mean = sprintf("%8.0f", _mean); _stddev = sprintf("%8.0f", _stddev); }
175              
176              
177             =head2 Command 2: Changing the Schema
178              
179             By default, dbroweval reads and writes the same format file.
180             The recommended method of adding and removing columns is to do so
181             before or after dbroweval. I.e.,
182              
183             cat data.fsdb |
184             dbcolcreate divisible_by_ten |
185             dbroweval '_divisible_by_ten = (_size % 10 == 0);' |
186             dbrow '_divisible_by_ten == 1' |
187             dbcol size mean divisible_by_ten
188              
189             Another approach is to use the C command to skip output of a row.
190             I.e., the equivalent:
191              
192             cat data.fsdb |
193             dbcolcreate divisible_by_ten |
194             dbroweval '_divisible_by_ten = (_size % 10 == 0); next row if (!_divisible_by_ten);' |
195             dbcol size mean divisible_by_ten
196              
197             However, neither of these approachs work very well when the output
198             is a I different schema.
199              
200             The recommended method for schema-changing commands is to write a full
201             filter, but a full filter is a bit heavy weight.
202             As an alternative, one can use the C<-m> option to request
203             manual configuration of the output, then use C<@out_args> to define
204             the output schema (it specifies the C arguments),
205             and C<$ofref> is the output row.
206             It may also reference <$in>, the input C argument,
207             and <$fref> as an aref to the current line.
208             Note that newly created columns I have underscore-names
209              
210             Thus a third equivalent is:
211              
212             cat data.fsdb | \
213             dbroweval -m -b '@out_args = ( -clone => $in, \
214             -cols => ($in->cols, divisible_by_ten); ' \
215             'my $div_by_10 = (_size % 10 == 0); \
216             $ofref = [ @$fref, $div_by_10 ] if ($div_by_ten);' |
217             dbcol size mean divisible_by_ten
218              
219             or
220              
221             cat data.fsdb | \
222             dbroweval -m -b '@out_args = ( -clone => $in, \
223             -cols => [qw(size mean divisible_by_ten)] ); ' \
224             'my $div_by_10 = (_size % 10 == 0); \
225             $ofref = [ _mean, _size, $div_by_10 ] if ($div_by_ten);'
226              
227              
228             Finally, one can write different a completely different schema, although
229             it's more work:
230              
231             cat data.fsdb | \
232             dbroweval -m -b '@out_args = (-cols => [qw(size n)]);' \
233             '$ofref = [ _size, 1 ];'
234              
235             writes different columns, and
236              
237             cat data.fsdb | \
238             dbroweval -n -m -b '@out_args = (-cols => [qw(n)]); \
239             my $count = 0;' -e '$ofref = [ $count ];' '$count++;'
240              
241             Is a fancy way to count lines.
242              
243             The begin code block should setup C<@out_args> to be the arguments to a
244             C call, and whatever is in C<$ofref>
245             (if anything) is written for each input line,
246             and once at the end.
247              
248             =head2 Command 3: Fun With Suppressing Output
249              
250             The C<-n> option suppresses default output.
251             Thus, a simple equivalent to F is:
252              
253             dbroweval -n -e '$ofref = $lfref;'
254              
255             Where C<$ofref> is the output fields,
256             which are copied from C<$lfref>, the hereby documented
257             internal representation of the last row.
258             Yes, this is a bit unappetizing, but,
259             in for a penny with C<$ofref>, in for a pound.
260              
261             =head2 Command 4: Extra Ouptut
262              
263             Calling C<&$write_fastpath_sub($fref)> will do extra output,
264             so this simple program will duplicate each line of input
265             (one extra output, plus one regular output for each line of input):
266              
267             dbroweval '&$write_fastpath_sub($fref)'
268              
269              
270             =head1 BUGS
271              
272             Handling of code in files isn't very elegant.
273              
274              
275             =head1 SEE ALSO
276              
277             L
278              
279              
280             =head1 CLASS FUNCTIONS
281              
282             =cut
283              
284             @ISA = qw(Fsdb::Filter);
285             ($VERSION) = 2.0;
286              
287 1     1   8257 use strict;
  1         2  
  1         37  
288 1     1   5 use Pod::Usage;
  1         1  
  1         139  
289              
290 1     1   5 use Fsdb::Support;
  1         2  
  1         44  
291 1     1   4 use Fsdb::Filter;
  1         2  
  1         24  
292 1     1   4 use Fsdb::IO::Reader;
  1         2  
  1         23  
293 1     1   4 use Fsdb::IO::Writer;
  1         1  
  1         1190  
294              
295             =head2 new
296              
297             $filter = new Fsdb::Filter::dbroweval(@arguments);
298              
299             =cut
300              
301             sub new {
302 0     0 1   my $class = shift @_;
303 0           my $self = $class->SUPER::new(@_);
304 0           bless $self, $class;
305 0           $self->set_defaults;
306 0           $self->parse_options(@_);
307 0           $self->SUPER::post_new();
308 0           return $self;
309             }
310              
311             =head2 set_defaults
312              
313             $filter->set_defaults();
314              
315             Internal: set up defaults.
316              
317             =cut
318              
319             sub set_defaults ($) {
320 0     0 1   my($self) = @_;
321 0           $self->SUPER::set_defaults();
322 0           $self->{_beg_code} = [];
323 0           $self->{_end_code} = [];
324 0           $self->{_code_files} = [];
325 0           $self->{_code_lines} = [];
326 0           $self->{_warnings} = undef;
327              
328 0           $self->{_no_output} = undef;
329 0           $self->{_no_output_even_comments} = undef;
330             }
331              
332             =head2 _confirm_ending_semicolon
333              
334             Not a method; but an internal routine to make sure code compiles.
335              
336             =cut
337              
338             sub _confirm_ending_semicolon(@) {
339 0     0     my($c) = @_;
340 0 0         $c = $c . ";" if ($c !~ /\;\s*$/);
341 0           return $c;
342             }
343              
344             =head2 parse_options
345              
346             $filter->parse_options(@ARGV);
347              
348             Internal: parse options
349              
350             =cut
351              
352             sub parse_options ($@) {
353 0     0 1   my $self = shift @_;
354              
355             $self->get_options(\@_,
356 0     0     'help|?' => sub { pod2usage(1); },
357 0     0     'man' => sub { pod2usage(-verbose => 2); },
358             'autorun!' => \$self->{_autorun},
359             'b|begin=s@' => $self->{_beg_code},
360             'close!' => \$self->{_close},
361             'd|debug+' => \$self->{_debug},
362             'e|end=s@' => $self->{_end_code},
363             'f|code-files=s@' => $self->{_code_files},
364 0     0     'i|input=s' => sub { $self->parse_io_option('input', @_); },
365             'log!' => \$self->{_logprog},
366             'm|manual-output' => \$self->{_manual_output},
367             'n|no-output' => \$self->{_no_output},
368             'N|no-output-even-comments' => \$self->{_no_output_even_comments},
369 0     0     'o|output=s' => sub { $self->parse_io_option('output', @_); },
370             'saveoutput=s' => \$self->{_save_output},
371             'w|warnings!' => \$self->{_warnings},
372 0 0         ) or pod2usage(2);
373             # rest is code
374 0           foreach (@_) {
375 0           push(@{$self->{_code_lines}}, _confirm_ending_semicolon($_));
  0            
376             };
377             }
378              
379             =head2 setup
380              
381             $filter->setup();
382              
383             Internal: setup, parse headers.
384              
385             =cut
386              
387             sub setup ($) {
388 0     0 1   my($self) = @_;
389              
390             #
391             # handle files
392             #
393 0           foreach (@{$self->{_code_files}}) {
  0            
394 0 0         open(INF, "<$_") || die $self->{_prog} . ": cannot open ``$_''.\n";
395 0           push(@{$self->{_code_lines}}, _confirm_ending_semicolon(join('', )));
  0            
396 0           close INF;
397             };
398              
399             #
400             # set up reader
401             #
402 0           $self->{_out} = undef;
403 0           my @in_options = ();
404 0 0         if ($self->{_no_output_even_comments}) {
405 0           $self->{_no_output} = 1;
406 0           push(@in_options, -outputheader => 'never');
407             };
408             push(@in_options, (-comment_handler => $self->create_pass_comments_sub))
409 0 0         if (!$self->{_no_output_even_comments});
410 0           $self->finish_io_option('input', @in_options);
411 0           my $read_fastpath_sub = $self->{_in}->fastpath_sub();
412              
413             #
414             # convert code to perl
415             #
416 0           my($PERL_CODE_F, $DB_CODE_A_F, $TITLE_F, $COMMAND_F) = (0..20); # names for the for parts of our foreach bit next:
417 0           $self->{_pretty_args} = "";
418 0           my($beg_code, $code, $end_code);
419 0           my $c;
420 0           my $any_needs_lfref = undef;
421 0           my $this_needs_lfref;
422 0           foreach my $iref ([\$beg_code, $self->{_beg_code}, "BEGIN CODE:", "-b"],
423             [\$code, $self->{_code_lines}, "CODE:", ""],
424             [\$end_code, $self->{_end_code}, "END_CODE:", "-e"]) {
425 0 0         next if ($#{$iref->[$DB_CODE_A_F]} < 0);
  0            
426 0           ($c, $this_needs_lfref) = $self->{_in}->codify(@{$iref->[$DB_CODE_A_F]});
  0            
427 0           ${$iref->[$PERL_CODE_F]} = $c;
  0            
428 0 0         $any_needs_lfref = 1 if ($this_needs_lfref);
429 0           my($code) = join("\n", @{$iref->[$DB_CODE_A_F]});
  0            
430 0 0         $any_needs_lfref = 1 if ($code =~ /lfref/);
431             $self->{_pretty_args} .= " $iref->[$COMMAND_F] { " .
432 0           Fsdb::Support::code_prettify(@{$iref->[$DB_CODE_A_F]}) . " }";
  0            
433 0 0         print STDERR "$iref->[$TITLE_F]:\n$c\n" if ($self->{_debug});
434             };
435 0 0         exit 1 if ($self->{_debug} == 1);
436              
437             #
438             # write the loop
439             # xxx: should be able to otpimize away $lfref
440             #
441             {
442 0           my $loop_sub;
  0            
443 0           my $in_ncols = $#{$self->{_in}->cols} + 1;
  0            
444 0           my @out_args = ();
445 0           my $row_output_code = '';
446 0           my $output_end_pre = q'
447             $ofref = undef; # reset for any finishing-up output
448             ';
449 0           my $output_end_post = q'
450             &$write_fastpath_sub($ofref) if (defined($ofref));
451             ';
452 0 0         if ($self->{_manual_output}) {
453 0           $row_output_code = 'if (defined($ofref)) { &$write_fastpath_sub($ofref); $ofref = undef; };';
454             } else {
455 0           @out_args = (-clone => $self->{_in});
456 0 0         $row_output_code = ($self->{_no_output}) ? '' :
457             '&$write_fastpath_sub($fref);';
458             };
459             my $loop_code = q'
460             my $selfref = \$self;
461             $loop_sub = sub {
462             ' .
463 0 0         ($self->{_warnings} ? "" : "no strict 'vars';\n") . q'
    0          
    0          
    0          
    0          
464             my $fref;
465             my $lfref;
466             my $ofref = undef;
467             my $write_fastpath_sub;
468             my $in = $self->{_in};
469             # begin user BEGIN CODE
470             ' . (defined($beg_code) ? $beg_code : '') . q'
471             # end user BEGIN CODE
472             ${$selfref}->finish_io_option("output", @out_args);
473             $write_fastpath_sub = ${$selfref}->{_out}->fastpath_sub();
474             row: # let users say "next row;"
475             while ($fref = &$read_fastpath_sub()) {
476             # begin user MAINLINE CODE
477             ' . (defined($code) ? $code : '') . q'
478             # end user MAINLINE CODE
479             ' . ($any_needs_lfref ? q'
480             $lfref = $fref; # save for next pass
481             ' : '') . $row_output_code . q'
482             };
483             ' . $output_end_pre . q'
484             # begin user END CODE
485             ' . (defined($end_code) ? $end_code : '') . q'
486             # end user END CODE
487             ' . $output_end_post . q'
488             };
489             ';
490             print "\nLOOP CODE:\n$loop_code\n"
491 0 0         if ($self->{_debug} >= 2);
492 0           eval $loop_code;
493 0 0         $@ && die $self->{_prog} . ": eval error compiling user-provided code: $@.\n: CODE:\n$loop_code\n";
494 0           $self->{_loop_sub} = $loop_sub;
495             };
496 0           $self->{_beg_code_final} = $beg_code;
497 0           $self->{_end_code_final} = $end_code;
498 0           $self->{_code_final} = $code;
499             }
500              
501             =head2 run
502              
503             $filter->run();
504              
505             Internal: run over all IO
506              
507             =cut
508             sub run ($) {
509 0     0 1   my($self) = @_;
510 0           &{$self->{_loop_sub}}();
  0            
511             }
512              
513              
514             =head2 finish
515              
516             $filter->finish();
517              
518             Internal: write trailer.
519              
520             =cut
521             sub finish($) {
522 0     0 1   my($self) = @_;
523 0 0         return if ($self->{_no_output_even_comments});
524 0           $self->SUPER::finish();
525             }
526              
527              
528             =head2 compute_program_log
529              
530             $log = $filter->figure_program_log();
531              
532             Override compute_program_log to do pretty-printed arguments.
533              
534             =cut
535              
536             sub compute_program_log($) {
537 0     0 1   my $self = shift @_;
538              
539             my $log = " | " . $self->{_prog} .
540 0           " " . $self->{_pretty_args};
541 0           return $log;
542             }
543              
544              
545              
546             =head1 AUTHOR and COPYRIGHT
547              
548             Copyright (C) 1991-2007 by John Heidemann
549              
550             This program is distributed under terms of the GNU general
551             public license, version 2. See the file COPYING
552             with the distribution for details.
553              
554             =cut
555              
556             1;