File Coverage

blib/lib/Getopt/Class.pm
Criterion Covered Total %
statement 462 604 76.4
branch 143 372 38.4
condition 83 240 34.5
subroutine 66 77 85.7
pod 17 18 94.4
total 771 1311 58.8


line stmt bran cond sub pod time code
1             ##----------------------------------------------------------------------------
2             ## Getopt::Long with Class - ~/lib/Getopt/Class.pm
3             ## Version v0.103.3
4             ## Copyright(c) 2022 DEGUEST Pte. Ltd.
5             ## Author: Jacques Deguest <jack@deguest.jp>
6             ## Created 2020/04/25
7             ## Modified 2022/11/23
8             ## All rights reserved
9             ##
10             ## This program is free software; you can redistribute it and/or modify it
11             ## under the same terms as Perl itself.
12             ##----------------------------------------------------------------------------
13             package Getopt::Class;
14             BEGIN
15             {
16 6     6   617086 use strict;
  6         63  
  6         179  
17 6     6   32 use warnings;
  6         13  
  6         162  
18 6     6   2699 use parent qw( Module::Generic );
  6         1745  
  6         32  
19 6     6   60976179 use vars qw( $VERSION );
  6         16  
  6         245  
20 6     6   5278 use DateTime;
  6         3010419  
  6         324  
21 6     6   4170 use DateTime::Format::Strptime;
  6         1231199  
  6         40  
22 6     6   4998 use Devel::Confess;
  6         46089  
  6         33  
23 6     6   4830 use Getopt::Long;
  6         62921  
  6         33  
24 6     6   4684 use Module::Generic::Array;
  6         62985  
  6         271  
25 6     6   7229 use Module::Generic::File qw( file );
  6         61299071  
  6         112  
26 6     6   5663 use Module::Generic::Scalar;
  6         15580203  
  6         248  
27 6     6   55 use Nice::Try;
  6         13  
  6         47  
28 6     6   11505943 use Scalar::Util;
  6         21  
  6         448  
29 6     6   127 our $VERSION = 'v0.103.3';
30             };
31              
32 6     6   39 use strict;
  6         14  
  6         159  
33 6     6   33 use warnings;
  6         13  
  6         3687  
