File Coverage

blib/lib/Rex/Dondley/ProcessTaskArgs.pm
Criterion Covered Total %
statement 72 74 97.3
branch 31 34 91.1
condition 17 24 70.8
subroutine 4 4 100.0
pod 1 1 100.0
total 125 137 91.2


line stmt bran cond sub pod time code
1             package Rex::Dondley::ProcessTaskArgs ;
2             $Rex::Dondley::ProcessTaskArgs::VERSION = '0.013';
3 5     5   103571 use strict;
  5         35  
  5         159  
4 5     5   24 use warnings;
  5         9  
  5         313  
5              
6             require Exporter;
7             our @ISA = qw(Exporter);
8             our @EXPORT = 'process_task_args';
9 5     5   2714 use Log::Log4perl::Shortcuts qw(:all);
  5         922617  
  5         4713  
10              
11             # checks validity of args passed to functions and assigns them to appropriate keys
12             # Accept 3 sets of args:
13             # first arg is reference to parameters passed by user to task
14             # next set of args is a list of allowed params which can also indicate required params
15             # last arg is an array hash for default values corresponding to each allowed params
16             sub process_task_args {
17 35 50 33 35 1 1071058 if (!$_[0] || (ref $_[0]) ne 'ARRAY') {
18 0         0 die 'First argument must be an array ref to user supplied arguments.';
19             }
20              
21             # standardize the argument data structure
22 35 100 66     672 if (ref $_[0] && ref $_[0]->[0] ne 'HASH') {
23 7         29 my @args = @{$_[0]};
  7         77  
24 7         28 shift @_;
25 7         95 @_ = ([ {}, \@args,], @_ );
26             }
27              
28 35         243 my $passed_in = shift @_;
29 35         92 my %passed_params = %{$passed_in->[0]};
  35         351  
30 35         132 my @unkeyed_args = @{$passed_in->[1]};
  35         274  
31 35 100       276 my @defaults = ref $_[-1] ? @{$_[-1]} : ();
  6         77  
32 35 100       143 pop @_ if @defaults;
33 35         444 my @valid_args = @_;
34 35 100 100     164 my @key_list = grep { $_ && $_ ne '1' && (ref $_) ne 'ARRAY' } @_;
  137         1203  
35              
36 35         106 my %defaults = ();
37 35         165 my $count = 0;
38 35         195 foreach my $key (@key_list) {
39 74         554 $defaults{$key} = $defaults[$count++];
40             }
41              
42             # create a hash of valid and required keys
43             # assumes all values are not required if @valid_args do not contain required value
44 35         83 my @ordered_keys;
45 35         91 my %valid_keys = ();
46 35 100 100     947 if ((exists $valid_args[1] && ($valid_args[1] !~ /^0|1$/)) || scalar @valid_args == 1) { # checks to see if list contains required values
      100        
47 7         36 foreach my $arg (@valid_args) {
48 11         37 $valid_keys{$arg} = 0;
49 11         51 @ordered_keys = @valid_args;
50             }
51             } else {
52 28         309 %valid_keys = @valid_args;
53 28         132 my $count = 0;
54 28         94 foreach my $key (@valid_args) {
55 126 100       441 if (!($count++ % 2)) {
56 63         419 push @ordered_keys, $key;
57             }
58             }
59             }
60              
61             # check to see if passed parameters are valid
62 35         100 my @invalid_keys;
63 35         148 foreach my $key (keys %passed_params) {
64 24         155 my $is_valid = grep { $_ eq $key } keys %valid_keys;
  52         305  
65 24 100       115 if (!$is_valid) {
66 2         16 push @invalid_keys, $key;
67             }
68 24 100       186 die ("Invalid key(s): '" . join (', ', @invalid_keys) . "' from ". (caller)[1] . ', line ' . (caller)[2]) if @invalid_keys;
69             }
70              
71              
72             # Populate the %passed_params hash with @unkeyed_args according
73             # to same order they were passed to this function via @valid_args.
74             # Throw error if there are more args than available keys.
75 33 100       120 if (@unkeyed_args) {
76 12         32 my @all_array_args = @unkeyed_args;
77 12         46 foreach my $array_arg (@unkeyed_args) {
78 19         85 foreach my $vkey (@key_list) {
79 27 100       67 if (exists $passed_params{$vkey}) {
80 9         23 next;
81             }
82 18         37 $passed_params{$vkey} = $array_arg;
83 18         32 shift @all_array_args;
84              
85 18         35 last;
86             }
87             }
88 12 100       44 die ('Too many array arguments passed from ' . (caller)[1] . ', line ' . (caller)[2] ) if @all_array_args;
89              
90             }
91              
92             # Ensure required args are present
93 32         132 my @reqd_keys = grep { $valid_keys{$_} } keys %valid_keys;
  70         230  
94 32         101 my @missing_keys;
95              
96 32         80 foreach my $rkey(@reqd_keys) {
97 24 100 66     357 if (!exists $passed_params{$rkey} || $passed_params{$rkey} eq '1') {
98 10 100       57 push @missing_keys, $rkey unless $defaults{$rkey};
99             }
100             }
101 32 100       302 die ("Missing required key(s): '" . join (', ', @missing_keys) . "' from " . (caller)[1] . ', line ' . (caller)[2]) if @missing_keys;
102              
103             # handle edge case when user passes key without value
104 28         86 foreach my $key (keys %passed_params) {
105 36 0 66     315 if ($passed_params{$key} && $passed_params{$key} eq '1' && $valid_keys{$key}) {
      33        
106 0         0 delete $passed_params{$key};
107             }
108             }
109 28         230 my %return_hash = (%defaults, %passed_params);
110              
111              
112              
113 28 100       132 if (wantarray) {
114 1         4 my @blah = @return_hash{ @ordered_keys };
115 1         24 return @blah;
116             } else {
117 27         410 return \%return_hash;
118             }
119             }
120             # methods here
121              
122             1; # Magic true value
123             # ABSTRACT: easier Rex task argument handling
124              
125             __END__
126              
127             =pod
128              
129             =head1 NAME
130              
131             Rex::Dondley::ProcessTaskArgs - easier Rex task argument handling
132              
133             =head1 VERSION
134              
135             version 0.013
136              
137             =head1 SYNOPSIS
138              
139             use Rex::Dondley::ProcessTaskArgs;
140              
141             task 'some_task' => sub {
142             # Process args passed to task
143             my $params = process_task_args( \@_, # arguments passed by user
144             available_key1 => 1, # a required argument
145             available_key2 => 0, # an optional argument
146              
147             # optional array hash for default values
148             [
149             'default_value_for_key1',
150             'default_value_for_key2',
151             ]
152             );
153              
154             # Now retrieve the values as usual
155             my $key1 = $params->{key1};
156             my $key2 = $params->{key2};
157             };
158              
159             # If no arguments are required, list of available keys can be simplified:
160             task 'another_task' => sub {
161             my $params = process_task_args( \@_, key1, key2 [ 'default_value_for_key1' ]);
162             };
163              
164             # Params can also be returned in an array. The returned order is the same as
165             # the order of the list of available keys.
166             my ($one, $two, $three) = process_task_args( \@_, one => 1, two => 2, three => 3 );
167              
168             =head1 DESCRIPTION
169              
170             This module is designed to alleviate some of the pain of processing arguments
171             passed to tasks from the command line and from other tasks with the
172             C<run_task()> function. Think of it as a simpler, more specialized
173             version of L<Params::Validate>.
174              
175             This module supplies a single function, C<process_task_args>, which accepts
176             three different types of arguments:
177              
178             =over 1
179              
180             =item * An array reference containing the original C<@_> special variable, followed
181             by...
182              
183             =item * A list containing the available keys and, optionally, which keys are
184             required, followed by...
185              
186             =item * An optional array reference containing the default values in the order
187             corresponding to the list of available keys
188              
189             =back
190              
191             C<process_task_args> does the following:
192              
193             =over 1
194              
195             =item * Ensures all required keys are given
196              
197             =item * If arguments do not have associated keys on the command line, it will
198             assign them to the next avaiable key according to the order provided by the
199             available key list
200              
201             =item * Replaces missing arguments with the default values, if provided
202              
203             =item * Ensures no extra arguments are supplied
204              
205             =item * Properly handles parameters passed via C<run_task()> as an array
206             C<run_task('some_task', params =E<gt> [ 'some_value' ]);>
207              
208             =back
209              
210             =head2 Special Edge Cases: Setting arguments to a value of 1 and using keys as switches
211              
212             A special case exists if an argument is required and has a default value and you
213             are trying to set its value to "1". In such a case, your value will be
214             overridden if you supplied a default value for the key in your default values
215             argument.
216              
217             To circumvent this unwanted behavior, you must make the key optional.
218             Alternatively, remove the default value from the default values array and
219             process the key manually.
220              
221             Similarly, if you wish to use an argument as a switch, (i.e. setting a key
222             without a value with C<--some_key>), you must do the same.
223              
224             =head2 Examples
225              
226             =head3 Example #1
227              
228             Given the following code:
229              
230             task 'another_task' => sub {
231             my $params = process_task_args( \@_, key1, key2 [ 'default_value_for_key1' ] );
232             };
233              
234             And the following command line command:
235              
236             rex some_task
237              
238             C<$params> will be:
239              
240             { key1 => 'default_value_for_key1', key2 => undef };
241              
242             =head3 Example #2
243              
244             Given the following code:
245              
246             task 'another_task' => sub {
247             my ($key1, $key2) = process_task_args( \@_, key1, key2 [ 'default_value_for_key1' ] );
248             };
249              
250             And the following command line command:
251              
252             rex some_task one two
253              
254             C<$key1> will have a value of `one` and C<$key2> will have a value of `two`.
255              
256             This examples demonstrates that the function will return an array of values in
257             an array context.
258              
259             =head3 Example #3
260              
261             Given the following code:
262              
263             task 'another_task' => sub {
264             my $params = process_task_args( \@_, key1, key2 );
265             };
266              
267             And the following command line command:
268              
269             rex some_task some_value
270              
271             C<$params> will be:
272              
273             { key1 => 'some_value', key2 => undef };
274              
275             =head3 Example #4
276              
277             Given the following code:
278              
279             task 'another_task' => sub {
280             my $params = process_task_args( \@_, key1, key2 );
281             };
282              
283             And the following command line command:
284              
285             rex some_task some_value another_value
286              
287             C<$params> will be:
288              
289             { key1 => 'some_value', key2 => another_value };
290              
291             =head3 Example #4
292              
293             Given the following code:
294              
295             task 'another_task' => sub {
296             my $params = process_task_args( \@_, key1, key2 );
297             };
298              
299             And the following command line command:
300              
301             rex some_task some_value --key1=another_value
302              
303             C<$params> will be:
304              
305             { key1 => 'another_value', key2 => 'some_value' };
306              
307             =head3 Example #6
308              
309             Given the following code:
310              
311             task 'another_task' => sub {
312             my $params = process_task_args( \@_, key1 => 1, key2 => 1 );
313             };
314              
315             And the following command line command:
316              
317             rex some_task --key1=another_value
318              
319             B<ERROR!> because C<key2> is required and it was not supplied.
320              
321             =head1 FUNCTIONS
322              
323             =head2 my $params = process_task_args($array_ref, $available_key1 [ => 1|0 ], $available_key2 [ => 1|0 ], ..., [ $array_ref ];
324             =function my @values = process_task_args($array_ref, $available_key1 [ => 1|0 ], $available_key2 [ => 1|0 ], ..., [ $array_ref ];
325              
326             The function will return values with keys as a hash reference in a scalar
327             contect or as array with just the value depending on context. See L</SYNOPSIS>
328             and exmaples above for usage instructions.
329              
330             =for :stopwords cpan testmatrix url annocpan anno bugtracker rt cpants kwalitee diff irc mailto metadata placeholders metacpan
331              
332             =head1 SUPPORT
333              
334             =head2 Perldoc
335              
336             You can find documentation for this module with the perldoc command.
337              
338             perldoc Rex::Dondley::ProcessTaskArgs
339              
340             =head2 Websites
341              
342             The following websites have more information about this module, and may be of help to you. As always,
343             in addition to those websites please use your favorite search engine to discover more resources.
344              
345             =over 4
346              
347             =item *
348              
349             MetaCPAN
350              
351             A modern, open-source CPAN search engine, useful to view POD in HTML format.
352              
353             L<https://metacpan.org/release/Rex-Dondley-ProcessTaskArgs>
354              
355             =back
356              
357             =head2 Source Code
358              
359             The code is open to the world, and available for you to hack on. Please feel free to browse it and play
360             with it, or whatever. If you want to contribute patches, please send me a diff or prod me to pull
361             from your repository :)
362              
363             L<https://github.com/sdondley/Rex-Dondley-ProcessTaskArgs>
364              
365             git clone git://github.com/sdondley/Rex-Dondley-ProcessTaskArgs.git
366              
367             =head1 BUGS AND LIMITATIONS
368              
369             You can make new bug reports, and view existing ones, through the
370             web interface at L<https://github.com/sdondley/Rex-Dondley-ProcessTaskArgs/issues>.
371              
372             =head1 INSTALLATION
373              
374             See perlmodinstall for information and options on installing Perl modules.
375              
376             =head1 AUTHOR
377              
378             Steve Dondley <s@dondley.com>
379              
380             =head1 COPYRIGHT AND LICENSE
381              
382             This software is copyright (c) 2020 by Steve Dondley.
383              
384             This is free software; you can redistribute it and/or modify it under
385             the same terms as the Perl 5 programming language system itself.
386              
387             =cut