| 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
|
|
|
|
|
|
|
|
|