File Coverage

blib/lib/Mnet/Report/Table.pm
Criterion Covered Total %
statement 110 276 39.8
branch 40 118 33.9
condition 15 98 15.3
subroutine 13 21 61.9
pod 3 3 100.0
total 181 516 35.0


line stmt bran cond sub pod time code
1             package Mnet::Report::Table;
2              
3             =head1 NAME
4              
5             Mnet::Report::Table - Output rows of report data
6              
7             =head1 SYNOPSIS
8              
9             # create an example new table object, with csv file output
10             my $table = Mnet::Report::Table->new({
11             output => "csv:file.csv",
12             columns => [
13             device => "string",
14             time => "time",
15             data => "Integer",
16             error => "error"
17             ],
18             });
19              
20             # output error row if script aborts with unreported errors
21             $table->row_on_error({ device => $device });
22              
23             # output a normal report row, call again to output more
24             $table->row({ device => $device, data => $data });
25              
26             =head1 DESCRIPTION
27              
28             Mnet::Report::Table can be used to create new report table objects, with a row
29             method call to add data, a row_on_error method to ensure errors always appear,
30             with various output options including csv, json, sql, and perl L
31             formats.
32              
33             =head1 METHODS
34              
35             Mnet::Report::Table implements the methods listed below.
36              
37             =cut
38              
39             # required modules
40 1     1   590 use warnings;
  1         9  
  1         41  
41 1     1   5 use strict;
  1         2  
  1         24  
42 1     1   516 use parent qw( Mnet::Log::Conditional );
  1         345  
  1         5  
43 1     1   45 use Carp;
  1         2  
  1         56  
44 1     1   403 use Mnet::Dump;
  1         3  
  1         35  
45 1     1   7 use Mnet::Log::Conditional qw( DEBUG INFO WARN FATAL NOTICE );
  1         2  
  1         63  
46 1     1   6 use Mnet::Opts::Cli::Cache;
  1         2  
  1         199  
