File Coverage

lib/Getopt/Class.pm
Criterion Covered Total %
statement 504 664 75.9
branch 166 418 39.7
condition 109 299 36.4
subroutine 69 83 83.1
pod 17 18 94.4
total 865 1482 58.3


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