File Coverage

lib/Getopt/Class.pm
Criterion Covered Total %
statement 505 666 75.8
branch 166 418 39.7
condition 110 301 36.5
subroutine 70 85 82.3
pod 17 18 94.4
total 868 1488 58.3


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