34              
35             sub init
36             {
37 6     6 1 5755 my $self = shift( @_ );
38 6   50     33 my $param = shift( @_ ) || return( $self->error( "No hash parameter was provided." ) );
39 6 50       54 return( $self->error( "Hash of parameters provided ($param) is not an hash reference." ) ) if( !$self->_is_hash( $param ) );
40 6         143 $self->SUPER::init( $param );
41 6         1057 $self->{configured} = 0;
42 6         35 $self->{classes} = {};
43 6         22 $self->{missing} = [];
44 6         21 $self->{colour_open} = '<';
45 6         20 $self->{colour_close} = '>';
46            
47 6   50     75 my $dict = $param->{dictionary} || return( $self->error( "No dictionary was provided to initiate Getopt::Long" ) );
48 6 50       31 return( $self->error( "Dictionary provided is not a hash reference." ) ) if( !$self->_is_hash( $dict ) );
49 6         86 $self->dictionary( $dict );
50            
51             # Set the aliases hash reference used to contain each of the option aliases,e ach pointing to the same dictionary definition
52 6         822 $self->{aliases} = {};
53            
54             # Tie'ing will make sure that values set for a key or its aliases are populated to other aliases
55             # Getopt::Long already does it, but this takes care of synchronising values for all aliases AFTER Getopt::Long has processed the options
56             # So that if the user change an option value using an alias:, e.g.:
57             # last_name => { type => 'string', alias => [qw( surname )] }
58             # last_name and surname would have the same value set thanks to Getopt::Long
59             # --last-name = 'Einstein';
60             # But if, after, the user does something like:
61             # $opts->{surname} = 'Doe';
62             # $opts->{last_name} would still be 'Einstein'
63             # Getopt::Class::Alias ensures the values for aliases and original key are the same seamlessly
64             # The way tie works means we must tie en empty hash, because we cannot tie an already populated hash sadly enough
65 6         20 my %options = ();
66             my $tie = tie( %options, 'Getopt::Class::Alias',
67             {
68             dict => $dict,
69             aliases => $self->{aliases},
70             debug => $self->{debug}
71 6   50     75 }) || return( $self->error( "Unable to get a Getopt::Class::Alias tie object: ", Getopt::Class::Alias->error ) );
72            
73 6         43 $self->{configure_options} = [qw( no_ignore_case no_auto_abbrev auto_version auto_help )];
74 6         15 my $opts = \%options;
75 6         14 my $params = [];
76             # Build the options parameters
77 6         64 foreach my $k ( sort( keys( %$dict ) ) )
78             {
79 96         158 my $k2_dash = $k;
80 96         175 $k2_dash =~ tr/_/-/;
81 96         153 my $k2_under = $k;
82 96         132 $k2_under =~ tr/-/_/;
83            
84 96         155 my $def = $dict->{ $k };
85            
86 96         158 my $opt_name = [ $k2_under ];
87             # If the dictionary element is given with dash, e.g. some-thing, we replace it with some_thing, which is our standard
88             # and we set some-thing as an alias
89 96 50 33     314 if( CORE::index( $k, '-' ) != -1 && $k eq $k2_dash )
90             {
91 0         0 $dict->{ $k2_under } = CORE::delete( $dict->{ $k } );
92 0         0 $k = $k2_under;
93             }
94             # Add the dash option as an alias if it is not the same as the underscore one, such as when this is just one word, e.g. version
95 96 100       221 CORE::push( @$opt_name, $k2_dash ) if( $k2_dash ne $k2_under );
96            
97 96 100 66     350 if( !ref( $def->{alias} ) && CORE::length( $def->{alias} ) )
98             {
99 18         48 $def->{alias} = [$def->{alias}];
100             }
101             # Add the given aliases, if any
102 96 100       364 if( $self->_is_array( $def->{alias} ) )
103             {
104 18 50       199 push( @$opt_name, @{$def->{alias}} ) if( scalar( @{$def->{alias}} ) );
  18         42  
  18         58  
105             # push( @$opt_name, $k2_under ) if( !scalar( grep( /^$k2_under$/, @{$def->{alias}} ) ) );
106             }
107             # Now, also add the original key-something and key_something to the alias, so we can find them from one of the aliases
108             # When we do exec, we'll be able to find all the aliases
109 96 100       681 $def->{alias} = [] if( !CORE::exists( $def->{alias} ) );
110 96 50       148 CORE::push( @{$def->{alias}}, $k2_dash ) if( !scalar( grep( /^$k2_dash$/, @{$def->{alias}} ) ) );
  96         210  
  96         413  
111 96 100       249 CORE::push( @{$def->{alias}}, $k2_under ) if( !scalar( grep( /^$k2_under$/, @{$def->{alias}} ) ) );
  24         64  
  96         1322  
112 96         397 $def->{alias} = Module::Generic::Array->new( $def->{alias} );
113            
114 96         1316 my $opt = join( '|', @$opt_name );
115 96 100       240 if( length( $def->{default} ) )
116             {
117 48         184 $opts->{ $k2_under } = $def->{default};
118             }
119             else
120             {
121 48         193 $opts->{ $k2_under } = '';
122             }
123 96         199 my $suff = '';
124 96 100 66     687 if( $def->{type} eq 'string' )
    100 66        
    100 33        
    100          
    50          
    100          
    100          
    50          
    50          
    0          
    0          
125             {
126 24         46 $suff = '=s';
127             }
128             elsif( $def->{type} eq 'string-hash' )
129             {
130 6         17 $suff = '=s%';
131             }
132             elsif( $def->{type} eq 'array' || $def->{type} eq 'file-array' )
133             {
134 6         17 $suff = '=s@';
135 6 50       28 $opts->{ $k2_under } = [] unless( length( $def->{default} ) );
136 6 0 33     29 $def->{min} = 1 if( !exists( $def->{min} ) && !exists( $def->{max} ) );
137             }
138             elsif( $def->{type} eq 'boolean' )
139             {
140 24         49 $suff = '!';
141             }
142             elsif( $def->{type} eq 'hash' )
143             {
144 0         0 $suff = '=s%';
145 0 0       0 $opts->{ $k2_under } = {} unless( length( $def->{default} ) );
146             }
147             elsif( $def->{type} eq 'code' && ref( $def->{code} ) eq 'CODE' )
148             {
149 18         63 $opts->{ $k2_under } = $def->{code};
150             }
151             elsif( $def->{type} eq 'integer' )
152             {
153 12         23 $suff = '=i';
154             }
155             elsif( $def->{type} eq 'decimal' )
156             {
157 0         0 $suff .= '=f';
158             }
159             elsif( $def->{type} eq 'date' || $def->{type} eq 'datetime' )
160             {
161 6         16 $suff = '=s';
162             }
163             elsif( $def->{type} eq 'code' )
164             {
165 0 0       0 return( $self->error( "Type is code, but there is no property code for this option \"$k\"." ) ) if( !CORE::exists( $def->{code} ) );
166 0 0       0 return( $self->error( "Type is code, but the property code is not a code reference for this option \"$k\"." ) ) if( ref( $def->{code} ) ne 'CODE' );
167 0         0 $opts->{ $k2_under } = $def->{code};
168             }
169             elsif( $def->{type} eq 'file' )
170             {
171 0         0 $suff = '=s';
172             }
173            
174 96 100       210 if( $def->{min} )
175             {
176             # If there is no max, it would be for example s{1,}
177             # 2nd formatter is %s because it could be blank. %d would translate to 0 when blank.
178 6     6   46 no warnings 'uninitialized';
  6         14  
  6         11763  
179 6         50 $suff .= sprintf('{%d,%s}', @$def{ qw( min max ) } );
180             }
181            
182 96 50 66     260 if( $def->{re} && ref( $def->{re} ) ne 'Regexp' )
183             {
184 0         0 return( $self->error( "Regular expression provided for property \"$k\" ($def->{re}) is not a proper regular expression. I was expecting something like qr// and of type 'Regexp'." ) );
185             }
186 96         335 push( @$params, $opt . $suff );
187             }
188 6         42 $self->options( $opts );
189 6         843 $self->parameters( $params );
190 6         1386 $self->{getopt} = Getopt::Long::Parser->new;
191 6         168 return( $self );
192             }
193              
194             sub check_class_data
195             {
196 1     1 1 7 my $self = shift( @_ );
197 1   50     4 my $class = shift( @_ ) || return( $self->error( "No class was provided to return its definition" ) );
198 1 50       4 return( $self->error( "Class provided '$class' is not a string." ) ) if( ref( $class ) );
199 1         2 my $p = {};
200 1 50 33     5 $p = shift( @_ ) if( scalar( @_ ) && $self->_is_hash( $_[0] ) );
201 1   50     4 my $dict = $self->class( $class ) || return;
202 1   50     4 my $v = $self->get_class_values( $class ) || return;
203 1         5 my $errors =
204             {
205             missing => {},
206             regexp => {},
207             };
208 1         6 foreach my $f ( sort( keys( %$dict ) ) )
209             {
210 4         9 my $def = $dict->{ $f };
211 4 100       9 my $n = $def->{name} ? $def->{name} : $f;
212 4   50     19 $def->{error} ||= "does not match requirements";
213 4 0 33     10 if( !!$p->{required} && $def->{required} )
214             {
215 0 0 0     0 if( ( $def->{type} =~ /^(?:boolean|decimal|integer|string)/ && !length( $v->{ $f } ) ) ||
      0        
      0        
      0        
      0        
      0        
216 0         0 ( ( $def->{type} eq 'hash' || $def->{type} eq 'string-hash' ) && !scalar( keys( %{$v->{ $f }} ) ) ) ||
217 0         0 ( $def->{type} eq 'array' && !scalar( @{$v->{ $f }} ) ) )
218             {
219 0         0 $errors->{missing}->{ $f } = "$f ($n) is missing";
220 0         0 next;
221             }
222             }
223 4 100 33     17 if( $def->{re} )
    50          
224             {
225 1 50 33     8 if( $def->{type} eq 'string' && length( $v->{ $f } ) && $v->{ $f } !~ /$def->{re}/ )
    50 33        
226             {
227 0         0 $errors->{regexp}->{ $f } = "$f ($n) " . $def->{error};
228             }
229             elsif( $def->{type} eq 'array' )
230             {
231 1         3 my $sub_err = [];
232 1         2 foreach my $this ( @{$v->{ $f }} )
  1         3  
233             {
234 1 50       9 if( $this !~ /$def->{re}/ )
235             {
236 1         4 push( @$sub_err, $this );
237             }
238             }
239 1         14 $errors->{regexp}->{ $f } = join( ', ', @$sub_err ) . ' ' . $def->{error};
240             }
241             }
242             elsif( $def->{type} eq 'decimal' && $v->{ $f } !~ /^\d+(\.\d{1,12})?$/ )
243             {
244 0         0 $errors->{regexp}->{ $f } = "$f ($n) " . $def->{error};
245             }
246             }
247 1         6 return( $errors );
248             }
249              
250             sub class
251             {
252 4     4 1 9 my $self = shift( @_ );
253 4   50     14 my $class = shift( @_ ) || return( $self->error( "No class was provided to return its definition" ) );
254 4 50       11 return( $self->error( "Class provided '$class' is not a string." ) ) if( ref( $class ) );
255 4         13 my $classes = $self->classes;
256 4 50       556 return( $self->error( "I was expecting an hash reference for the classes dictionaries but got '$classes' instead." ) ) if( !ref( $classes ) );
257 4 50 66     31 return( $self->error( "No class \"$class\" was found." ) ) if( scalar( keys( %$classes ) ) && !exists( $classes->{ $class } ) );
258 4         15 my $dict = $self->dictionary;
259 4 50       458 return( $self->error( "No dictionary data could be found!" ) ) if( !scalar( keys( %$dict ) ) );
260 4         45 foreach my $k ( sort( keys( %$dict ) ) )
261             {
262 64         88 my $def = $dict->{ $k };
263 64 100       123 next if( !exists( $def->{class} ) );
264 20         31 my $class_names = $def->{class};
265 20         29 my $k2 = $k;
266 20         35 $k2 =~ tr/-/_/;
267 20         36 foreach my $class ( @$class_names )
268             {
269             # Create the class if it doe snot exists yet
270 32 100       68 $classes->{ $class } = {} if( !exists( $classes->{ $class } ) );
271 32         45 my $this = $classes->{ $class };
272             # Then add the property and it definition hash
273 32         51 $this->{ $k2 } = $def;
274             # If there are any alias, we add them too
275 32 50 50     73 if( $def->{alias} && scalar( @{$def->{alias}} ) )
  32         87  
276             {
277 32         42 foreach my $f ( @{$def->{alias}} )
  32         59  
278             {
279 32         49 my $f2 = $f;
280 32         42 $f2 =~ tr/-/_/;
281 32         75 $this->{ $f } = $this->{ $f2 } = $def;
282             }
283             }
284             }
285             }
286 4 50       14 return( $self->error( "No class \"$class\" was found." ) ) if( !exists( $classes->{ $class } ) );
287 4         15 return( $classes->{ $class } );
288             }
289              
290 4     4 1 22 sub classes { return( shift->_set_get_hash( 'classes', @_ ) ); }
291              
292             sub class_properties
293             {
294 2     2 1 1953 my $self = shift( @_ );
295 2         5 my $class = shift( @_ );
296 2 50       8 return( $self->error( "No class was provided to list its properties." ) ) if( !length( $class ) );
297 2         5 my $fields = [];
298 2         9 my $ref = $self->class( $class );
299 2         10 my $props = [ sort( keys( %$ref ) ) ];
300 2         14 return( Module::Generic::Array->new( $props ) );
301             }
302              
303             sub configure
304             {
305 6     6 1 16 my $self = shift( @_ );
306 6 50       28 return( $self ) if( $self->{configured} );
307 6         17 my $conf = [];
308 6 50       25 $conf = shift( @_ ) if( ref( $_[0] ) );
309 6 50       39 $conf = $self->configure_options if( !scalar( @$conf ) );
310 6   50     935 my $getopt = $self->getopt || return( $self->error( "No Getopt::Long::Parser object found." ) );
311 6 50 33     174 try
  6         13  
  6         15  
  6         33  
  0         0  
  6         17  
  6         22  
  6         20  
312 6     6   10 {
313 6         58 $getopt->configure( @$conf );
314 6         678 $self->{configured} = 1;
315             }
316 6 0 50     39 catch( $e )
  6 0 33     31  
  6 0       20  
  6 0       17  
  6 0       13  
  6 0       11  
  6 0       15  
  6 0       31  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 50       0  
  0 0       0  
  0 50       0  
  0         0  
  0         0  
  0         0  
  6         32  
  0         0  
  6         19  
  0         0  
  0         0  
  6         37  
  6         33  
  6         20  
  6         28  
  0         0  
  0         0  
  0         0  
  0         0  
317 0     0   0 {
318 0         0 return( $self->error( "An error occurred while configuration Getlong::Opt: $e" ) );
319 6 0 0 6   52 }
  6 0 0     19  
  6 0 33     8166  
  0 0 33     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0 0     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  6 0       187  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 50       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  6         30  
  0         0  
  0         0  
  0         0  
  0         0  
  6         27  
320 6         24 return( $self );
321             }
322              
323 6     6 1 26 sub configure_errors { return( shift->_set_get_array_as_object( 'configure_errors', @_ ) ); }
324              
325 6     6 1 29 sub configure_options { return( shift->_set_get_array_as_object( 'configure_options', @_ ) ); }
326              
327 28     28 1 1653 sub dictionary { return( shift->_set_get_hash( 'dictionary', @_ ) ); }
328              
329             sub exec
330             {
331 6     6 1 900 my $self = shift( @_ );
332 6 50       27 $self->configure || return;
333 6         18 my $errors = [];
334 6         14 my $missing = [];
335 6         23 my $dict = $self->dictionary;
336 6 50       741 return( $self->error( "The data returned by dictionary() is not an hash reference." ) ) if( !$self->_is_hash( $dict ) );
337 6 50       89 return( $self->error( "Somehow, the dictionary hash is empty!" ) ) if( !scalar( keys( %$dict ) ) );
338 6         24 my $opts = $self->options;
339 6 50       682 return( $self->error( "The data returned by options() is not an hash reference." ) ) if( !$self->_is_hash( $opts ) );
340 6 50       133 return( $self->error( "Somehow, the options hash is empty!" ) ) if( !scalar( keys( %$opts ) ) );
341 6         27 my $params = $self->parameters;
342 6 50       836 return( $self->error( "Data returned by parameters() is not an array reference" ) ) if( !$self->_is_array( $params ) );
343 6 50       87 return( $self->error( "Somehow, the parameters array is empty!" ) ) if( !scalar( @$params ) );
344 6   50     26 my $getopt = $self->getopt || return( $self->error( "No Getopt::Long object found." ) );
345 6         141 my $required = $self->required;
346 6 50       842 return( $self->error( "Data returned by required() is not an array reference" ) ) if( !$self->_is_array( $required ) );
347            
348 6   50     85 my $tie = tied( %$opts ) || return( $self->error( "Unable to get the tie object for the options value hash." ) );
349            
350             local $Getopt::Long::SIG{ '__DIE__' } = sub
351             {
352 0     0   0 push( @$errors, join( '', @_ ) );
353 6         49 };
354             local $Getopt::Long::SIG{ '__WARN__' } = sub
355             {
356 0     0   0 push( @$errors, join( '', @_ ) );
357 6         36 };
358 6         32 $self->configure_errors( $errors );
359            
360 6         960 $tie->enable(1);
361             $getopt->getoptions( $opts, @$params ) || do
362 6 50       39 {
363 0         0 my $usage = $self->usage;
364 0 0       0 return( $usage->() ) if( ref( $usage ) eq 'CODE' );
365 0         0 return;
366             };
367            
368 6         1077 foreach my $key ( @$required )
369             {
370 1 0 0     4 if( exists( $opts->{ $key } ) &&
      33        
371             ( !defined( $opts->{ $key } ) ||
372             !length( $opts->{ $key } ) ||
373             $opts->{ $key } =~ /^[[:blank:]]*$/ ||
374             ( ref( $opts->{ $key } ) eq 'SCALAR' &&
375             ( !length( ${$opts->{ $key }} ) || ${$opts->{ $key }} =~ /^[[:blank:]]*$/ )
376             ) ||
377             (
378             ref( $opts->{ $key } ) eq 'ARRAY' &&
379             !scalar( @{$opts->{ $key }} )
380             )
381             )
382             )
383             {
384 1         4 push( @$missing, $key );
385             }
386             }
387 6         32 $self->missing( $missing );
388            
389             # Make sure we can access each of the options dictionary definition not just from the original key, but also from any of it aliases
390 6         1150 my $aliases = $self->{aliases};
391 6         43 foreach my $k ( keys( %$dict ) )
392             {
393 96         130 my $def = $dict->{ $k };
394 96         139 $aliases->{ $k } = $def;
395 96         126 foreach my $a ( @{$def->{alias}} )
  96         171  
396             {
397 138         254 $aliases->{ $a } = $def;
398             }
399             }
400 6         36 $tie->enable(1);
401            
402 6         28 $self->postprocess;
403            
404             # return( $opts );
405             # e return a Getopt::Class::Values object, so we can call the option values hash key as method:
406             # $object->metadata / $object->metadata( $some_hash );
407             # instead of:
408             # $object->{metadata}
409             # return( $opts );
410             my $o = Getopt::Class::Values->new({
411             data => $opts,
412             dict => $dict,
413             aliases => $aliases,
414             debug => $self->{debug},
415 6   50     74 }) || return( $self->pass_error( Getopt::Class::Values->error ) );
416 6         75 return( $o );
417             }
418              
419             sub get_class_values
420             {
421 1     1 1 3 my $self = shift( @_ );
422 1   50     3 my $class = shift( @_ ) || return( $self->error( "No class was provided to return its definition" ) );
423 1 50       3 return( $self->error( "Class provided '$class' is not a string." ) ) if( ref( $class ) );
424 1   50     33 my $this_dict = $self->class( $class ) || return;
425 1         5 my $opts = $self->options;
426 1 50       117 return( $self->error( "The data returned by options() is not an hash reference." ) ) if( !$self->_is_hash( $opts ) );
427 1 50       17 return( $self->error( "Somehow, the options hash is empty!" ) ) if( !scalar( keys( %$opts ) ) );
428 1         4 my $v = {};
429 1 50 33     12 $v = shift( @_ ) if( scalar( @_ ) && $self->_is_hash( $_[0] ) );
430 1         6 foreach my $f ( sort( keys( %$this_dict ) ) )
431             {
432 4   100     22 my $ref = lc( Scalar::Util::reftype( $opts->{ $f } ) // '' );
433 4 50       19 if( $ref eq 'hash' )
    100          
    50          
434             {
435 0 0       0 $v->{ $f } = $opts->{ $f } if( scalar( keys( %{$opts->{ $f }} ) ) > 0 );
  0         0  
436             }
437             elsif( $ref eq 'array' )
438             {
439 1 50       3 $v->{ $f } = $opts->{ $f } if( scalar( @{$opts->{ $f }} ) > 0 );
  1         25  
440             }
441             elsif( !length( $ref ) )
442             {
443 3 50       10 $v->{ $f } = $opts->{ $f } if( length( $opts->{ $f } ) );
444             }
445             }
446 1         5 return( $v );
447             }
448              
449 12     12 1 68 sub getopt { return( shift->_set_get_object( 'getopt', 'Getopt::Long::Parser', @_ ) ); }
450              
451 7     7 1 36 sub missing { return( shift->_set_get_array_as_object( 'missing', @_ ) ); }
452              
453 19     19 1 64 sub options { return( shift->_set_get_hash( 'options', @_ ) ); }
454              
455 12     12 1 79 sub parameters { return( shift->_set_get_array_as_object( 'parameters', @_ ) ); }
456              
457             sub postprocess
458             {
459 6     6 0 14 my $self = shift( @_ );
460 6         23 my $dict = $self->dictionary;
461 6         720 my $opts = $self->options;
462 6         705 foreach my $k ( sort( keys( %$dict ) ) )
463             {
464 96         215 my $def = $dict->{ $k };
465 96 50 66     304 next if( !length( $opts->{ $k } ) && !$def->{default} );
466 71 50       241 return( $self->error( "Dictionary is malformed with entry $k value not being an hash reference." ) ) if( ref( $def ) ne 'HASH' );
467            
468 71 100 66     766 if( ( $def->{type} eq 'date' || $def->{type} eq 'datetime' ) && length( $opts->{ $k } ) )
    100 66        
    100 66        
    100 66        
    100          
    100          
    50          
    50          
469             {
470 2         43 my $dt = $self->_parse_timestamp( $opts->{ $k } );
471 2 50       24544 return( $self->pass_error ) if( !defined( $dt ) );
472 2 50       10 $opts->{ $k } = $dt if( $dt );
473             }
474             elsif( $def->{type} eq 'array' )
475             {
476 6         53 $opts->{ $k } = Module::Generic::Array->new( $opts->{ $k } );
477             }
478             elsif( $def->{type} eq 'hash' ||
479             $def->{type} eq 'string-hash' )
480             {
481 6         72 $opts->{ $k } = $self->_set_get_hash_as_object( $k, $opts->{ $k } );
482             }
483             elsif( $def->{type} eq 'boolean' )
484             {
485 18 100       57 $opts->{ $k } = ( $opts->{ $k } ? $self->true : $self->false );
486             }
487             elsif( $def->{type} eq 'string' )
488             {
489 14         106 $opts->{ $k } = Module::Generic::Scalar->new( $opts->{ $k } );
490             }
491             elsif( $def->{type} eq 'integer' || $def->{decimal} )
492             {
493             # Even though this is a number, this was set as a scalar reference, so we need to dereference it
494 7 100       64 if( $self->_is_scalar( $opts->{ $k } ) )
495             {
496 6         72 $opts->{ $k } = Module::Generic::Scalar->new( $opts->{ $k } );
497             }
498             else
499             {
500 1         14 $opts->{ $k } = $self->_set_get_number( $k, $opts->{ $k } );
501             }
502             }
503             elsif( $def->{type} eq 'file' )
504             {
505 0         0 $opts->{ $k } = file( $opts->{ $k } );
506             }
507             elsif( $def->{type} eq 'file-array' )
508             {
509 0         0 my $arr = Module::Generic::Array->new;
510 0         0 foreach( @{$opts->{ $k }} )
  0         0  
511             {
512 0         0 push( @$arr, file( $_ ) );
513             }
514 0         0 $opts->{ $k } = $arr;
515             }
516             }
517 6         31 return( $self );
518             }
519              
520 7     7 1 37 sub required { return( shift->_set_get_array_as_object( 'required', @_ ) ); }
521              
522 0     0 1 0 sub usage { return( shift->_set_get_code( 'usage', @_ ) ); }
523              
524             package Getopt::Class::Values;
525             BEGIN
526 0         0 {
527 6     6   53 use strict;
  6         16  
  6         126  
528 6     6   36 use warnings;
  6         86  
  6         197  
529 6     6   37 use parent qw( Module::Generic );
  6         11  
  6         47  
530 6     6   456 use Devel::Confess;
  6     0   17  
  6         57  
531             };
532              
533 6     6   523 use strict;
  6         53  
  6         110  
534 6     6   32 use warnings;
  6         13  
  6         2108  
535              
536             sub new
537             {
538 6     6   21 my $that = shift( @_ );
539 6         18 my %hash = ();
540 6         37 my $obj = tie( %hash, 'Getopt::Class::Repository' );
541 6   33     56 my $self = bless( \%hash => ( ref( $that ) || $that ) )->init( @_ );
542 6         29 $obj->enable( 1 );
543 6         25 return( $self );
544             }
545              
546             sub init
547             {
548 6     6   15 my $self = shift( @_ );
549 6   33     27 my $class = ref( $self ) || $self;
550 6         271 $self->{data} = {};
551 6         29 $self->{dict} = {};
552 6         26 $self->{aliases} = {};
553             # Can only set properties that exist
554 6         24 $self->{_init_strict} = 1;
555 6 50       52 $self->SUPER::init( @_ ) || return( $self->pass_error( $self->error ) );
556 6 50       58 return( $self->error( "No dictionary as provided." ) ) if( !$self->{dict} );
557 6 50       23 return( $self->error( "No dictionary as provided." ) ) if( !$self->{aliases} );
558 6 50       49 return( $self->error( "Dictionary provided is not an hash reference." ) ) if( !$self->_is_hash( $self->{dict} ) );
559 6 50       79 return( $self->error( "Aliases provided is not an hash reference." ) ) if( !$self->_is_hash( $self->{aliases} ) );
560 6 50       52 scalar( keys( %{$self->{dict}} ) ) || return( $self->error( "No dictionary data was provided." ) );
  6         25  
561 6 50       30 return( $self->error( "Data provided is not an hash reference." ) ) if( !$self->_is_hash( $self->{data} ) );
562 6         64 my $call_offset = 0;
563 6         66 while( my @call_data = caller( $call_offset ) )
564             {
565 12 50 66     115 unless( $call_offset > 0 && $call_data[0] ne $class && (caller($call_offset-1))[0] eq $class )
      66        
566             {
567 6         13 $call_offset++;
568 6         47 next;
569             }
570 6 50 0     41 last if( $call_data[9] || ( $call_offset > 0 && (caller($call_offset-1))[0] ne $class ) );
      33        
571 0         0 $call_offset++;
572             }
573 6         34 my $bitmask = ( caller( $call_offset ) )[9];
574 6         21 my $offset = $warnings::Offsets{uninitialized};
575 6         18 my $should_display_warning = vec( $bitmask, $offset, 1 );
576 6         26 $self->{warnings} = $should_display_warning;
577 6         18 return( $self );
578             }
579              
580             AUTOLOAD
581             {
582 10     10   28841 my( $method ) = our $AUTOLOAD =~ /([^:]+)$/;
583 6     6   50 no overloading;
  6         13  
  6         1942  
584 10         25 my $self = shift( @_ );
585 10   33     30 my $class = ref( $self ) || $self;
586             # Options dictionary
587 10         49 my $dict = $self->{dict};
588             # Values provided on command line
589 10         30 my $data = $self->{data};
590             # printf( STDERR "AUTOLOAD: \$data has %d items and property '$method' has value '%s'\n", scalar( keys( %$self ) ), $self->{ $method } );
591             # return if( !CORE::exists( $data->{ $method } ) );
592 10 50       37 return if( !CORE::exists( $self->{ $method } ) );
593 10         21 my $f = $method;
594             # Dictionary definition for this particular option field
595 10         20 my $def = $dict->{ $f };
596 10 50 33     38 if( !exists( $def->{type} ) ||
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0          
597             !defined( $def->{type} ) )
598             {
599 10 50       25 CORE::warn( "Property \"${f}\" has no defined type. Using scalar.\n" ) if( $self->{warnings} );
600 10         46 return( $self->_set_get_scalar( $f, @_ ) );
601             }
602             elsif( $def->{type} eq 'boolean' || ( $self->_is_object( $self->{ $f } ) && $self->{ $f }->isa( 'Module::Generic::Boolean' ) ) )
603             {
604 0         0 return( $self->_set_get_boolean( $f, @_ ) );
605             }
606             elsif( $def->{type} eq 'string' ||
607             Scalar::Util::reftype( $self->{ $f } ) eq 'SCALAR' )
608             {
609 0         0 return( $self->_set_get_scalar_as_object( $f, @_ ) );
610             }
611             elsif( $def->{type} eq 'integer' ||
612             $def->{type} eq 'decimal' )
613             {
614 0         0 return( $self->_set_get_number( $f, @_ ) );
615             }
616             elsif( $def->{type} eq 'date' ||
617             $def->{type} eq 'datetime' )
618             {
619 0         0 return( $self->_set_get_datetime( $f, @_ ) );
620             }
621             elsif( $def->{type} eq 'array' )
622             {
623 0         0 return( $self->_set_get_array_as_object( $f, @_ ) );
624             }
625             elsif( $def->{type} eq 'hash' ||
626             $def->{type} eq 'string-hash' )
627             {
628 0         0 return( $self->_set_get_hash_as_object( $f, @_ ) );
629             }
630             elsif( $def->{type} eq 'code' )
631             {
632 0         0 return( $self->_set_get_code( $f, @_ ) );
633             }
634             else
635             {
636 0 0       0 CORE::warn( "I do not know what to do with this property \"$f\" type \"$def->{type}\". Using scalar.\n" ) if( $self->{warnings} );
637 0         0 return( $self->_set_get_scalar( $f, @_ ) );
638             }
639             };
640              
641             package Getopt::Class::Repository;
642             BEGIN
643 0         0 {
644 6     6   40 use strict;
  6         14  
  6         117  
645 6     6   26 use warnings;
  6         9  
  6         144  
646 6     6   28 use Scalar::Util;
  6         13  
  6         164  
647 6     6   43 use Devel::Confess;
  6         12  
  6         23  
648 6     6   370 use constant VALUES_CLASS => 'Getopt::Class::Value';
  6     0   13  
  6         3401  
649             };
650              
651             # tie( %self, 'Getopt::Class::Repository' );
652             # Used by Getopt::Class::Values to ensure that whether the data are accessed as methods or as hash keys,
653             # in either way it returns the option data
654             # Actually option data are stored in the Getopt::Class::Values object data property
655             sub TIEHASH
656             {
657 6     6   19 my $self = shift( @_ );
658 6   33     33 my $class = ref( $self ) || $self;
659 6         31 return( bless( { data => {} } => $class ) );
660             }
661              
662             sub CLEAR
663             {
664 0     0   0 my $self = shift( @_ );
665 0         0 my $data = $self->{data};
666 0         0 my $caller = caller;
667 0         0 %$data = ();
668             }
669              
670             sub DELETE
671             {
672 0     0   0 my $self = shift( @_ );
673 0         0 my $data = $self->{data};
674 0         0 my $key = shift( @_ );
675 0 0 0     0 if( caller eq VALUES_CLASS || !$self->{enable} )
676             {
677 0         0 CORE::delete( $self->{ $key } );
678             }
679             else
680             {
681 0         0 CORE::delete( $data->{ $key } );
682             }
683             }
684              
685             sub EXISTS
686             {
687 135     135   7616 my $self = shift( @_ );
688 135         230 my $data = $self->{data};
689 135         201 my $key = shift( @_ );
690 135 100 66     534 if( caller eq VALUES_CLASS || !$self->{enable} )
691             {
692 18         61 CORE::exists( $self->{ $key } );
693             }
694             else
695             {
696 117         354 CORE::exists( $data->{ $key } );
697             }
698             }
699              
700             sub FETCH
701             {
702 302     302   9819 my $self = shift( @_ );
703 302         461 my $data = $self->{data};
704 302         415 my $key = shift( @_ );
705 302         473 my $caller = caller;
706             # print( STDERR "FETCH($caller)[enable=$self->{enable}] <- '$key''\n" );
707 302 100 66     934 if( caller eq VALUES_CLASS || !$self->{enable} )
708             {
709             # print( STDERR "FETCH($caller)[enable=$self->{enable}] <- '$key' <- '$self->{$key}'\n" );
710 240         754 return( $self->{ $key } )
711             }
712             else
713             {
714             # print( STDERR "FETCH($caller)[enable=$self->{enable}] <- '$key' <- '$self->{$key}'\n" );
715 62         181 return( $data->{ $key } );
716             }
717             }
718              
719             sub FIRSTKEY
720             {
721 7     7   25 my $self = shift( @_ );
722 7         20 my $data = $self->{data};
723 7         21 my @keys = ();
724 7 100 66     57 if( caller eq VALUES_CLASS || !$self->{enable} )
725             {
726 6         34 @keys = keys( %$self );
727             }
728             else
729             {
730 1         9 @keys = keys( %$data );
731             }
732 7         28 $self->{ITERATOR} = \@keys;
733 7         53 return( shift( @keys ) );
734             }
735              
736             sub NEXTKEY
737             {
738 63     63   99 my $self = shift( @_ );
739 63         93 my $data = $self->{data};
740 63 50       114 my $keys = ref( $self->{ITERATOR} ) ? $self->{ITERATOR} : [];
741 63         195 return( shift( @$keys ) );
742             }
743              
744             sub SCALAR
745             {
746 0     0   0 my $self = shift( @_ );
747 0         0 my $data = $self->{data};
748 0 0 0     0 if( caller eq VALUES_CLASS || !$self->{enable} )
749             {
750 0         0 return( scalar( keys( %$self ) ) );
751             }
752             else
753             {
754 0         0 return( scalar( keys( %$data ) ) );
755             }
756             }
757              
758             sub STORE
759             {
760 126     126   789 my $self = shift( @_ );
761 126         193 my $data = $self->{data};
762 126         223 my( $key, $val ) = @_;
763             # print( STDERR "STORE($caller)[enable=$self->{enable}] -> '$key'\n" );
764 126 50 33     414 if( caller eq VALUES_CLASS || !$self->{enable} )
765             {
766             # print( STDERR "STORE($caller)[enable=$self->{enable}] -> '$key' -> '$val'\n" );
767 126         444 $self->{ $key } = $val;
768             }
769             else
770             {
771             # print( STDERR "STORE($caller)[enable=$self->{enable}] -> '$key' -> '$val'\n" );
772 0         0 $data->{ $key } = $val;
773             }
774             }
775              
776             sub enable
777             {
778 18     18   41 my $self = shift( @_ );
779 18 50       52 if( @_ )
780             {
781 18         41 $self->{enable} = shift( @_ );
782             }
783 18         33 return( $self->{enable} );
784             }
785              
786             # This is an alternative to perl feature of refealiasing
787             # https://metacpan.org/pod/perlref#Assigning-to-References
788             package Getopt::Class::Alias;
789             BEGIN
790 0         0 {
791 6     6   42 use strict;
  6         10  
  6         114  
792 6     6   34 use warnings;
  6         15  
  6         204  
793 6     6   32 use parent -norequire, qw( Getopt::Class::Repository Module::Generic );
  6         13  
  6         33  
794 6     6   278 use Scalar::Util;
  6         12  
  6         167  
795 6     6   29 use Devel::Confess;
  6     0   9  
  6         24  
796             };
797              
798             # tie( %$opts, 'Getopt::Class::Alias', $dictionary );
799             sub TIEHASH
800             {
801             # $this is actually the HASH tied
802 6     6   19 my $this = shift( @_ );
803 6   33     43 my $class = ref( $this ) || $this;
804             # Valid options are:
805             # dict: options dictionary
806             # debug
807 6         16 my $opts = {};
808 6 50       28 $opts = shift( @_ ) if( @_ );
809             # print( STDERR __PACKAGE__ . "::TIEHASH() called with following arguments: '", join( ', ', @_ ), "'.\n" );
810 6         14 my $call_offset = 0;
811 6         71 while( my @call_data = caller( $call_offset ) )
812             {
813             # printf( STDERR "[$call_offset] In file $call_data[1] at line $call_data[2] from subroutine %s has bitmask $call_data[9]\n", (caller($call_offset+1))[3] );
814 18 50 66     149 unless( $call_offset > 0 && $call_data[0] ne $class && (caller($call_offset-1))[0] eq $class )
      66        
815             {
816             # print( STDERR "Skipping package $call_data[0]\n" );
817 18         34 $call_offset++;
818 18         109 next;
819             }
820 0 0 0     0 last if( $call_data[9] || ( $call_offset > 0 && (caller($call_offset-1))[0] ne $class ) );
      0        
821 0         0 $call_offset++;
822             }
823             # print( STDERR "Using offset $call_offset with bitmask ", ( caller( $call_offset ) )[9], "\n" );
824 6         45 my $bitmask = ( caller( $call_offset - 1 ) )[9];
825 6         23 my $offset = $warnings::Offsets{uninitialized};
826             # print( STDERR "Caller (2)'s bitmask is '$bitmask', warnings offset is '$offset' and vector is '", vec( $bitmask, $offset, 1 ), "'.\n" );
827 6   50     29 my $should_display_warning = vec( ( $bitmask // 0 ), $offset, 1 );
828            
829 6   50     26 my $dict = $opts->{dict} || return( __PACKAGE__->error( "No dictionary was provided to Getopt::Class:Alias" ) );
830 6 50       79 if( Scalar::Util::reftype( $dict ) ne 'HASH' )
    50          
831             {
832             #warn( "Dictionary provided is not an hash reference.\n" ) if( $should_display_warning );
833             #return;
834 0         0 return( __PACKAGE__->error({ message => "Dictionary provided is not an hash reference.", no_return_null_object => 1 }) );
835             }
836             elsif( !scalar( keys( %$dict ) ) )
837             {
838             #warn( "The dictionary hash reference provided is empty.\n" ) if( $should_display_warning );
839             #return;
840 0         0 return( __PACKAGE__->error( "The dictionary hash reference provided is empty." ) );
841             }
842            
843             my $aliases = $opts->{aliases} || do
844 6   33     27 {
845             #warn( "No aliases map was provided to Getopt::Class:Alias\n" ) if( $should_display_warning );
846             #return;
847             return( __PACKAGE__->error( "No aliases map was provided to Getopt::Class:Alias" ) );
848             };
849 6 50       35 if( Scalar::Util::reftype( $aliases ) ne 'HASH' )
850             {
851             #warn( "Aliases map provided is not an hash reference.\n" ) if( $should_display_warning );
852             #return;
853 0         0 return( __PACKAGE__->error( "Aliases map provided is not an hash reference." ) );
854             }
855             my $hash =
856             {
857             data => {},
858             dict => $dict,
859             aliases => $aliases,
860             warnings => $should_display_warning,
861 6   50     65 debug => ( $opts->{debug} || 0 ),
862             # _data_repo => 'data',
863             colour_open => '<',
864             colour_close => '>',
865             };
866 6         344 return( bless( $hash => $class ) );
867             }
868              
869             sub FETCH
870             {
871 369     369   2707831 my $self = shift( @_ );
872 369         544 my $data = $self->{data};
873             # my $dict = $self->{dict};
874 369         538 my $key = shift( @_ );
875             # my $def = $dict->{ $key };
876 369         1632 return( $data->{ $key } );
877             }
878              
879             sub STORE
880             {
881 174     174   197714 my $self = shift( @_ );
882 174         287 my $class = ref( $self );
883 174         310 my $data = $self->{data};
884             # Aliases contains both the original dictionary key and all its aliases
885 174         350 my $aliases = $self->{aliases};
886 174         529 my( $pack, $file, $line ) = caller;
887 174         375 my( $key, $val ) = @_;
888             # $self->message_colour( 3, "Called from line $line in file \"$file\" for property \"<green>$key</>\" with reference (<black on white>", ref( $val ), "</>) and value \"<red>$val</>\">" );
889 174         287 my $dict = $self->{dict};
890 174         251 my $enabled = $self->{enable};
891             my $fallback = sub
892             {
893 0     0   0 my( $k, $v ) = @_;
894 0         0 $data->{ $k } = $v;
895 174         638 };
896 174 100 100     584 if( $enabled && CORE::exists( $aliases->{ $key } ) )
897             {
898             my $def = $aliases->{ $key } || do
899 53   33     148 {
900             CORE::warn( "No dictionary definition found for \"$key\".\n" ) if( $self->{warnings} );
901             return( $fallback->( $key, $val ) );
902             };
903 53 50       255 if( !$self->_is_array( $def->{alias} ) )
904             {
905 0 0       0 CORE::warn( "I was expecting an array reference for this alias, but instead got '$def->{alias}'.\n" ) if( $self->{warnings} );
906 0         0 return( $fallback->( $key, $val ) );
907             }
908             my $alias = $def->{alias} || do
909 53   33     705 {
910             CORE::warn( "No alias property found. This should not happen.\n" ) if( $self->{warnings} );
911             return( $fallback->( $key, $val ) );
912             };
913 53         319 $self->messagef_colour( 3, 'Found alias "{green}' . $alias . '{/}" with %d elements: {green}"%s"{/}', scalar( @$alias ), $alias->join( "', '" ) );
914 53         7486 $self->messagef_colour( 3, "Found alias '<green>$alias</>' with %d elements: <green>'%s'</>", scalar( @$alias ), $alias->join( "', '" ) );
915 53 50       4908 if( Scalar::Util::reftype( $alias ) ne 'ARRAY' )
916             {
917 0 0       0 CORE::warn( "Alias property is not an array reference. This should not happen.\n" ) if( $self->{warnings} );
918 0         0 return( $fallback->( $key, $val ) );
919             }
920 53         1565 $self->message_colour( 3, "Setting primary property \"<green>${key}</>\" to value \"<black on white>${val}</>\"." );
921 53         6880 $data->{ $key } = $val;
922 53         154 foreach my $a ( @$alias )
923             {
924 83 100       535 next if( $a eq $key );
925             # We do not set the value, if for some reason, the user would have removed this key
926 30         115 $self->message_colour( 3, "Setting alias \"<green>${a}</>\" to value \"<val black on white>${val}</>\" (ref=", ref( $val ), ")." );
927             # $data->{ $a } = $val if( CORE::exists( $data->{ $a } ) );
928 30         1598 $data->{ $a } = $val;
929             }
930             }
931             else
932             {
933 121         548 $data->{ $key } = $val;
934             }
935             }
936              
937             1;
938              
939             __END__
940              
941             =encoding utf-8
942              
943             =head1 NAME
944              
945             Getopt::Class - Extended dictionary version of Getopt::Long
946              
947             =head1 SYNOPSIS
948              
949             use Getopt::Class;
950             our $DEBUG = 0;
951             our $VERBOSE = 0;
952             our $VERSION = '0.1';
953             my $dict =
954             {
955             create_user => { type => 'boolean', alias => [qw(create_person create_customer)], action => 1 },
956             create_product => { type => 'boolean', action => 1 },
957             debug => { type => 'integer', default => \$DEBUG },
958             help => { type => 'code', code => sub{ pod2usage(1); }, alias => '?', action => 1 },
959             man => { type => 'code', code => sub{ pod2usage( -exitstatus => 0, -verbose => 2 ); }, action => 1 },
960             quiet => { type => 'boolean', default => 0, alias => 'silent' },
961             verbose => { type => 'boolean', default => \$VERBOSE, alias => 'v' },
962             version => { type => 'code', code => sub{ printf( "v%.2f\n", $VERSION ); }, action => 1 },
963            
964             api_server => { type => 'string', default => 'api.example.com' },
965             api_version => { type => 'string', default => 1 },
966             as_admin => { type => 'boolean' },
967             dry_run => { type => 'boolean', default => 0 },
968            
969             name => { type => 'string', class => [qw( person product )] },
970             created => { type => 'datetime', class => [qw( person product )] },
971             define => { type => 'string-hash', default => {} },
972             langs => { type => 'array', class => [qw( person product )], re => qr/^[a-z]{2}([_|-][A-Z]{2})?/, min => 1, default => [qw(en)] },
973             currency => { type => 'string', class => [qw(product)], name => 'currency', re => qr/^[a-z]{3}$/, error => "must be a three-letter iso 4217 value" },
974             age => { type => 'integer', class => [qw(person)], name => 'age', },
975             path => { type => 'file' },
976             skip => { type => 'file-array' },
977             };
978            
979             # Assuming command line arguments like:
980             prog.pl --create-user --name Bob --langs fr ja --age 30 --created now --debug 3 \
981             --path ./here/some/where --skip ./bad/directory ./not/here ./avoid/me/
982              
983             my $opt = Getopt::Class->new({
984             dictionary => $dict,
985             }) || die( Getopt::Class->error, "\n" );
986             my $opts = $opt->exec || die( $opt->error, "\n" );
987             $opt->required( [qw( name langs )] );
988             my $err = $opt->check_class_data( 'person' );
989             printf( "User is %s and is %d years old\n", $opts{qw( name age )} ) if( $opts->{debug} );
990              
991             # Get all the properties for class person
992             my $props = $opt->class_properties( 'person' );
993              
994             # Get values collected for class 'person'
995             if( $opts->{create_user} )
996             {
997             my $values = $opt->get_class_values( 'person' );
998             # Having collected the values for our class of properties, and making sure all
999             # required are here, we can add them to database or make api calls, etc
1000             }
1001             elsif( $opts->{create_product} )
1002             {
1003             # etc...
1004             }
1005            
1006             # Or you can also access those values as object methods
1007             if( $opts->create_product )
1008             {
1009             $opts->langs->push( 'en_GB' ) if( !$opts->langs->length );
1010             printf( "Created on %s\n", $opts->created->iso8601 );
1011             }
1012              
1013             =head1 VERSION
1014              
1015             v0.103.3
1016              
1017             =head1 DESCRIPTION
1018              
1019             L<Getopt::Class> is a lightweight wrapper around L<Getopt::Long> that implements the idea of class of properties and makes it easier and powerful to set up L<Getopt::Long>. This module is particularly useful if you want to provide several sets of options for different features or functions of your program. For example, you may have a part of your program that deals with user while another deals with product. Each of them needs their own properties to be provided.
1020              
1021             =head1 CONSTRUCTOR
1022              
1023             =head2 new
1024              
1025             To instantiate a new L<Getopt::Class> object, pass an hash reference of following parameters:
1026              
1027             =over 4
1028              
1029             =item I<dictionary>
1030              
1031             This is required. It must contain a key value pair where the value is an anonymous hash reference that can contain the following parameters:
1032              
1033             =over 8
1034              
1035             =item I<alias>
1036              
1037             This is an array reference of alternative options that can be used in an interchangeable way
1038              
1039             my $dict =
1040             {
1041             last_name => { type => 'string', alias => [qw( family_name surname )] },
1042             };
1043             # would make it possible to use either of the following combinations
1044             --last-name Doe
1045             # or
1046             --surname Doe
1047             # or
1048             --family-name Doe
1049              
1050             =item I<default>
1051              
1052             This contains the default value. For a string, this could be anything, and also a reference to a scalar, such as:
1053              
1054             our $DEBUG = 0;
1055             my $dict =
1056             {
1057             debug => { type => 'integer', default => \$DEBUG },
1058             };
1059              
1060             It can also be used to provide default value for an array, such as:
1061              
1062             my $dict =
1063             {
1064             langs => { type => 'array', class => [qw( person product )], re => qr/^[a-z]{2}([_|-][A-Z]{2})?/, min => 1, default => [qw(en)] },
1065             };
1066              
1067             But beware that if you provide a value, it will not superseed the existing default value, but add it on top of it, so
1068              
1069             --langs en fr ja
1070              
1071             would not produce an array with C<en>, C<fr> and C<ja> entries, but an array such as:
1072              
1073             ['en', 'en', 'fr', 'ja' ]
1074              
1075             because the initial default value is not replaced when one is provided. This is a design from L<Getopt::Long> and although I could circumvent this, I a not sure I should.
1076              
1077             =item I<error>
1078              
1079             A string to be used to set an error by L</"check_class_data">. Typically the string should provide meaningful information as to what the data should normally be. For example:
1080              
1081             my $dict =
1082             {
1083             currency => { type => 'string', class => [qw(product)], name => 'currency', re => qr/^[a-z]{3}$/, error => "must be a three-letter iso 4217 value" },
1084             };
1085              
1086             =item I<file>
1087              
1088             This type will mark the value as a directory or file path and will become a L<Module::Generic::File> object.
1089              
1090             This is particularly convenient when the user provided you with a relative path, such as:
1091              
1092             ./my_prog.pl --debug 3 --path ./here/
1093              
1094             And if you are not very careful and inadvertently change directory like when using L<File::Find>, then this relative path could lead to some unpleasant surprise.
1095              
1096             Setting this argument type to C<file> ensure the resulting value is a L<Module::Generic::File>, whose underlying file or directory will be resolved to their absolute path.
1097              
1098             =item I<file-array>
1099              
1100             Same as I<file> argument type, but allows multiple value saved as an array. For example:
1101              
1102             ./my_prog.pl --skip ./not/here ./avoid/me/ ./skip/this/directory
1103              
1104             This would result in the option property I<skip> being an L<array object|Module::Generic::Array> containing 3 entries.
1105              
1106             =item I<max>
1107              
1108             This is well explained in L<Getopt::Long/"Options with multiple values">
1109              
1110             It serves "to specify the minimal and maximal number of arguments an option takes".
1111              
1112             =item I<min>
1113              
1114             Same as above
1115              
1116             =item I<re>
1117              
1118             This must be a regular expression and is used by L</"check_class_data"> to check the sanity of the data provided by the user.
1119             So, for example:
1120              
1121             my $dict =
1122             {
1123             currency => { type => 'string', class => [qw(product)], name => 'currency', re => qr/^[a-z]{3}$/, error => "must be a three-letter iso 4217 value" },
1124             };
1125              
1126             then the user calls your program with, among other options:
1127              
1128             --currency euro
1129              
1130             would set an error that can be retrieved as an output of L</"check_class_data">
1131              
1132             =item I<required>
1133              
1134             Set this to true or false (1 or 0) to instruct L</"check_class_data"> whether to check if it is missing or not.
1135              
1136             This is an alternative to the L</"required"> method which is used at an earlier stage, during L</"exec">
1137              
1138             =item I<type>
1139              
1140             Type can be I<array>, I<boolean>, I<code>, I<decimal>, I<hash>, I<integer>, I<string>, I<string-hash>
1141              
1142             Type I<hash> is convenient for free key-value pair such as:
1143              
1144             --define customer_id=10 --define transaction_id 123
1145              
1146             would result for C<define> with an anonymous hash as value containing C<customer_id> with value C<10> and C<transaction_id> with value C<123>
1147              
1148             Type code implies an anonymous sub routine and should be accompanied with the attribute I<code>, such as:
1149              
1150             { type => 'code', code => sub{ pod2usage(1); exit( 0 ) }, alias => '?', action => 1 },
1151              
1152             Also as seen in the example above, you can add additional properties to be used in your program, here such as I<action> that could be used to identify all options that are used to trigger an action or a call to a sub routine.
1153              
1154             =back
1155              
1156             =item I<debug>
1157              
1158             This takes an integer, and is used to set the level of debugging. Anything under 3 will not provide anything meaningful.
1159              
1160             =back
1161              
1162             =head1 METHODS
1163              
1164             =head2 check_class_data
1165              
1166             Provided with a string corresponding to a class name, this will check the data provided by the user.
1167              
1168             Currently this means it checks if the data is present when the attribute I<required> is set, and it checks the data against a regular expression if one is provided with the attribute I<re>
1169              
1170             It returns an hash reference with 2 keys: I<missing> and I<regexp>. Each with an anonymous hash reference with key matching the option name and the value the error string. So:
1171              
1172             my $dict =
1173             {
1174             name => { type => 'string', class => [qw( person product )], required => 1 },
1175             langs => { type => 'array', class => [qw( person product )], re => qr/^[a-z]{2}([_|-][A-Z]{2})?/, min => 1, default => [qw(en)] },
1176             };
1177              
1178             Assuming your user calls your program without C<--name> and with C<--langs FR EN> this would have L</"check_class_data"> return the following data structure:
1179              
1180             $errors =
1181             {
1182             missing => { name => "name (name) is missing" },
1183             regexp => { langs => "langs (langs) does not match requirements" },
1184             };
1185              
1186             =head2 class
1187              
1188             Provided with a string representing a property class, and this returns an hash reference of all the dictionary entries matching this class
1189              
1190             =head2 classes
1191              
1192             This returns an hash reference containing class names, each of which has an anonymous hash reference with corresponding dictionary entries
1193              
1194             =head2 class_properties
1195              
1196             Provided with a string representing a class name, this returns an array reference of options, a.k.a. class properties.
1197              
1198             The array reference is a L<Module::Generic::Array> object.
1199              
1200             =head2 configure
1201              
1202             This calls L<Getopt::Long/"configure"> with the L</"configure_options">.
1203              
1204             It can be overriden by calling L</"configure"> with an array reference.
1205              
1206             If there is an error, it will return undef and set an L</"error"> accordingly.
1207              
1208             Otherwise, it returns the L<Getopt::Class> object, so it can be chained.
1209              
1210             =head2 configure_errors
1211              
1212             This returns an array reference of the errors generated by L<Getopt::Long> upon calling L<Getopt::Long/"getoptions"> by L</"exec">
1213              
1214             The array is an L<Module::Generic::Array> object
1215              
1216             =head2 configure_options
1217              
1218             This returns an array reference of the L<Getopt::Long> configuration options upon calling L<Getopt::Long/"configure"> by method L</"configure">
1219              
1220             The array is an L<Module::Generic::Array> object
1221              
1222             =head2 dictionary
1223              
1224             This returns the hash reference representing the dictionary set when the object was instantiated. See L</"new"> method.
1225              
1226             =head2 error
1227              
1228             Return the last error set as a L<Module::Generic::Exception> object. Because the object can be stringified, you can do directly:
1229              
1230             die( $opt->error, "\n" ); # with a stack trace
1231              
1232             or
1233              
1234             die( sprintf( "Error occurred at line %d in file %s with message %s\n", $opt->error->line, $opt->error->file, $opt->error->message ) );
1235              
1236             =head2 exec
1237              
1238             This calls L<Getopt::Long/"getoptions"> with the L</"options"> hash reference and the L</"parameters"> array reference and after having called L</"configure"> to configure L<Getopt::Long> with the proper parameters according to the dictionary provided at the time of object instantiation.
1239              
1240             If there are any L<Getopt::Long> error, they can be retrieved with method L</"configure_errors">
1241              
1242             my $opt = Getopt::Class->new({ dictionary => $dict }) || die( Getopt::Class->error );
1243             my $opts = $opt->exec || die( $opt->error );
1244             if( $opt->configure_errors->length > 0 )
1245             {
1246             # do something about it
1247             }
1248              
1249             If any required options have been specified with the method L</"required">, it will check any missing option then and set an array of those missing options that can be retrieved with method L</"missing">
1250              
1251             This method makes sure that any option can be accessed with underscore or dash whichever, so a dictionary entry such as:
1252              
1253             my $dict =
1254             {
1255             create_customer => { type => 'boolean', alias => [qw(create_client create_user)], action => 1 },
1256             };
1257              
1258             can be called by your user like:
1259              
1260             ---create-customer
1261             # or
1262             --create-client
1263             # or
1264             --create-user
1265              
1266             because a duplicate entry with the underscore replaced by a dash is created (actually it's an alias of one to another). So you can say in your program:
1267              
1268             my $opts = $opt->exec || die( $opt->error );
1269             if( $opts->{create_user} )
1270             {
1271             # do something
1272             }
1273              
1274             L</"exec"> returns an hash reference whose properties can be accessed directly, but those properties can also be accessed as methods.
1275              
1276             This is made possible because the hash reference returned is a blessed object from L<Getopt::Class::Values> and provides an object oriented access to all the option values.
1277              
1278             A string is an object from L<Module::Generic::Scalar>
1279              
1280             $opts->customer_name->index( 'Doe' ) != -1
1281              
1282             A boolean is an object from L<Module::Generic::Boolean>
1283              
1284             An integer or decimal is an object from L<Text::Number>
1285              
1286             A date/dateime value is an object from L<DateTime>
1287              
1288             $opts->created->iso8601 # 2020-05-01T17:10:20
1289              
1290             An hash reference is an object created with L<Module::Generic/"_set_get_hash_as_object">
1291              
1292             $opts->metadata->transaction_id
1293              
1294             An array reference is an object created with L<Module::Generic/"_set_get_array_as_object">
1295              
1296             $opts->langs->push( 'en_GB' ) if( !$opts->langs->exists( 'en_GB' ) );
1297             $opts->langs->forEach(sub{
1298             $self->active_user_lang( shift( @_ ) );
1299             });
1300              
1301             Whatever the object type of the option value is based on the dictionary definitions you provide to L</"new">
1302              
1303             =head2 get_class_values
1304              
1305             Provided with a string representing a property class, and this returns an hash reference of all the key-value pairs provided by your user. So:
1306              
1307             my $dict =
1308             {
1309             create_customer => { type => 'boolean', alias => [qw(create_client create_user)], action => 1 },
1310             name => { type => 'string', class => [qw( person product )] },
1311             created => { type => 'datetime', class => [qw( person product )] },
1312             define => { type => 'string-hash', default => {} },
1313             langs => { type => 'array', class => [qw( person product )], re => qr/^[a-z]{2}([_|-][A-Z]{2})?/, min => 1, default => [] },
1314             currency => { type => 'string', class => [qw(product)], name => 'currency', re => qr/^[a-z]{3}$/, error => "must be a three-letter iso 4217 value" },
1315             age => { type => 'integer', class => [qw(person)], name => 'age', },
1316             };
1317              
1318             Then the user calls your program with:
1319              
1320             --create-user --name Bob --age 30 --langs en ja --created now
1321              
1322             # In your app
1323             my $opt = Getopt::Class->new({ dictionary => $dict }) || die( Getopt::Class->error );
1324             my $opts = $opt->exec || die( $opt->error );
1325             # $vals being an hash reference as a subset of all the values returned in $opts above
1326             my $vals = $opt->get_class_values( 'person' )
1327             # returns an hash only with keys name, age, langs and created
1328              
1329             =head2 getopt
1330              
1331             Sets or get the L<Getopt::Long::Parser> object. You can provide yours if you want but beware that certain options are necessary for L<Getopt::Class> to work. You can check those options with the method L</"configure_options">
1332              
1333             =head2 missing
1334              
1335             Returns an array of missing options. The array reference returned is a L<Module::Generic::Array> object, so you can do thins like
1336              
1337             if( $opt->missing->length > 0 )
1338             {
1339             # do something
1340             }
1341              
1342             =head2 options
1343              
1344             Returns an hash reference of options created by L</"new"> based on the dictionary you provide. This hash reference is used by L</"exec"> to call L<Getopt::Long/"getoptions">
1345              
1346             =head2 parameters
1347              
1348             Returns an array reference of parameters created by L</"new"> based on the dictionary you provide. This hash reference is used by L</"exec"> to call L<Getopt::Long/"getoptions">
1349              
1350             This array reference is a L<Module::Generic::Array> object
1351              
1352             =head2 required
1353              
1354             Set or get the array reference of required options. This returns a L<Module::Generic::Array> object.
1355              
1356             =head2 usage
1357              
1358             Set or get the anonymous subroutine or sub routine reference used to show the user the proper usage of your program.
1359              
1360             This is called by L</"exec"> after calling L<Getopt::Long/"getoptions"> if there is an error, i.e. if L<Getopt::Long/"getoptions"> does not return a true value.
1361              
1362             If you use object to call the sub routine usage, I recommend using the module L<curry>
1363              
1364             If this is not set, L</"exec"> will simply return undef or an empty list depending on the calling context.
1365              
1366             =head1 ERROR HANDLING
1367              
1368             This module never dies, or at least not by design. If an error occurs, each method returns undef and sets an error that can be retrieved with the method L</"error">
1369              
1370             =head1 AUTHOR
1371              
1372             Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>
1373              
1374             =head1 SEE ALSO
1375              
1376             L<Getopt::Long>
1377              
1378             =head1 COPYRIGHT & LICENSE
1379              
1380             Copyright (c) 2019-2020 DEGUEST Pte. Ltd.
1381              
1382             You can use, copy, modify and redistribute this package and associated
1383             files under the same terms as Perl itself.
1384              
1385             =cut