line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# Weather::GHCN::Options.pm - class for GHCN options
|
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
## no critic (Documentation::RequirePodAtEnd)
|
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
=head1 NAME
|
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
Weather::GHCN::Options - create and manage option lists/objects used by GHCN modules and scripts
|
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
=head1 VERSION |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
version v0.0.010 |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
=head1 SYNOPSIS
|
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
use Weather::GHCN::Options;
|
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
=head1 DESCRIPTION
|
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
The B module provides a class and methods that are
|
21
|
|
|
|
|
|
|
used within GHCN modules or from application scripts that use GHCN
|
22
|
|
|
|
|
|
|
modules to create and manage options that determine the behaviour of
|
23
|
|
|
|
|
|
|
GHCN methods.
|
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
The module is primarily for use by module Weather::GHCN::StationTable.
|
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
=cut
|
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
# these are needed because perlcritic fails to detect that Object::Pad handles these things
|
30
|
|
|
|
|
|
|
## no critic [ValuesAndExpressions::ProhibitVersionStrings]
|
31
|
|
|
|
|
|
|
## no critic [TestingAndDebugging::RequireUseWarnings]
|
32
|
|
|
|
|
|
|
|
33
|
5
|
|
|
5
|
|
2207
|
use v5.18; # minimum for Object::Pad
|
|
5
|
|
|
|
|
20
|
|
34
|
5
|
|
|
5
|
|
29
|
use warnings;
|
|
5
|
|
|
|
|
8
|
|
|
5
|
|
|
|
|
184
|
|
35
|
5
|
|
|
5
|
|
28
|
use Object::Pad 0.66 qw( :experimental(init_expr) );
|
|
5
|
|
|
|
|
79
|
|
|
5
|
|
|
|
|
28
|
|
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
package Weather::GHCN::Options;
|
38
|
|
|
|
|
|
|
class Weather::GHCN::Options;
|
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
our $VERSION = 'v0.0.010'; |
41
|
|
|
|
|
|
|
|
42
|
5
|
|
|
5
|
|
2048
|
use Carp qw(carp croak);
|
|
5
|
|
|
|
|
26
|
|
|
5
|
|
|
|
|
327
|
|
43
|
5
|
|
|
5
|
|
37
|
use Const::Fast;
|
|
5
|
|
|
|
|
11
|
|
|
5
|
|
|
|
|
36
|
|
44
|
5
|
|
|
5
|
|
3224
|
use Hash::Wrap {-lvalue => 1, -defined => 1, -as => '_wrap_hash'};
|
|
5
|
|
|
|
|
21385
|
|
|
5
|
|
|
|
|
32
|
|
45
|
5
|
|
|
5
|
|
9810
|
use Text::Abbrev;
|
|
5
|
|
|
|
|
199
|
|
|
5
|
|
|
|
|
333
|
|
46
|
5
|
|
|
5
|
|
1182
|
use Weather::GHCN::CountryCodes qw( search_country );
|
|
5
|
|
|
|
|
15
|
|
|
5
|
|
|
|
|
421
|
|
47
|
5
|
|
|
5
|
|
1029
|
use Weather::GHCN::Common qw( :all );
|
|
5
|
|
|
|
|
12
|
|
|
5
|
|
|
|
|
784
|
|
48
|
5
|
|
|
5
|
|
2847
|
use YAML::Tiny;
|
|
5
|
|
|
|
|
31405
|
|
|
5
|
|
|
|
|
30110
|
|
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
######################################################################
|
51
|
|
|
|
|
|
|
# Constants
|
52
|
|
|
|
|
|
|
######################################################################
|
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
const my $TRUE => 1; # perl's usual TRUE
|
55
|
|
|
|
|
|
|
const my $FALSE => not $TRUE; # a dual-var consisting of '' and 0
|
56
|
|
|
|
|
|
|
const my $SPACE => q( );
|
57
|
|
|
|
|
|
|
const my $EMPTY => q();
|
58
|
|
|
|
|
|
|
const my $DASH => q(-);
|
59
|
|
|
|
|
|
|
const my $BAR => q(|);
|
60
|
|
|
|
|
|
|
const my $BANG => q(!);
|
61
|
|
|
|
|
|
|
const my $NEWLINE => qq(\n);
|
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
const my $DEFAULT_PROFILE_FILE => '~/.ghcn_fetch.yaml';
|
64
|
|
|
|
|
|
|
const my $ALIAS_NAME_RE => qr{ \A [_]?[[:lower:]]+ \Z }xms;
|
65
|
|
|
|
|
|
|
const my @REPORT_TYPE_LIST => qw(detail daily monthly yearly url curl kml stn id);
|
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
=head1 METHODS
|
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
=head2 new
|
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
Create a new Options object.
|
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
=cut
|
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
######################################################################
|
76
|
|
|
|
|
|
|
# Set up the default Tk::Getopt option table, which we will use for
|
77
|
|
|
|
|
|
|
# both Tk::Getopt and to derive an options list of Getopt::Long
|
78
|
|
|
|
|
|
|
# for when Tk::Getopt is not installed.
|
79
|
|
|
|
|
|
|
######################################################################
|
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
## no critic [ValuesAndExpressions::ProhibitMagicNumbers]
|
82
|
|
|
|
|
|
|
## no critic [ValuesAndExpressions::ProhibitNoisyQuotes]
|
83
|
|
|
|
|
|
|
## no critic [ValuesAndExpressions::ProhibitEmptyQuotes]
|
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
my $Tk_opt_table = [
|
86
|
|
|
|
|
|
|
'Basic options',
|
87
|
|
|
|
|
|
|
['location', '=s', undef, label => 'Filter stations by their location name (regex)'],
|
88
|
|
|
|
|
|
|
['state', '=s', undef, label => 'Filter stations by state or province',
|
89
|
|
|
|
|
|
|
alias => ['province'] ],
|
90
|
|
|
|
|
|
|
['country', '=s', undef, label => 'Filter station by the country they are in'],
|
91
|
|
|
|
|
|
|
['gsn', '!', undef, label => 'Include only GSN reference stations'],
|
92
|
|
|
|
|
|
|
['', '', '-'],
|
93
|
|
|
|
|
|
|
['report', '=s', '',
|
94
|
|
|
|
|
|
|
label => 'Type of report',
|
95
|
|
|
|
|
|
|
strict => 1,
|
96
|
|
|
|
|
|
|
choices => [
|
97
|
|
|
|
|
|
|
[ 'station list', '' ],
|
98
|
|
|
|
|
|
|
[ 'yearly summary', 'yearly' ],
|
99
|
|
|
|
|
|
|
[ 'monthly summary', 'monthly' ],
|
100
|
|
|
|
|
|
|
[ 'daily summary', 'daily' ],
|
101
|
|
|
|
|
|
|
[ 'detail level', 'detail' ],
|
102
|
|
|
|
|
|
|
[ 'station KML', 'kml' ],
|
103
|
|
|
|
|
|
|
[ 'station url list', 'url' ],
|
104
|
|
|
|
|
|
|
[ 'station curl list', 'curl' ],
|
105
|
|
|
|
|
|
|
[ 'station list only', 'stn' ],
|
106
|
|
|
|
|
|
|
]
|
107
|
|
|
|
|
|
|
],
|
108
|
|
|
|
|
|
|
['', '', '-'],
|
109
|
|
|
|
|
|
|
['dataonly', '!', undef, label => 'Only print the data table'],
|
110
|
|
|
|
|
|
|
['performance', '!', undef, label => 'Report performance statistics'],
|
111
|
|
|
|
|
|
|
['verbose', '!', undef, label => 'Print information messages'],
|
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
'Date filters',
|
114
|
|
|
|
|
|
|
['range', '=s', undef, label => 'Filter selected station data by year range'],
|
115
|
|
|
|
|
|
|
['active', '=s', undef, label => 'Filter stations by their active year range'],
|
116
|
|
|
|
|
|
|
['partial', '!', undef, label => 'Allow stations only active for part of the active range'],
|
117
|
|
|
|
|
|
|
['quality', '=i', 90, label => 'Quality threshold (percent as an integer)'],
|
118
|
|
|
|
|
|
|
['', '','-'],
|
119
|
|
|
|
|
|
|
['fday', '=s', undef, label => 'Filter output to include a specific day'],
|
120
|
|
|
|
|
|
|
['fmonth', '=s', undef, label => 'Filter output to include a specific month'],
|
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
'GIS filters',
|
123
|
|
|
|
|
|
|
['gps', '=s', undef, label => 'Filter stations by latitude and longitude',
|
124
|
|
|
|
|
|
|
help => 'Enter decimal latitude and longitude'],
|
125
|
|
|
|
|
|
|
['radius', '=i', 50, label => 'Radius to search for stations near coordinates'],
|
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
'Analysis Options',
|
129
|
|
|
|
|
|
|
['anomalies', '!', undef, label => 'Provide calculated anomalies in the output'],
|
130
|
|
|
|
|
|
|
['baseline', '=s', '1971-2000',
|
131
|
|
|
|
|
|
|
label => 'Baseline year range'],
|
132
|
|
|
|
|
|
|
['precip', '!', undef, label => 'Include precipitation stats in the results'],
|
133
|
|
|
|
|
|
|
['tavg', '!', undef, label => 'Include TAVG in the results'],
|
134
|
|
|
|
|
|
|
['nogaps', '!', undef, label => 'Emit extra rows for missing months or days'],
|
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
'Other Options',
|
137
|
|
|
|
|
|
|
['kmlcolor', '=s', 'red', label => 'Color to use for KML placemarks' ],
|
138
|
|
|
|
|
|
|
['profile', '=s', $DEFAULT_PROFILE_FILE,
|
139
|
|
|
|
|
|
|
label => 'Profile file location (for option preloading)'], #, nogui => 1],
|
140
|
|
|
|
|
|
|
['cachedir', '=s', undef, label => 'Directory for cached files'],
|
141
|
|
|
|
|
|
|
['refresh', '=s', 'yearly',
|
142
|
|
|
|
|
|
|
help => 'Refresh yearly, (default), never, always, or if N days old (N > 1)',
|
143
|
|
|
|
|
|
|
label => 'Cache refresh option',
|
144
|
|
|
|
|
|
|
choices => [ 'yearly', 'always', 'never', '' ],
|
145
|
|
|
|
|
|
|
],
|
146
|
|
|
|
|
|
|
];
|
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
## use critic [ValuesAndExpressions::ProhibitMagicNumbers]
|
149
|
|
|
|
|
|
|
## use critic [ValuesAndExpressions::ProhibitNoisyQuotes]
|
150
|
|
|
|
|
|
|
## use critic [ValuesAndExpressions::ProhibitEmptyQuotes]
|
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
#####################################################################
|
153
|
|
|
|
|
|
|
# Class fields
|
154
|
|
|
|
|
|
|
######################################################################
|
155
|
|
|
|
|
|
|
|
156
|
23
|
|
|
23
|
1
|
7554
|
field $_opt_href :mutator; # a hashref of merged options (with default values applied))
|
|
23
|
|
|
|
|
119
|
|
157
|
23
|
|
|
23
|
1
|
96
|
field $_opt_obj :mutator; # a Hash::Wrap object derived from $_opt_href
|
|
23
|
|
|
|
|
109
|
|
158
|
285
|
|
|
285
|
1
|
1195
|
field $_profile_href :mutator; # a hash containing profile file options
|
|
285
|
|
|
|
|
642
|
|
159
|
|
|
|
|
|
|
field $_tk_opt_aref; # the Tk:Getopt array that defines all GHCN options
|
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
=head1 FIELD ACCESSORS
|
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
Writeable (mutator) access is provided for some fields primarily so
|
164
|
|
|
|
|
|
|
an Options object can be tested independantly from StationTable.
|
165
|
|
|
|
|
|
|
In general, an Options object is set by the StationTable set_options
|
166
|
|
|
|
|
|
|
method and should not be modified directly by the consuming application
|
167
|
|
|
|
|
|
|
using these mutators.
|
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
=over 4
|
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
=item opt_href
|
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
This writable field is set by StationTable->set_options and is a
|
174
|
|
|
|
|
|
|
hashref of user options merged with default values.
|
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
For programmatic access to option values, use of B is
|
177
|
|
|
|
|
|
|
preferred to prevent mispellings. (See B.)
|
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
=item opt_obj
|
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
This writable field is set by StationTable->set_options and is a
|
182
|
|
|
|
|
|
|
Hash::Wrap object composed from the B hashref field. It
|
183
|
|
|
|
|
|
|
provides accessor field methods for user options (merged with default
|
184
|
|
|
|
|
|
|
values).
|
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
Using this object, rather than B, for access to option
|
187
|
|
|
|
|
|
|
values is safer programming choice as any misspelling of an option
|
188
|
|
|
|
|
|
|
name will result in a run time error. In contrast, mispelling a hash
|
189
|
|
|
|
|
|
|
key will simply result in an undef being returned.
|
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
=item profile_href
|
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
This writable field is set by StationTable->set_options and contains
|
194
|
|
|
|
|
|
|
the profile options it was given.
|
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
=back
|
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
=cut
|
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
######################################################################
|
201
|
|
|
|
|
|
|
=head1 CLASS METHODS
|
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
The following class methods are supported. Class Options uses
|
204
|
|
|
|
|
|
|
Object::Perl, so these class methods (specified using the :common
|
205
|
|
|
|
|
|
|
method attribute) should be accessed using -> not :: because ->
|
206
|
|
|
|
|
|
|
will shift off the $class argument and :: won't.
|
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
=head2 get_tk_options_table
|
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
Returns: @tk_opttable or \@tk_opttable
|
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
Provides access to the predefined TK::Getopt options list that define
|
213
|
|
|
|
|
|
|
the Getopt::Long arguments supported by class StationTable for user
|
214
|
|
|
|
|
|
|
options.
|
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
The table is a list of lists and strings. The strings define
|
217
|
|
|
|
|
|
|
sections that Tk::Getopt renders as panels or tabs in the GUI it
|
218
|
|
|
|
|
|
|
constructs. The lists contain option names and types (in Getopt::Long
|
219
|
|
|
|
|
|
|
style) as well as default values, aliases, and labels to be displayed
|
220
|
|
|
|
|
|
|
in the GUI, choices for multi-select options, and other extensions.
|
221
|
|
|
|
|
|
|
See Tk::Getopt OPTTABLE ARGUMENTS for details.
|
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
=cut
|
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
method get_tk_options_table :common () {
|
226
|
|
|
|
|
|
|
return wantarray ? ( $Tk_opt_table->@* ) : $Tk_opt_table;
|
227
|
|
|
|
|
|
|
}
|
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
=head2 get_getopt_list
|
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
Returns: @options or \@options
|
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
In scalar context, return a list reference to a translation of the
|
234
|
|
|
|
|
|
|
TK::Getopt options list into the simpler list used by Getopt::Long.
|
235
|
|
|
|
|
|
|
This gives application authors a choice between using Tk::Getopt and
|
236
|
|
|
|
|
|
|
the non-GUI and more traditional Getopt::Long.
|
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
In list context, this method returns a Getopt::Long-style list
|
239
|
|
|
|
|
|
|
options.
|
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
Typically, this method would be called prior to Getopt::Long in order
|
242
|
|
|
|
|
|
|
to obtain an options list for using the StationTable class; e.g.
|
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
my %opt;
|
245
|
|
|
|
|
|
|
my @options = ( Weather::GHCN::Options->get_getopt_list() );
|
246
|
|
|
|
|
|
|
GetOptions( \%opt, @options);
|
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
=cut
|
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
method get_getopt_list :common () {
|
251
|
|
|
|
|
|
|
## no critic [ProhibitDoubleSigils]
|
252
|
|
|
|
|
|
|
my @options_text;
|
253
|
|
|
|
|
|
|
my @options_list;
|
254
|
|
|
|
|
|
|
my @options;
|
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
# According to https://metacpan.org/pod/Tk::Getopt -opttable
|
257
|
|
|
|
|
|
|
# should be a reference to an array containing all options.
|
258
|
|
|
|
|
|
|
# Elements of this array may be strings, which indicate the
|
259
|
|
|
|
|
|
|
# beginning of a new group, or array references describing the
|
260
|
|
|
|
|
|
|
# options. The first element of this array is the name of the
|
261
|
|
|
|
|
|
|
# option, the second is the type (=s for string, =i for integer,
|
262
|
|
|
|
|
|
|
# ! for boolean, =f for float etc., see Getopt::Long) for a
|
263
|
|
|
|
|
|
|
# detailed list. The third element is optional and contains the
|
264
|
|
|
|
|
|
|
# default value (otherwise the default is undefined).
|
265
|
|
|
|
|
|
|
# Further elements are optional too and describe more attributes. For a
|
266
|
|
|
|
|
|
|
# complete list of these attributes refer to "OPTTABLE ARGUMENTS".
|
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
foreach my $row ( $Tk_opt_table->@* ) {
|
269
|
|
|
|
|
|
|
next if ref $row ne 'ARRAY';
|
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
# pick off the first three values, then slurp the rest
|
272
|
|
|
|
|
|
|
my ($opt_kw, $opt_type, $default, @other) = $row->@*;
|
273
|
|
|
|
|
|
|
# skip the group dividers
|
274
|
|
|
|
|
|
|
next if not $opt_kw;
|
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
my %h;
|
277
|
|
|
|
|
|
|
while (my $item = shift @other) {
|
278
|
|
|
|
|
|
|
if (ref $item eq 'HASH') {
|
279
|
|
|
|
|
|
|
while (my ($k,$v) = each $item->%*) {
|
280
|
|
|
|
|
|
|
$h{$k} = $v;
|
281
|
|
|
|
|
|
|
}
|
282
|
|
|
|
|
|
|
} else {
|
283
|
|
|
|
|
|
|
my $value = shift @other;
|
284
|
|
|
|
|
|
|
$h{$item} = $value;
|
285
|
|
|
|
|
|
|
}
|
286
|
|
|
|
|
|
|
}
|
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
my $label = $h{'label'} // $SPACE;
|
289
|
|
|
|
|
|
|
my $alias_aref = $h{'alias'} // [];
|
290
|
|
|
|
|
|
|
my $opt_kw_with_aliases = join $BAR, $opt_kw, $alias_aref->@*;
|
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
push @options_list, $opt_kw_with_aliases . $opt_type;
|
293
|
|
|
|
|
|
|
push @options, [$opt_kw_with_aliases, $opt_type, $label];
|
294
|
|
|
|
|
|
|
}
|
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
# calculate the width of the option spec column so the labels,
|
297
|
|
|
|
|
|
|
# which we print as comment in the text output, will line up
|
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
my $colwidth = 0;
|
300
|
|
|
|
|
|
|
foreach my $opt_aref (@options) {
|
301
|
|
|
|
|
|
|
my ($opt_kw_with_aliases, $opt_type, $label) = $opt_aref->@*;
|
302
|
|
|
|
|
|
|
my $len = length( q(') . $opt_kw_with_aliases . $opt_type . q(', ) );
|
303
|
|
|
|
|
|
|
$colwidth = $len if $len > $colwidth;
|
304
|
|
|
|
|
|
|
}
|
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
my $fmt = sprintf '%%-%ds', $colwidth;
|
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
foreach my $opt_aref (@options) {
|
309
|
|
|
|
|
|
|
my ($opt_kw_with_aliases, $opt_type, $label) = $opt_aref->@*;
|
310
|
|
|
|
|
|
|
my $kw = sprintf $fmt, q(') . $opt_kw_with_aliases . $opt_type . q(',);
|
311
|
|
|
|
|
|
|
push @options_text, $kw . '# ' . $label;
|
312
|
|
|
|
|
|
|
}
|
313
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
return wantarray ? ( @options_list ) : join $NEWLINE, sort @options_text;
|
315
|
|
|
|
|
|
|
}
|
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
=head2 get_option_choices ( $option )
|
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
Returns: \%choices
|
321
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
Find all the options which have a multiple choice response, and return
|
323
|
|
|
|
|
|
|
a hash keyed on the option name and with a values consisting
|
324
|
|
|
|
|
|
|
of a hash of the valid responses as value/label pairs.
|
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
=cut
|
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
method get_option_choices :common () {
|
329
|
|
|
|
|
|
|
my %choices;
|
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
foreach my $row ( $Tk_opt_table->@* ) {
|
332
|
|
|
|
|
|
|
next if ref $row ne 'ARRAY';
|
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
# pick off the first three values, then slurp the rest
|
335
|
|
|
|
|
|
|
my ($opt_kw, $opt_type, $default, @others) = $row->@*;
|
336
|
|
|
|
|
|
|
# skip the group dividers
|
337
|
|
|
|
|
|
|
next if not $opt_kw;
|
338
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
my $href;
|
340
|
|
|
|
|
|
|
if (@others and ref $others[0] eq 'HASH' ) {
|
341
|
|
|
|
|
|
|
$href = $others[0];
|
342
|
|
|
|
|
|
|
} elsif (@others % 2 == 0) {
|
343
|
|
|
|
|
|
|
$href = { @others };
|
344
|
|
|
|
|
|
|
} else {
|
345
|
|
|
|
|
|
|
croak "*E* unable to parse opttable: @others";
|
346
|
|
|
|
|
|
|
}
|
347
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
my %hv;
|
349
|
|
|
|
|
|
|
if ( $href->{'choices'} and ref $href->{'choices'} eq 'ARRAY' ) {
|
350
|
|
|
|
|
|
|
foreach my $slot ( $href->{'choices'}->@* ) {
|
351
|
|
|
|
|
|
|
if (ref $slot eq 'ARRAY') {
|
352
|
|
|
|
|
|
|
$hv{ $slot->[1] } = $slot->[0];
|
353
|
|
|
|
|
|
|
}
|
354
|
|
|
|
|
|
|
elsif (ref $slot eq $EMPTY) {
|
355
|
|
|
|
|
|
|
$hv{ $slot } = $TRUE;
|
356
|
|
|
|
|
|
|
}
|
357
|
|
|
|
|
|
|
}
|
358
|
|
|
|
|
|
|
$choices{$opt_kw} = \%hv;
|
359
|
|
|
|
|
|
|
}
|
360
|
|
|
|
|
|
|
}
|
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
return \%choices;
|
363
|
|
|
|
|
|
|
}
|
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
=head2 get_option_defaults
|
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
Returns: \%defaults
|
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
Returns the option defaults, obtained from the same predefined list
|
370
|
|
|
|
|
|
|
of lists/strings returned by get_tk_options_table.
|
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
=cut
|
373
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
method get_option_defaults :common () {
|
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
my %defaults = ();
|
377
|
|
|
|
|
|
|
foreach my $slot ($Tk_opt_table->@*) {
|
378
|
|
|
|
|
|
|
next if ref $slot ne 'ARRAY';
|
379
|
|
|
|
|
|
|
my $key = $slot->[0];
|
380
|
|
|
|
|
|
|
next if not $key;
|
381
|
|
|
|
|
|
|
my $default_value = $slot->[2];
|
382
|
|
|
|
|
|
|
$defaults{$key} = $default_value;
|
383
|
|
|
|
|
|
|
}
|
384
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
return \%defaults;
|
386
|
|
|
|
|
|
|
}
|
387
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
=head2 valid_report_type ($rt, \@opttable)
|
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
This function is used to validate the report type. Valid values are
|
391
|
|
|
|
|
|
|
defined in the built-in Tk options table, which can be obtained by
|
392
|
|
|
|
|
|
|
calling:
|
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
my @opttable = ( Weather::GHCN::Options->get_tk_options_table() );
|
395
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
=cut
|
397
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
method valid_report_type :common ($rt, $opttable_aref) {
|
399
|
|
|
|
|
|
|
my $choices_href = Weather::GHCN::Options->get_option_choices;
|
400
|
|
|
|
|
|
|
return $choices_href->{'report'}->{ lc $rt };
|
401
|
|
|
|
|
|
|
}
|
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
=head2 deabbrev_report_type ($rt)
|
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
The report types supported by the -report option can be abbrevated,
|
406
|
|
|
|
|
|
|
so long as the abbrevation is unambiquous. For example, 'daily' can
|
407
|
|
|
|
|
|
|
be abbreviated to 'dail', 'dai', or 'da', but not 'd' because 'detail'
|
408
|
|
|
|
|
|
|
is also a valid report type and 'd' would not disambiguate the two.
|
409
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
This function takes a (possibly abbreviated) report type and returns
|
411
|
|
|
|
|
|
|
an unabbreviated report type.
|
412
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
=cut
|
414
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
method deabbrev_report_type :common ($rt) {
|
416
|
|
|
|
|
|
|
my %r_abbrev = abbrev( @REPORT_TYPE_LIST );
|
417
|
|
|
|
|
|
|
my $deabbreved = $r_abbrev{ lc $rt };
|
418
|
|
|
|
|
|
|
return $deabbreved;
|
419
|
|
|
|
|
|
|
}
|
420
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
=head2 valid_refresh_option ($refresh, \@opttable)
|
422
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
This function is used to validate the refresh option. Valid values are
|
424
|
|
|
|
|
|
|
defined in the built-in Tk options table, which can be obtained by
|
425
|
|
|
|
|
|
|
calling:
|
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
my @opttable = ( Weather::GHCN::Options->get_tk_options_table() );
|
428
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
=cut
|
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
method valid_refresh_option :common ($refresh, $opttable_aref) {
|
432
|
|
|
|
|
|
|
my $choices_href = Weather::GHCN::Options->get_option_choices;
|
433
|
|
|
|
|
|
|
# we only validate the non-numeric options
|
434
|
|
|
|
|
|
|
return $TRUE if $refresh =~ m{ \A \d+ \Z }xms;
|
435
|
|
|
|
|
|
|
return $choices_href->{'refresh'}->{ lc $refresh };
|
436
|
|
|
|
|
|
|
}
|
437
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
=head2 deabbrev_refresh_option ($refresh)
|
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
The refresh option values can be abbrevated, so long as the abbrevation
|
441
|
|
|
|
|
|
|
is unambiquous. For example, 'yearly' can
|
442
|
|
|
|
|
|
|
be abbreviated to 'y', 'ye', 'yea', etc.
|
443
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
This function takes a (possibly abbreviated) refresh option and returns
|
445
|
|
|
|
|
|
|
an unabbreviated refresh option.
|
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
=cut
|
448
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
method deabbrev_refresh_option :common ($refresh) {
|
450
|
|
|
|
|
|
|
# we only deabbreviate the non-numeric options
|
451
|
|
|
|
|
|
|
return $refresh if $refresh =~ m{ \A \d+ \Z }xms;
|
452
|
|
|
|
|
|
|
my %r_abbrev = abbrev( qw(yearly never always) );
|
453
|
|
|
|
|
|
|
my $deabbreved = $r_abbrev{ lc $refresh };
|
454
|
|
|
|
|
|
|
return $deabbreved;
|
455
|
|
|
|
|
|
|
}
|
456
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
######################################################################
|
459
|
|
|
|
|
|
|
=head1 INSTANCE METHODS
|
460
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
=over 4
|
462
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
=item combine_options ( $user_opt_href, $profile_href={} )
|
464
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
Returns: ($opt_href, $opt_obj)
|
466
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
This method takes a hash reference containing user options, and optionally
|
468
|
|
|
|
|
|
|
a hash reference of profile options, and combines them with default
|
469
|
|
|
|
|
|
|
values. The end result is a complete set of all the options
|
470
|
|
|
|
|
|
|
supported by Weather::GHCN::StationTable with user-specified options taking
|
471
|
|
|
|
|
|
|
precedence over profile options, and profile options taking precedence
|
472
|
|
|
|
|
|
|
over defaults.
|
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
This set of options is returned as both a hash reference and as a
|
475
|
|
|
|
|
|
|
Hash::Wrap object. The latter is preferred for use by consuming
|
476
|
|
|
|
|
|
|
applications, because it provides accessor methods for each option.
|
477
|
|
|
|
|
|
|
In addition, an ->defined( " |
478
|
|
|
|
|
|
|
your code can determine whether an option value was set to B.
|
479
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
The advantage to using an option object rather than an option hash
|
481
|
|
|
|
|
|
|
is that a misspelled option name will cause a runtime error.
|
482
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
=back
|
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
=cut
|
486
|
|
|
|
|
|
|
|
487
|
124
|
|
|
124
|
1
|
197971
|
method combine_options ( $user_opt_href, $profile_href={} ) {
|
|
124
|
|
|
|
|
224
|
|
|
124
|
|
|
|
|
223
|
|
|
124
|
|
|
|
|
203
|
|
|
124
|
|
|
|
|
173
|
|
488
|
|
|
|
|
|
|
# assign the class-level tk_options_table aref, generated before BUILD, to the instance field
|
489
|
124
|
|
|
|
|
208
|
$_tk_opt_aref = $Tk_opt_table;
|
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
# start with the user options
|
492
|
124
|
|
|
|
|
476
|
my %merged_options = ( $user_opt_href->%* );
|
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
# merge in the profile options
|
495
|
124
|
|
|
|
|
544
|
while ( my ($k,$v) = each $profile_href->%* ) {
|
496
|
8
|
|
33
|
|
|
76
|
$merged_options{$k} //= $v;
|
497
|
|
|
|
|
|
|
}
|
498
|
|
|
|
|
|
|
|
499
|
124
|
|
|
|
|
324
|
my $defaults_href = get_option_defaults();
|
500
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
# merge in the defaults
|
502
|
124
|
|
|
|
|
554
|
while ( my ($k,$v) = each $defaults_href->%* ) {
|
503
|
3100
|
|
100
|
|
|
10807
|
$merged_options{$k} //= $v;
|
504
|
|
|
|
|
|
|
}
|
505
|
|
|
|
|
|
|
|
506
|
124
|
|
|
|
|
290
|
$_opt_href = \%merged_options;
|
507
|
124
|
|
|
|
|
3318
|
$_opt_obj = _wrap_hash \%merged_options;
|
508
|
|
|
|
|
|
|
|
509
|
124
|
|
|
|
|
2376
|
return ($_opt_href, $_opt_obj);
|
510
|
|
|
|
|
|
|
}
|
511
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
=head2 initialize
|
513
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
Returns: @errors
|
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
This method initializes options that can't simply be initialized by
|
517
|
|
|
|
|
|
|
constants. Specifically:
|
518
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
=over 4
|
520
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
=item Aliases
|
522
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
Alias entries defined in the user profile are matched against
|
524
|
|
|
|
|
|
|
the -location option value. If a match is found to the alias name,
|
525
|
|
|
|
|
|
|
the alias value is substituted for the location value.
|
526
|
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
Alias names must be lowercase letters only. An optional underscore
|
528
|
|
|
|
|
|
|
prefix is permitted. Names not matching this rule will be silently
|
529
|
|
|
|
|
|
|
ignored by initialize().
|
530
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
=item country
|
532
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
The B option value can be:
|
534
|
|
|
|
|
|
|
|
535
|
|
|
|
|
|
|
* a 2-character GEC (FIPS) country code
|
536
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
* a 3-character alpha ISO 3166 country code
|
538
|
|
|
|
|
|
|
|
539
|
|
|
|
|
|
|
* a 3-digit numeric ISO 3166 country number
|
540
|
|
|
|
|
|
|
|
541
|
|
|
|
|
|
|
* an internet domain country suffix (e.g. '.ca')
|
542
|
|
|
|
|
|
|
|
543
|
|
|
|
|
|
|
* a 3-character regex string
|
544
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
If a regex string is given, then it will be matched (unanchored and
|
546
|
|
|
|
|
|
|
case insensitve) against country names. If multiple matches are
|
547
|
|
|
|
|
|
|
found, then an error is returned and the user will need to provide a
|
548
|
|
|
|
|
|
|
more specific pattern.
|
549
|
|
|
|
|
|
|
|
550
|
|
|
|
|
|
|
=item active
|
551
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
The B option filters stations according to the years that
|
553
|
|
|
|
|
|
|
they were active. If the B option is specified, but the
|
554
|
|
|
|
|
|
|
B option is not, then B will set the B
|
555
|
|
|
|
|
|
|
option value to the B option value so that only stations that
|
556
|
|
|
|
|
|
|
were active during the requested data range will be selected.
|
557
|
|
|
|
|
|
|
|
558
|
|
|
|
|
|
|
=item quality
|
559
|
|
|
|
|
|
|
|
560
|
|
|
|
|
|
|
The B option determines whether a station's data will be
|
561
|
|
|
|
|
|
|
included in the output when it has missing data. Quality is
|
562
|
|
|
|
|
|
|
expressed as a number between 0 and 100, representing the percentage
|
563
|
|
|
|
|
|
|
of data that cannot be missing; 90% is the default For example, if
|
564
|
|
|
|
|
|
|
you have a range of 3 years (1095 days) when B is 90, then
|
565
|
|
|
|
|
|
|
you need 90% x 1095 = 985 days of data. Anything less and the
|
566
|
|
|
|
|
|
|
station is rejected.
|
567
|
|
|
|
|
|
|
|
568
|
|
|
|
|
|
|
When filters fmonth and fday are used, the amount of data included
|
569
|
|
|
|
|
|
|
will typically drop far below 90% thereby rejecting all stations.
|
570
|
|
|
|
|
|
|
To avoid this nuisance, B will set quality to 0% if
|
571
|
|
|
|
|
|
|
either the B or B options are present.
|
572
|
|
|
|
|
|
|
|
573
|
|
|
|
|
|
|
=back
|
574
|
|
|
|
|
|
|
|
575
|
|
|
|
|
|
|
=cut
|
576
|
|
|
|
|
|
|
|
577
|
124
|
|
|
124
|
1
|
379
|
method initialize () {
|
|
124
|
|
|
|
|
214
|
|
|
124
|
|
|
|
|
182
|
|
578
|
124
|
|
|
|
|
191
|
my @errors;
|
579
|
|
|
|
|
|
|
|
580
|
124
|
100
|
|
|
|
2152
|
if ( $_opt_obj->country ) {
|
581
|
|
|
|
|
|
|
# using undef as the search type so it will figure it out based
|
582
|
|
|
|
|
|
|
# on the value pattern and length
|
583
|
8
|
|
|
|
|
213
|
my @cou = search_country( $_opt_obj->country, undef );
|
584
|
|
|
|
|
|
|
|
585
|
8
|
100
|
|
|
|
40
|
push @errors, '*E* unrecognized country code or name'
|
586
|
|
|
|
|
|
|
if not @cou;
|
587
|
|
|
|
|
|
|
|
588
|
8
|
100
|
|
|
|
36
|
push @errors, '*E* ambiguous country code or name'
|
589
|
|
|
|
|
|
|
if @cou > 1;
|
590
|
|
|
|
|
|
|
|
591
|
|
|
|
|
|
|
# return the GEC (FIPS) country code, which is what GHCN uses
|
592
|
8
|
|
|
|
|
222
|
$_opt_obj->country = $cou[0]->{gec};
|
593
|
|
|
|
|
|
|
}
|
594
|
|
|
|
|
|
|
|
595
|
|
|
|
|
|
|
# default the station active range to the year filter range if its value is an empty string
|
596
|
124
|
100
|
100
|
|
|
4717
|
if ( $_opt_obj->defined('active') and $_opt_obj->active eq $EMPTY ) {
|
597
|
5
|
|
|
|
|
256
|
$_opt_obj->active = $_opt_obj->range;
|
598
|
|
|
|
|
|
|
}
|
599
|
|
|
|
|
|
|
|
600
|
124
|
100
|
100
|
|
|
3315
|
$_opt_obj->quality = 0
|
601
|
|
|
|
|
|
|
if $_opt_obj->fmonth or $_opt_obj->fday;
|
602
|
|
|
|
|
|
|
|
603
|
124
|
|
|
|
|
7269
|
return @errors;
|
604
|
|
|
|
|
|
|
}
|
605
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
=head2 options_as_string
|
607
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
This option returns a string that contains all the options and their
|
609
|
|
|
|
|
|
|
values, in a format similar to what they would look like when entered
|
610
|
|
|
|
|
|
|
as command-line arguments. For boolean options only the option name
|
611
|
|
|
|
|
|
|
is include (no value). Option values containing whitespace are
|
612
|
|
|
|
|
|
|
enclosed in double quotes. Option/value pairs are separated by
|
613
|
|
|
|
|
|
|
two spaces.
|
614
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
This method is primarily provided so the consuming application can
|
616
|
|
|
|
|
|
|
print the options that were used during a run, perhaps to a log or
|
617
|
|
|
|
|
|
|
in the output.
|
618
|
|
|
|
|
|
|
|
619
|
|
|
|
|
|
|
=cut
|
620
|
|
|
|
|
|
|
|
621
|
6
|
|
|
6
|
1
|
467
|
method options_as_string () {
|
|
6
|
|
|
|
|
15
|
|
|
6
|
|
|
|
|
12
|
|
622
|
6
|
|
|
|
|
11
|
my @options;
|
623
|
6
|
|
|
|
|
26
|
my $boolean = _get_boolean_options($Tk_opt_table);
|
624
|
|
|
|
|
|
|
|
625
|
6
|
|
|
|
|
95
|
foreach my $k ( sort keys $_opt_href->%* ) {
|
626
|
153
|
100
|
|
|
|
270
|
next if $k eq 'aliases';
|
627
|
150
|
100
|
|
|
|
280
|
next if $k eq 'cachedir';
|
628
|
144
|
100
|
|
|
|
235
|
next if $k eq 'profile';
|
629
|
138
|
|
|
|
|
218
|
my $v = $_opt_href->{$k};
|
630
|
138
|
100
|
|
|
|
248
|
next if not defined $v;
|
631
|
|
|
|
|
|
|
|
632
|
49
|
100
|
|
|
|
98
|
if ( $boolean->{$k} ) {
|
633
|
2
|
|
|
|
|
6
|
push @options, $DASH . $k;
|
634
|
2
|
|
|
|
|
5
|
next;
|
635
|
|
|
|
|
|
|
}
|
636
|
|
|
|
|
|
|
|
637
|
47
|
|
|
|
|
74
|
my $val = $v;
|
638
|
|
|
|
|
|
|
|
639
|
47
|
50
|
|
|
|
141
|
if ( $val =~ m{\A \s* \Z}xms ) {
|
640
|
0
|
|
|
|
|
0
|
$val = q(") . $val . q(");
|
641
|
|
|
|
|
|
|
}
|
642
|
47
|
|
|
|
|
128
|
push @options, $DASH . $k. $SPACE . $val;
|
643
|
|
|
|
|
|
|
}
|
644
|
6
|
|
|
|
|
79
|
return join $SPACE x 2, @options;
|
645
|
|
|
|
|
|
|
}
|
646
|
|
|
|
|
|
|
|
647
|
|
|
|
|
|
|
=head2 validate
|
648
|
|
|
|
|
|
|
|
649
|
|
|
|
|
|
|
Returns: @errors
|
650
|
|
|
|
|
|
|
|
651
|
|
|
|
|
|
|
This method is called by StationTable->set_options to make sure all
|
652
|
|
|
|
|
|
|
the options that were provided to B are valid. It also
|
653
|
|
|
|
|
|
|
handles abbreviations for options color and report. Any errors
|
654
|
|
|
|
|
|
|
arising from invalid value or from problems detected during
|
655
|
|
|
|
|
|
|
B (which is called at the end of B) are returned
|
656
|
|
|
|
|
|
|
in a list.
|
657
|
|
|
|
|
|
|
|
658
|
|
|
|
|
|
|
=cut
|
659
|
|
|
|
|
|
|
|
660
|
124
|
|
|
124
|
1
|
4610
|
method validate () {
|
|
124
|
|
|
|
|
200
|
|
|
124
|
|
|
|
|
173
|
|
661
|
124
|
|
|
|
|
204
|
my @errors;
|
662
|
124
|
|
|
|
|
197
|
my $bad_range_cnt = 0;
|
663
|
|
|
|
|
|
|
|
664
|
124
|
100
|
|
|
|
2528
|
if ( $_opt_obj->defined('aliases') ) {
|
665
|
9
|
|
|
|
|
239
|
foreach my $alias_name ( keys $_opt_obj->aliases->%* ) {
|
666
|
23
|
|
|
|
|
1679
|
my $errmsg = '*E* alias names in profile must be lowercase letters with optional underscore prefix: ' . $alias_name;
|
667
|
23
|
100
|
|
|
|
166
|
push @errors, $errmsg
|
668
|
|
|
|
|
|
|
unless $alias_name =~ $ALIAS_NAME_RE;
|
669
|
|
|
|
|
|
|
}
|
670
|
|
|
|
|
|
|
}
|
671
|
|
|
|
|
|
|
|
672
|
124
|
100
|
|
|
|
2598
|
if ( $_opt_obj->active ) {
|
673
|
24
|
100
|
|
|
|
640
|
if ( not $_opt_obj->active =~ m{ \A (18|19|20)\d\d [-] (18|19|20)\d\d }xms ) {
|
674
|
7
|
|
|
|
|
183
|
push @errors, '*E* invalid -active year range ' . $_opt_obj->active;
|
675
|
7
|
|
|
|
|
74
|
$bad_range_cnt++;
|
676
|
|
|
|
|
|
|
}
|
677
|
|
|
|
|
|
|
}
|
678
|
|
|
|
|
|
|
|
679
|
124
|
100
|
|
|
|
4668
|
if ( $_opt_obj->range ) {
|
680
|
33
|
100
|
|
|
|
883
|
if ( not $_opt_obj->range =~ m{ \A (18|19|20)\d\d [-,] (18|19|20)\d\d }xms ) {
|
681
|
7
|
|
|
|
|
185
|
push @errors, '*E* invalid -range ' . $_opt_obj->range;
|
682
|
7
|
|
|
|
|
74
|
$bad_range_cnt++;
|
683
|
|
|
|
|
|
|
}
|
684
|
|
|
|
|
|
|
}
|
685
|
|
|
|
|
|
|
|
686
|
124
|
100
|
100
|
|
|
4413
|
push @errors, '*E* invalid 2-character state or province code ' . $_opt_obj->state
|
687
|
|
|
|
|
|
|
if $_opt_obj->defined('state') and not $_opt_obj->state =~ m{ \A [[:alpha:]]{2} \Z }xms;
|
688
|
|
|
|
|
|
|
|
689
|
124
|
100
|
100
|
|
|
3505
|
push @errors, '*E* -partial only allowed if -active specified'
|
690
|
|
|
|
|
|
|
if $_opt_obj->partial and not $_opt_obj->defined('active');
|
691
|
|
|
|
|
|
|
|
692
|
|
|
|
|
|
|
# Note: full Condition Coverage in Devel::Cover seems impossible if these two ifs are combined
|
693
|
|
|
|
|
|
|
# (I tried every combination of uncoverable branch and condition I could think of to
|
694
|
|
|
|
|
|
|
# to suppress the missing case. In the end, this was the only thing that worked.)
|
695
|
124
|
100
|
100
|
|
|
4520
|
if ( $_opt_obj->range and $_opt_obj->active ) {
|
696
|
|
|
|
|
|
|
# uncoverable branch false
|
697
|
9
|
50
|
|
|
|
317
|
if ( $bad_range_cnt == 0 ) {
|
698
|
9
|
|
|
|
|
135
|
my $r = rng_new( $_opt_obj->range );
|
699
|
9
|
|
|
|
|
197
|
my $a = rng_new( $_opt_obj->active );
|
700
|
|
|
|
|
|
|
|
701
|
9
|
100
|
|
|
|
29
|
push @errors, '*E* -range must be a subset of -active'
|
702
|
|
|
|
|
|
|
if not $r->subset($a);
|
703
|
|
|
|
|
|
|
}
|
704
|
|
|
|
|
|
|
}
|
705
|
|
|
|
|
|
|
|
706
|
124
|
100
|
100
|
|
|
7472
|
push @errors, '*E* -gps argument must be decimal lat/long, separated by spaces or punctuation'
|
707
|
|
|
|
|
|
|
if $_opt_obj->gps and $_opt_obj->gps !~ m{ \A [+-]? \d{1,3} [.] \d+ (?: [[:punct:]] | \s+ ) [+-]? \d{1,3} [.] \d+ \Z }xms;
|
708
|
|
|
|
|
|
|
|
709
|
124
|
|
|
|
|
3176
|
my %report_abbrev = abbrev( @REPORT_TYPE_LIST );
|
710
|
|
|
|
|
|
|
|
711
|
124
|
|
|
|
|
28411
|
my $report = lc $_opt_obj->report;
|
712
|
|
|
|
|
|
|
|
713
|
|
|
|
|
|
|
# uncoverable branch true
|
714
|
124
|
50
|
|
|
|
3039
|
croak '*E* undef report type'
|
715
|
|
|
|
|
|
|
if not defined $report;
|
716
|
|
|
|
|
|
|
|
717
|
|
|
|
|
|
|
push @errors, '*E* invalid report option: ' . $report
|
718
|
124
|
100
|
100
|
|
|
431
|
if $report and not $report_abbrev{ $report };
|
719
|
|
|
|
|
|
|
|
720
|
124
|
|
|
|
|
2080
|
$_opt_obj->report = $report_abbrev{ $report };
|
721
|
|
|
|
|
|
|
|
722
|
|
|
|
|
|
|
|
723
|
124
|
|
|
|
|
1401
|
my %refresh_abbrev = abbrev( qw(yearly never always) );
|
724
|
|
|
|
|
|
|
|
725
|
124
|
|
|
|
|
14754
|
my $refresh = lc $_opt_obj->refresh;
|
726
|
|
|
|
|
|
|
|
727
|
|
|
|
|
|
|
# uncoverable branch true
|
728
|
124
|
50
|
|
|
|
1948
|
croak '*E* undef refresh option'
|
729
|
|
|
|
|
|
|
if not defined $refresh;
|
730
|
|
|
|
|
|
|
|
731
|
|
|
|
|
|
|
push @errors, '*E* invalid refresh option: ' . $refresh
|
732
|
124
|
50
|
33
|
|
|
601
|
if $refresh and not $refresh_abbrev{ $refresh };
|
733
|
|
|
|
|
|
|
|
734
|
124
|
|
|
|
|
2041
|
$_opt_obj->refresh = $refresh_abbrev{ $refresh };
|
735
|
|
|
|
|
|
|
|
736
|
|
|
|
|
|
|
#-----------------------------------------------------------------
|
737
|
|
|
|
|
|
|
# end of noted section
|
738
|
|
|
|
|
|
|
#-----------------------------------------------------------------
|
739
|
|
|
|
|
|
|
|
740
|
124
|
|
|
|
|
1374
|
my %color_abbrev = abbrev( qw(blue green azure purple red white yellow) );
|
741
|
|
|
|
|
|
|
|
742
|
|
|
|
|
|
|
# uncoverable branch false
|
743
|
124
|
50
|
|
|
|
24687
|
if ( $_opt_obj->defined('kmlcolor') ) {
|
744
|
124
|
|
|
|
|
2577
|
my $kmlcolor = $_opt_obj->kmlcolor;
|
745
|
124
|
100
|
|
|
|
2912
|
if ( $kmlcolor eq $EMPTY ) {
|
746
|
1
|
|
|
|
|
3
|
push @errors, '*E* invalid -kmlcolor value ""'
|
747
|
|
|
|
|
|
|
} else {
|
748
|
|
|
|
|
|
|
push @errors, '*E* invalid -kmlcolor value'
|
749
|
123
|
100
|
|
|
|
331
|
if not $color_abbrev{ $kmlcolor };
|
750
|
|
|
|
|
|
|
}
|
751
|
124
|
|
|
|
|
1941
|
$_opt_obj->kmlcolor = $color_abbrev{ $kmlcolor };
|
752
|
|
|
|
|
|
|
}
|
753
|
|
|
|
|
|
|
|
754
|
|
|
|
|
|
|
|
755
|
124
|
100
|
|
|
|
2954
|
if ( $_opt_obj->defined('fmonth') ) {
|
756
|
15
|
100
|
100
|
|
|
355
|
push @errors, '*E* -fmonth must be a single number or valid range spec (e.g. 1-5,9)'
|
757
|
|
|
|
|
|
|
if not rng_valid($_opt_obj->fmonth)
|
758
|
|
|
|
|
|
|
or not rng_within($_opt_obj->fmonth, '1-12');
|
759
|
|
|
|
|
|
|
}
|
760
|
|
|
|
|
|
|
|
761
|
124
|
100
|
|
|
|
9041
|
if ( $_opt_obj->defined('fday') ) {
|
762
|
15
|
100
|
100
|
|
|
321
|
push @errors, '*E* -fday must be a single number or valid range spec (e.g. 3,15,20-31)'
|
763
|
|
|
|
|
|
|
if not rng_valid($_opt_obj->fday)
|
764
|
|
|
|
|
|
|
or not rng_within($_opt_obj->fday, '1-31');
|
765
|
|
|
|
|
|
|
}
|
766
|
|
|
|
|
|
|
|
767
|
124
|
|
|
|
|
7204
|
my @init_errors = $self->initialize();
|
768
|
|
|
|
|
|
|
|
769
|
124
|
|
|
|
|
1869
|
return (@errors, @init_errors);
|
770
|
|
|
|
|
|
|
}
|
771
|
|
|
|
|
|
|
|
772
|
|
|
|
|
|
|
=head2 DOES
|
773
|
|
|
|
|
|
|
|
774
|
|
|
|
|
|
|
Defined by Object::Pad. Included for POD::Coverage.
|
775
|
|
|
|
|
|
|
|
776
|
|
|
|
|
|
|
=head2 META
|
777
|
|
|
|
|
|
|
|
778
|
|
|
|
|
|
|
Defined by Object::Pad. Included for POD::Coverage.
|
779
|
|
|
|
|
|
|
|
780
|
|
|
|
|
|
|
=cut
|
781
|
|
|
|
|
|
|
|
782
|
|
|
|
|
|
|
######################################################################
|
783
|
|
|
|
|
|
|
# Subroutines
|
784
|
|
|
|
|
|
|
######################################################################
|
785
|
|
|
|
|
|
|
|
786
|
7
|
|
|
7
|
|
17
|
sub _get_boolean_options ($_tk_opt_aref) {
|
|
7
|
|
|
|
|
13
|
|
|
7
|
|
|
|
|
16
|
|
787
|
|
|
|
|
|
|
|
788
|
7
|
|
|
|
|
12
|
my %boolean;
|
789
|
|
|
|
|
|
|
|
790
|
7
|
|
|
|
|
23
|
foreach my $row ( $_tk_opt_aref->@* ) {
|
791
|
231
|
100
|
|
|
|
435
|
next unless ref $row eq 'ARRAY';
|
792
|
196
|
|
|
|
|
467
|
my ($name, $type) = $row->@*;
|
793
|
196
|
100
|
|
|
|
462
|
$boolean{$name}++ if $type eq $BANG;
|
794
|
|
|
|
|
|
|
}
|
795
|
|
|
|
|
|
|
|
796
|
7
|
|
|
|
|
29
|
return \%boolean;
|
797
|
|
|
|
|
|
|
}
|
798
|
|
|
|
|
|
|
|
799
|
|
|
|
|
|
|
1; |