47              
48             # init autoflush, global variables, and sig handlers
49             # autoflush is set so multi-process syswrite lines don't clobber each other
50             # @selves tracks report objects until deferred/row_on_error output end block
51             # $error and sig handlers used to track first error, if Mnet::Log not loaded
52             BEGIN {
53 1     1   9 $| = 1;
54 1         3 my @selves = ();
55 1         2 my $error = undef;
56 1 50       4 if (not $INC{'Mnet/Log.pm'}) {
57             $SIG{__DIE__} = sub {
58 11 100       365 if (not defined $Mnet::Report::Table::error) {
59 1         5 chomp($Mnet::Report::Table::error = "@_");
60             }
61 11         67 die @_;
62 1         7 };
63             $SIG{__WARN__} = sub {
64 0 0       0 if (not defined $Mnet::Report::Table::error) {
65 0         0 chomp($Mnet::Report::Table::error = "@_");
66             }
67             warn @_
68 1         3588 };
  0         0  
69             }
70             }
71              
72              
73              
74             sub new {
75              
76             =head2 new
77              
78             $table = Mnet::Report::Table->new(\%opts)
79              
80             A new Mnet::Report::Table object can be created with the options showm in
81             the example below:
82              
83             my $table = Mnet::Report::Table->new({
84             columns => [ # ordered column names and types
85             device => "string", # strips eol chars in csv output
86             count => "integer", # +/- integer numbers
87             error => "error", # first error, see row_on_error()
88             time => "time", # time as yyyy/mm/dd hh:mm:ss
89             unix => "epoch", # unix time, see perldoc -f time
90             ],
91             output => "csv:$file", # see this module's OUTPUT section
92             append => $boolean, # set to append output to file
93             log_id => $string, # see perldoc Mnet::Log new method
94             nodefer => $boolean, # set to output rows immediately
95             });
96              
97             The columns option is required, and is an array reference containing an ordered
98             list of hashed column names of type string, integer, error, time, or epoch.
99              
100             Columns of type string and integer are set by the user for new rows using the
101             row method in this module. Columns of type error, time, and epoch are set
102             automatically for each row of output.
103              
104             The output option should be used to specify an output format and filename, as
105             in the example above. Refer to the OUTPUT section below for more information.
106              
107             The append option opens the output file for appending and doesn't write a
108             heading row, Otherwise the default is to overwrite the output file when the
109             new table object is created.
110              
111             The nodefer option can be used so that report data rows are output immediately
112             when the row method is called. Otherwise row data is output when the script
113             exits. This can affect the reporting of errors, refer to the row_on_error
114             method below for more information.
115              
116             Note that new Mnet::Report::Table objects should be created before forking
117             batch children if the L module is being used.
118              
119             Errors are issued for invalid options.
120              
121             =cut
122              
123             # read input class and options hash ref merged with cli options
124 11   33 11 1 6420 my $class = shift // croak("missing class arg");
125 11   50     42 my $opts = Mnet::Opts::Cli::Cache::get(shift // {});
126              
127             # bless new object created from input opts hash
128             # this allows log_id and other Mnet::Log options to be in effect
129             # the following keys start with underscore and are used internally:
130             # _column_error => set if error column is present, for _row_on_error
131             # _column_order => array ref listing column names in sort order
132             # _column_types => hash ref keyed by column names with value as type
133             # _output_rows => list of row hashes, output t end unless nodefer
134             # _output_fh => filehandle for row outputs, opened from new method
135             # _row_on_error => set with row hash to ensure output if any errors
136             # in addition refer to perldoc for input opts and Mnet::Log0->new opts
137 11         16 my $self = $opts;
138 11         19 bless $self, $class;
139 11         17 push @{$Mnet::Report::Table::selves}, $self;
  11         24  
140 11         45 $self->debug("new starting");
141              
142             # abort if we were called before batch fork if Mnet::Batch was loaded
143             # avoids problems with first row method call from new sub to init output
144             # for example: _output_csv batch parent must create file and heading row
145             # we don't want every batch child creating duplicate heading rows
146             croak("new Mnet::Report::Table must be created before Mnet::Batch::fork")
147 11 50 33     29 if $INC{"Mnet/Batch.pm"} and Mnet::Batch::fork_called();
148              
149             # abort if opts columns array ref is not set
150             # create _column_types hash ref and _column_order array ref in new object
151             # croak for invalid column types and error type if Mnet::Log not loaded
152 11 100       311 croak("missing opts input columns key") if not $opts->{columns};
153 10 100       139 croak("invalid opts input columns key") if ref $opts->{columns} ne "ARRAY";
154 9 100       11 croak("missing opts input column data") if not scalar(@{$opts->{columns}});
  9         136  
155 8         18 $self->{_column_types} = {};
156 8         16 $self->{_column_order} = [];
157 8         12 while (@{$opts->{columns}}) {
  22         45  
158 17   66     26 my $column = shift @{$opts->{columns}} // croak("missing column name");
  17         146  
159 16   66     21 my $type = shift @{$opts->{columns}} // croak("missing column type");
  16         176  
160 15 50       39 croak("invalid column name $column") if $column =~ /["\r\n]/;
161 15         55 $self->debug("new column = $column ($type)");
162 15         33 $self->{_column_types}->{$column} = $type;
163 15         21 push @{$self->{_column_order}}, $column;
  15         25  
164 15 100       38 $self->{_column_error} = 1 if $type eq "error";
165 15 100       62 if ($type !~ /^(epoch|error|integer|string|time)$/) {
166 1         105 croak("column type $type is invalid");
167             }
168             }
169              
170             # debug calls to display output option set for this object
171 5         30 $self->debug("new output = ".Mnet::Dump::line($self->{output}));
172              
173             # call _output method with no row arg to init output
174             # allows batch parent or non-batch proc to open file and output headings
175 5         14 $self->debug("new init _output call");
176 5         14 $self->_output;
177              
178             # finished new method, return Mnet::Report::Table object
179 4         23 $self->debug("new finished, returning $self");
180 4         11 return $self;
181             }
182              
183              
184              
185             sub row {
186              
187             =head2 row
188              
189             $table->row(\%data)
190              
191             This method will add a row of specified data to the current report table
192             object, as in the following example:
193              
194             $table->row({
195             device => $string,
196             sample => $integer,
197             })
198              
199             Note that an error is issued if any keys in the data were not defined as string
200             or integer columns when the new method was used to create the current object.
201              
202             =cut
203              
204             # read input object
205 4   33 4 1 35 my $self = shift // croak("missing self arg");
206 4   66     126 my $data = shift // croak("missing data arg");
207              
208             # init hash ref to hold output row data
209 3         13 my $row = $self->_row_data($data);
210              
211             # output or store row data
212 0 0       0 if ($self->{nodefer}) {
213 0         0 $self->_output($row);
214             } else {
215 0         0 push @{$self->{_output_rows}}, $row;
  0         0  
216             }
217              
218             # finished row method
219 0         0 return;
220             }
221              
222              
223              
224             sub _row_data {
225              
226             # \%row = $self->_row_data(\%data)
227             # purpose: set keys in output row hash form input data hash, with error refs
228              
229             # read input object
230 3   50 3   8 my $self = shift // die "missing self arg";
231 3   50     22 my $data = shift // die "missing data arg";
232 3         27 $self->debug("_row_data starting");
233              
234             # init hash ref to hold output row data
235 3         8 my $row = {};
236              
237             # loop through all columns in the current object
238 3         5 foreach my $column (sort keys %{$self->{_column_types}}) {
  3         20  
239 6         13 my $type = $self->{_column_types}->{$column};
240 6         11 my $value = $data->{$column};
241              
242             # set epoch column to unix time, refer to perldoc -f time
243             # on most systems is non-leap seconds since 00:00:00 jan 1, 1970 utc
244             # this is simplest way to agnostically store time for various uses
245 6 50       25 if ($type eq "epoch") {
    100          
    100          
    50          
    50          
246 0         0 $row->{$column} = time;
247 0 0       0 $row->{$column} = Mnet::Test::time() if $INC{'Mnet/Test.pm'};
248 0 0       0 croak("invalid time column $column") if exists $data->{$column};
249              
250             # set error column type as reference to global first error variable
251             # update global error ref, so all rows show errors from end block
252             # croak if the user supplied data for an error column
253             } elsif ($type eq "error") {
254 3         14 $row->{$column} = \$Mnet::Report::Table::error;
255 3 100       102 croak("invalid error column $column") if exists $data->{$column};
256              
257             # set integer column type, croak on bad integer
258             } elsif ($type eq "integer") {
259 2 100       18 if (defined $value) {
260 1         5 $value =~ s/(^\s+|\s+$)//;
261 1 50       7 if ($value =~ /^(\+|\-)?\d+$/) {
262 0         0 $row->{$column} = $value;
263             } else {
264 1         4 $value = Mnet::Dump::line($value);
265 1         97 croak("invalid integer column $column value $value");
266             }
267             }
268              
269             # set string column type
270             } elsif ($type eq "string") {
271 0         0 $row->{$column} = $value;
272              
273             # set time column types to yyyy/mm/dd hh:mm:ss
274             } elsif ($type eq "time") {
275 1         3 my $time = time;
276 1 50       7 $time = Mnet::Test::time() if $INC{'Mnet/Test.pm'};
277 1         79 my ($sec, $min, $hour, $date, $month, $year) = localtime($time);
278 1         5 $month++; $year += 1900;
  1         6  
279 1         4 my @fields = ($year, $month, $date, $hour, $min, $sec);
280 1         17 $row->{$column} = sprintf("%04s/%02s/%02s %02s:%02s:%02s", @fields);
281 1 50       115 croak("invalid time column $column") if exists $data->{$column};
282              
283             # abort on unknown column type
284             } else {
285 0         0 die "invalid column type $type";
286             }
287              
288             # continue loop through columns in the currect object
289             }
290              
291             # croak if any input data columns were not declared for current object
292 0         0 foreach my $column (sort keys %$data) {
293 0 0       0 next if exists $self->{_column_types}->{$column};
294 0         0 croak("column $column was not defined for $self->{output}");
295             }
296              
297             # finished row method, return row hash ref
298 0         0 $self->debug("_row_data finished");
299 0         0 return $row;
300             }
301              
302              
303              
304             sub row_on_error {
305              
306             =head2 row_on_error
307              
308             $table->row_on_error(\%data)
309              
310             This method can be used to ensure that an Mnet::Report::Table object with an
311             error column outputs an error row when the script exits if no prior output row
312             reflected that there was an error, as in the example below:
313              
314             # declare report object as a global
315             use Mnet::Report::Table;
316             my $table = Mnet::Report::Table->new({
317             output => "json:file.json",
318             columns => [
319             input => "text",
320             error => "error",
321             ttl => "integer"
322             ],
323             });
324              
325             # call Mnet::Batch::fork here, if using Mnet::Batch module
326              
327             # output error row at exit if there was an unreported error
328             $table->row_on_error({ input => "error" });
329              
330             # output first row, no error, always present in output
331             $table->row({ input => "first" });
332              
333             # lots of code could go here, with possibility of errors...
334             die if int(rand) > .5;
335              
336             # output second row, no error, present if die did not occur
337             $table->row({ input => "second" });
338              
339             # row_on_error output at exit for unpreported errors
340             exit;
341              
342             This ensures that a script does not die after the row_on_error call without
343             any indication of an error in the report output.
344              
345             The default is to output all report data rows when the script exits. At this
346             time all error columns for all rows will be set with the first of any prior
347             script errors. In this case row_on_error will output an error row if there
348             was an error and the row method was never called.
349              
350             If the nodefer option was set when a new Mnet::Report::Table object was created
351             then row data is output immediately each time the row method is called, with
352             the error column set only if there was an error before the row method call. Any
353             errors afterward could go unreported. In this case row_on_error will output an
354             extra row at script exit if there was an error after the last row method call,
355             or the row method was never called.
356              
357             =cut
358              
359             # read inputs, store row_on_error row data as object _row_on_error
360             # this is output from module end block if there were unreported errors
361 0   0 0 1 0 my $self = shift // croak("missing self arg");
362 0   0     0 my $data = shift // croak("missing data arg");
363 0 0       0 croak("row_on_error requires error column") if not $self->{_column_error};
364 0         0 $self->{_row_on_error} = $self->_row_data($data);
365 0         0 return;
366             }
367              
368              
369              
370             =head1 OUTPUT OPTIONS
371              
372             When a new Mnet::Report::Table object is created the output option can be set
373             to any of the output format types listed in the documentation sections below.
374              
375             If the L module is loaded report rows are always logged with the
376             info method.
377              
378             Note the L module --test command line option silently overrides all
379             other report output options, outputting report data using the L
380             module if loaded or sending report output to stdout in L format,
381             for consistant test results.
382              
383             Output files are opened when an Mnet::Report object is created, with a heading
384             row if necessary. Refer to the new method in this documentation for information
385             on the append and nodefer options that control how the output file is opened
386             and when row data is written.
387              
388             Output options below can use /dev/stdout as the output file, which works nicely
389             with the L --silent option used with the L --batch
390             option, allowing report output from all concurrently executing batch children
391             to be easily piped or redirected in aggregate as necessary. However be aware
392             thet /dev/stdout report output is not captured by the L module.
393              
394             Note the L module --test command line option silently overrides
395             all other report output options, outputting report data using the L
396             module if loaded or sending report output to stdout in L format,
397             for consistant test results.
398              
399             =cut
400              
401             sub _output {
402              
403             # $self->_output(\$row)
404             # purpose: called from new to open file and output headings, called from row
405             # \%row: row data, or undef for init call from new method w/Mnet::Batch loaded
406             # $self->{output} object property is parsed to determin output type
407             # $self->{append} clear by default, output overwrites file, heading rows output
408             # $self->{append} set will append to output file, headng rows are suppressed
409              
410             # read input object and row data hash reference
411 5   50 5   12 my $self = shift // die "missing self arg";
412 5         8 my $row = shift;
413 5         22 $self->debug("_output starting");
414              
415             # init file parsed from output option and row output line
416 5         10 my ($file, $output) = (undef, undef);
417              
418             # handle --test output, skipped for undef heading row
419 5         13 my $cli = Mnet::Opts::Cli::Cache::get({});
420 5 50       19 if ($cli->{test}) {
421 0 0       0 if (defined $row) {
422 0         0 $self->debug("_output calling _output_test");
423 0         0 $output = $self->_output_test($row);
424             }
425              
426             # handle non-test output
427             } else {
428              
429             # log report row output, skipped for undef heading row
430 5 50       13 if (defined $row) {
431 0         0 $self->debug("_output calling _output_log");
432 0         0 $output = $self->_output_log($row);
433             }
434              
435             # note that no output option was set
436 5 100       21 if (not defined $self->{output}) {
    50          
    50          
    50          
    50          
437 4         24 $self->debug("_output skipped, output option not set");
438              
439             # handle csv output, refer to sub _output_csv
440             } elsif ($self->{output} =~ /^csv(:(.+))?$/) {
441 0         0 $self->debug("_output calling _output_csv");
442 0         0 $output = $self->_output_csv($row);
443 0   0     0 $file = $2 // "/dev/stdout";
444              
445             # handle dump output, call with var name arg, refer to sub _output_dump
446             } elsif ($self->{output} =~ /^dump(:([a-zA-Z]\w*)(:(.+))?)?$/) {
447 0         0 $self->debug("_output calling _output_dump");
448 0   0     0 $output = $self->_output_dump($row, $2 // "dump");
449 0   0     0 $file = $4 // "/dev/stdout";
450              
451             # handle json output, call with var name arg, refer to sub _output_json
452             } elsif ($self->{output} =~ /^json(:([a-zA-Z]\w*)(:(.+))?)?$/) {
453 0         0 $self->debug("_output calling _output_json");
454 0   0     0 $output = $self->_output_json($row, $2 // "json");
455 0   0     0 $file = $4 // "/dev/stdout";
456              
457             # handle sql output, call with table name arg, refer to sub _output_sql
458             } elsif ($self->{output} =~ /^sql(:("([^"]+)"|(\w+))(:(.+))?)?$/) {
459 0         0 $self->debug("_output calling _output_sql");
460 0   0     0 $output = $self->_output_sql($row, $3 // $4 // "table");
      0        
461 0   0     0 $file = $6 // "/dev/stdout";
462              
463             # error on invalid output option
464             } else {
465 1         12 $self->fatal("invalid output option $self->{output}");
466             }
467              
468             # finished handling non-test output
469             }
470              
471             # open output filehandle, honor object append option
472             # open output file for first heading row call so we know we can open it
473             # so we don't continue running script when report file won't work
474 4 50 33     11 if ($file and not $self->{_output_fh}) {
475 0         0 my $mode = ">";
476 0 0       0 $mode = ">>" if $self->{append};
477 0         0 $self->debug("_output opening ${mode}$file");
478 0 0       0 open($self->{_output_fh}, $mode, $file)
479             or $self->fatal("unable to open ${mode}$file, $!");
480             }
481              
482             # output row
483             # note that for heading row the input row value is undefined
484             # normal rows are always output, heading row output only if append not set
485 4 50       9 if ($output) {
486 0 0 0     0 if ($row or not $self->{append}) {
487 0         0 syswrite $self->{_output_fh}, "$output\n";
488             }
489             }
490              
491             # finished _output method
492 4         12 $self->debug("_output finished");
493 4         8 return;
494             }
495              
496              
497              
498             sub _output_csv {
499              
500             # $output = $self->_output_csv($row)
501             # purpose: return output row data in csv format, or heading row
502             # \%row: row data, undef for heading row which returns heading row
503             # $output: single line of row output, or heading row if input row was undef
504              
505             =head2 output csv
506              
507             csv
508             csv:$file
509              
510             The csv output option can be used to output a csv file, /dev/stdout by default.
511              
512             All csv outputs are doule quoted. Double quotes in the outut data are escaped
513             with an extra double quote.
514              
515             All end of line carraige return and linefeed characters are replaced with
516             spaces in the csv output. Multiline csv output data is not supported.
517              
518             The output csv file will be created with a heading row when the new method is
519             called unless the append option was set when the new method was called.
520              
521             Refer to the OUTPUT OPTIONS section of this module for more info.
522              
523             =cut
524              
525             # read input object and row data hash reference
526 0   0 0     my $self = shift // die "missing self arg";
527 0           my $row = shift;
528 0           $self->debug("_output_csv starting");
529              
530             # init csv row output sting, will be heading row if input row is undef
531 0           my $output = undef;
532              
533             # declare sub to quote and escape csv value
534             # eol chars removed so concurrent batch outputs klines don't intermix
535             # double quotes are escaped with an extra double quote
536             # value is prefixed and suffixed with double quotes
537             sub _output_csv_escaped {
538 0   0 0     my $value = shift // "";
539 0           $value =~ s/(\r|\n)/ /g;
540 0           $value =~ s/"/""/g;
541 0           $value = '"'.$value.'"';
542 0           return $value;
543             }
544              
545             # determine if headings row is needed
546             # headings are needed if current script is not a batch script
547             # headings are needed for parent process of batch executions
548             # headings are not needed if the append option is set for table
549 0           my $headings_needed = 0;
550 0 0 0       if (not $INC{"Mnet/Batch.pm"} or not $MNet::Batch::fork_called) {
551 0 0         if (not $self->{append}) {
552 0 0         $headings_needed = 1 if not defined $row;
553             }
554             }
555              
556             # output heading row, if needed
557 0 0         if ($headings_needed) {
558 0           $self->debug("_output_csv generating heading row");
559 0           my @headings = ();
560 0           foreach my $column (@{$self->{_column_order}}) {
  0            
561 0           push @headings, _output_csv_escaped($column);
562             }
563 0           $output = join(",", @headings);
564             }
565              
566             # output data row, if defined
567 0 0         if (defined $row) {
568 0           my @data = ();
569 0           foreach my $column (@{$self->{_column_order}}) {
  0            
570 0           my $column_data = $row->{$column};
571 0 0         $column_data = ${$row->{$column}} if ref $row->{$column};
  0            
572 0           push @data, _output_csv_escaped($column_data);
573             }
574 0           $output = join(",", @data);
575             }
576              
577             # finished _output_csv method, return output line
578 0           $self->debug("_output_csv finished");
579 0           return $output;
580             }
581              
582              
583              
584             sub _output_dump {
585              
586             # $output = $self->_output_dump($row, $var)
587             # purpose: return output row data in perl Data::Dumper format
588             # \%row: row data, undef for heading row which returns undef (no heading row)
589             # $var: var name parsed from object output option used in Data::Dumper output
590             # $output: single line of row output, or undef if input row was undef
591              
592             =head2 output dump
593              
594             dump
595             dump $var
596             dump:$var:$file
597              
598             The dump output option writes one row per line in L format
599             prefixed by the specified var name, defaulting to 'dump' and /dev/stdout.
600              
601             This dump output can be read back into a perl script as follows:
602              
603             use Data::Dumper;
604             while () {
605             my ($line, $var) = ($_, undef);
606             my $table = $1 if $line =~ s/^\$(\S+)/\$var/ or die;
607             eval "$line";
608             print Dumper($var);
609             }
610              
611             Refer to the OUTPUT OPTIONS section of this module for more info.
612              
613             =cut
614              
615             # read input object and row data hash reference
616 0   0 0     my $self = shift // die "missing self arg";
617 0   0       my $row = shift // return;
618 0   0       my $var = shift // die "missing var arg";
619 0           $self->debug("_output_dump starting");
620              
621             # dereference error columns
622 0           foreach my $column (keys %$row) {
623 0 0         $row->{$column} = ${$row->{$column}} if ref $row->{$column};
  0            
624             }
625              
626             # create output row string, singl line dump
627 0           my $output = "\$$var = ".Mnet::Dump::line($row).";";
628              
629             # finished _output_dump method, return output line
630 0           $self->debug("_output_dump finished");
631 0           return $output;
632             }
633              
634              
635              
636             sub _output_json {
637              
638             # $output = $self->_output_json($row, $var)
639             # purpose: return output row data in json format
640             # \%row: row data, undef for heading row which returns undef (no heading row)
641             # $var: var name parsed from object output option used in json output
642             # $output: single line of row output, or undef if input row was undef
643              
644             =head2 output json
645              
646             json
647             json:$var
648             json:$var:$file
649              
650             The json output option writes one row per line in json format prefixed by the
651             specified var name, defaulting to 'json' and /dev/stdout. This requires that
652             the L module is available.
653              
654             The output json looks something like the example below:
655              
656             var = {"device":"test","error":null};
657              
658             This json output can be read back into a perl script as follows:
659              
660             use JSON;
661             use Data::Dumper;
662             while () {
663             my ($line, $var) = ($_, undef);
664             my $table = $1 if $line =~ s/^(\S+) = // or die;
665             $var = decode_json($line);
666             print Dumper($var);
667             }
668              
669             Refer to the OUTPUT OPTIONS section of this module for more info.
670              
671             =cut
672              
673             # read input object and row data hash reference
674 0   0 0     my $self = shift // die "missing self arg";
675 0   0       my $row = shift // return;
676 0   0       my $var = shift // die "missing var arg";
677 0           $self->debug("_output_json starting");
678              
679             # abort with an error if JSON module is not available
680             croak("Mnet::Report::Table json requires perl JSON module is installed")
681 0 0 0       if not $INC{'JSON.pm'} and not eval("require JSON; 1");
682              
683             # dereference error columns
684 0           foreach my $column (keys %$row) {
685 0 0         $row->{$column} = ${$row->{$column}} if ref $row->{$column};
  0            
686             }
687              
688             # create output data row
689             # json is sorted so that test output doesn't vary
690             # this will be undefined if called from new method
691 0           my $output = "$var = ".JSON->new->canonical->encode($row).";";
692              
693             # finished _output_json method, return output line
694 0           $self->debug("_output_json finished");
695 0           return $output;
696             }
697              
698              
699              
700             sub _output_log {
701              
702             # $self->_output_log
703             # purpose: output report row as info log entries
704              
705             # read input object and row data hash reference
706 0   0 0     my $self = shift // die "missing self arg";
707 0           my $row = shift;
708 0           $self->debug("_output_log starting");
709              
710             # dereference error columns
711 0           foreach my $column (keys %$row) {
712 0 0         $row->{$column} = ${$row->{$column}} if ref $row->{$column};
  0            
713             }
714              
715             # determine width of widest column, for formatting
716 0           my $width = 0;
717 0           foreach my $column (@{$self->{_column_order}}) {
  0            
718 0 0         $width = length($column) if length($column) > $width;
719             }
720              
721             # output data row to Mnet::Log
722             # row will be undefined if called from new method
723 0 0         if (defined $row) {
724 0           my $prefix = "row";
725 0           $self->info("$prefix {");
726 0           foreach my $column (@{$self->{_column_order}}) {
  0            
727 0           my $value = Mnet::Dump::line($row->{$column});
728 0           $self->info(sprintf("$prefix %-${width}s => $value", $column));
729             }
730 0           $self->info("$prefix }");
731             }
732              
733             # finished _output_log method
734 0           $self->debug("_output_log finished");
735 0           return;
736             }
737              
738              
739              
740             sub _output_sql {
741              
742             # $output = $self->_output_sql($row, $var)
743             # purpose: return output row data in sql format, as an insert statement
744             # \%row: row data, undef for heading row which returns undef (no heading row)
745             # $table: table name parsed from object output option used in sql output
746             # $output: single line of row output, or undef if input row was undef
747              
748             =head2 output sql
749              
750             sql
751             sql:$table
752             sql:"$table"
753             sql:$table:$file
754             sql:"$table":$file
755              
756             The sql output option writes one row per line as sql insert statements using
757             the specified table name, double-quoting non-word table names, defaulting to
758             "table" and /dev/stdout, in the following format:
759              
760             INSERT INTO (, ...) VALUES (, ...);
761              
762             Table and column names are double quoted, and values are single quoted. Single
763             quotes in values are escaped with an extra single quote character, LF and CR
764             characters are escaped as '+CHAR(10)+' and '+CHAR(13)+' respectively.
765              
766             Refer to the OUTPUT OPTIONS section of this module for more info.
767              
768             =cut
769              
770             # read input object and row data hash reference
771 0   0 0     my $self = shift // die "missing self arg";
772 0   0       my $row = shift // return;
773 0   0       my $table = shift // die "missing table arg";
774 0           $self->debug("_output_sql starting");
775              
776             # init sql row output sting, will be heading row if input row is undef
777 0           my $output = undef;
778              
779             # dereference error columns
780 0           foreach my $column (keys %$row) {
781 0 0         $row->{$column} = ${$row->{$column}} if ref $row->{$column};
  0            
782             }
783              
784             # output data row
785             # this will be undefined if called from new method
786             # double quote column names to handle unusual column names
787             # escape multiline outputs which concurrent batch procs can clobber
788 0 0         if (defined $row) {
789 0           my @sql_columns = ();
790 0           my @sql_values = ();
791 0           foreach my $column (@{$self->{_column_order}}) {
  0            
792 0           push @sql_columns, '"' . $column . '"';
793 0   0       my $value = $row->{$column} // "";
794 0           $value =~ s/'/''/g;
795 0           $value =~ s/\r/'+CHAR(10)+'/g;
796 0           $value =~ s/\n/'+CHAR(13)+'/g;
797 0           push @sql_values, "'" . $value . "'";
798             }
799 0           $output = "INSERT INTO \"$table\" ";
800 0           $output .= "(" . join(",", @sql_columns) . ") ";
801 0           $output .= "VALUES (" . join(",", @sql_values) . ");";
802             }
803              
804             # finished _output_sql method, return output line
805 0           $self->debug("_output_sql finished");
806 0           return $output;
807             }
808              
809              
810              
811             sub _output_test {
812              
813             # $self->_output_test(\%row)
814             # purpose: output test row data to stdout in Data::Dumper for when --test set
815             # \%row: row data, or undef for init call from new method w/Mnet::Batch loaded
816              
817             # read input object and row data hash reference
818 0   0 0     my $self = shift // die "missing self arg";
819 0           my $row = shift;
820 0           $self->debug("_output_test starting");
821              
822             # dereference error columns
823 0           foreach my $column (keys %$row) {
824 0 0         $row->{$column} = ${$row->{$column}} if ref $row->{$column};
  0            
825             }
826              
827             # determine width of widest column, for formatting
828 0           my $width = 0;
829 0           foreach my $column (@{$self->{_column_order}}) {
  0            
830 0 0         $width = length($column) if length($column) > $width;
831             }
832              
833             # output data row to Mnet::Log
834             # row will be undefined if called from new method
835 0 0 0       if (defined $row and $INC{"Mnet/Log.pm"}) {
    0          
836 0           $self->debug("_output_test calling _output_log");
837 0           $self->_output_log($row);
838              
839             # otherwise output data row to standard output
840             # row will be undefined if called from new method
841             } elsif (defined $row) {
842 0           syswrite STDOUT, "\nMnet::Report::Table row = {\n";
843 0           foreach my $column (@{$self->{_column_order}}) {
  0            
844 0           my $value = Mnet::Dump::line($row->{$column});
845 0           syswrite STDOUT, sprintf(" %-${width}s => $value\n", $column);
846             }
847 0           syswrite STDOUT, "}\n";
848             }
849              
850             # finished _output_test method
851 0           $self->debug("_output_test finished");
852 0           return;
853             }
854              
855              
856              
857             # ensure that row data and error for all report objects has been output
858             # update global error var if Mnet::Log is loaded, ref used for error columns
859             # output rows for report objects that stored rows for end (nodefer not set)
860             # output row_on_error if there were unreported errors or nodefer was set
861             sub END {
862 1 50   1   1156 $Mnet::Report::Table::error = Mnet::Log::error() if $INC{'Mnet/Log.pm'};
863 1         3 foreach my $self (@{$Mnet::Report::Table::selves}) {
  1         4  
864 11         14 $self->_output($_) foreach @{$self->{_output_rows}};
  11         20  
865 11 0 33     19 if ($self->{_row_on_error} and $Mnet::Report::Table::error) {
866 0 0 0     0 if (not $self->{_row_on_error} or $self->{nodefer}) {
867 0         0 $self->_output($self->{_row_on_error});
868             }
869             }
870             }
871             }
872              
873              
874              
875             =head1 TESTING
876              
877             Mnet::Report::Table supports the L module test, record, and replay
878             functionality, tracking report data so it can be included in test results.
879              
880             =head1 SEE ALSO
881              
882             L
883              
884             L
885              
886             L
887              
888             L
889              
890             L
891              
892             L
893              
894             =cut
895              
896             # normal package return
897             1;
898