File Coverage

lib/Class/Usul/TraitFor/UntaintedGetopts.pm
Criterion Covered Total %
statement 35 36 97.2
branch 1 4 25.0
condition n/a
subroutine 14 15 93.3
pod 6 6 100.0
total 56 61 91.8


line stmt bran cond sub pod time code
1             package Class::Usul::TraitFor::UntaintedGetopts;
2              
3 19     19   13419 use namespace::autoclean;
  19         57  
  19         179  
4              
5 19     19   1800 use Class::Usul::Constants qw( FAILED NUL QUOTED_RE TRUE );
  19         43  
  19         182  
6 19     19   17726 use Class::Usul::Functions qw( emit_err untaint_cmdline );
  19         57  
  19         135  
7 19     19   32961 use Class::Usul::Getopt qw( describe_options );
  19         80  
  19         260  
8 19     19   18843 use Data::Record;
  19         26056  
  19         530  
9 19     19   8530 use Encode qw( decode );
  19         134965  
  19         1247  
10 19     19   6002 use JSON::MaybeXS qw( decode_json );
  19         46830  
  19         973  
11 19     19   128 use Scalar::Util qw( blessed );
  19         85  
  19         799  
12 19     19   103 use Moo::Role;
  19         41  
  19         200  
13              
14             my $Extra_Argv = []; my $Untainted_Argv = [];
15              
16             my $Usage = "Did we forget new_with_options?\n";
17              
18             # Private functions
19             my $_extra_argv = sub {
20             return $_[ 0 ]->{_extra_argv} //= [ @{ $Extra_Argv } ];
21             };
22              
23             my $_extract_params = sub {
24             my ($args, $config, $options_data, $cmdline_opt) = @_;
25              
26             my $params = { %{ $args } }; my @missing_required;
27              
28             my $prefer = $config->{prefer_commandline};
29              
30             for my $name (keys %{ $options_data }) {
31             my $option = $options_data->{ $name };
32              
33             if ($prefer or not defined $params->{ $name }) {
34             my $val; defined ($val = $cmdline_opt->$name()) and
35             $params->{ $name } = $option->{json} ? decode_json( $val ) : $val;
36             }
37              
38             $option->{required} and not defined $params->{ $name }
39             and push @missing_required, $name;
40             }
41              
42             return ($params, @missing_required);
43             };
44              
45             my $_option_specification = sub {
46             my ($name, $opt) = @_;
47              
48             my $dash_name = $name; $dash_name =~ tr/_/-/; # Dash name support
49             my $option_spec = $dash_name;
50              
51             defined $opt->{short } and $option_spec .= '|'.$opt->{short};
52             $opt->{repeatable} and not defined $opt->{format} and $option_spec .= '+';
53             $opt->{negateable} and $option_spec .= '!';
54             defined $opt->{format} and $option_spec .= '='.$opt->{format};
55             return $option_spec;
56             };
57              
58             my $_set_usage_conf = sub { # Should be in describe_options third argument
59             return Class::Usul::Getopt::Usage->usage_conf( $_[ 0 ] );
60             };
61              
62             my $_split_args = sub {
63             my $splitters = shift; my @new_argv;
64              
65             for (my $i = 0, my $nargvs = @ARGV; $i < $nargvs; $i++) { # Parse all argv
66             my $arg = $ARGV[ $i ];
67              
68             my ($name, $value) = split m{ [=] }mx, $arg, 2; $name =~ s{ \A --? }{}mx;
69              
70             if (my $splitter = $splitters->{ $name }) {
71             $value //= $ARGV[ ++$i ];
72              
73             for my $subval (map { s{ \A [\'\"] | [\'\"] \z }{}gmx; $_ }
74             $splitter->records( $value )) {
75             push @new_argv, "--${name}", $subval;
76             }
77             }
78             else { push @new_argv, $arg }
79             }
80              
81             return @new_argv;
82             };
83              
84             my $_sort_options = sub {
85             my ($opts, $a, $b) = @_; my $max = 999;
86              
87             my $oa = $opts->{ $a }{order} || $max; my $ob = $opts->{ $b }{order} || $max;
88              
89             return ($oa == $max) && ($ob == $max) ? $a cmp $b : $oa <=> $ob;
90             };
91              
92             my $_untainted_argv = sub {
93             return $_[ 0 ]->{_untainted_argv} //= [ @{ $Untainted_Argv } ];
94             };
95              
96             my $_build_options = sub {
97             my $options_data = shift; my $splitters = {}; my @options = ();
98              
99             for my $name (sort { $_sort_options->( $options_data, $a, $b ) }
100             keys %{ $options_data }) {
101             my $option = $options_data->{ $name };
102             my $cfg = $option->{config} // {};
103             my $doc = $option->{doc } // "No help for ${name}";
104              
105             push @options, [ $_option_specification->( $name, $option ), $doc, $cfg ];
106             defined $option->{autosplit} or next;
107             $splitters->{ $name } = Data::Record->new( {
108             split => $option->{autosplit}, unless => QUOTED_RE } );
109             $option->{short}
110             and $splitters->{ $option->{short} } = $splitters->{ $name };
111             }
112              
113             return ($splitters, @options);
114             };
115              
116             # Private methods
117             my $_parse_options = sub {
118             my ($self, %args) = @_; my $opt;
119              
120             my $class = blessed $self || $self;
121             my %data = $class->_options_data;
122             my %config = $class->_options_config;
123             my $enc = $config{encoding} // 'UTF-8';
124              
125             my @skip_options; defined $config{skip_options}
126             and @skip_options = @{ $config{skip_options} };
127              
128             @skip_options and delete @data{ @skip_options };
129              
130             my ($splitters, @options) = $_build_options->( \%data );
131              
132             my %gld_conf; my @gld_attr = ('getopt_conf', 'show_defaults');
133              
134             my $usage_opt = $config{usage_opt} ? $config{usage_opt} : 'Usage: %c %o';
135              
136             @gld_conf{ @gld_attr } = @config{ @gld_attr };
137             $config{usage_conf } and $_set_usage_conf->( $config{usage_conf} );
138             $config{protect_argv } and local @ARGV = @ARGV;
139             $enc and @ARGV = map { decode( $enc, $_ ) } @ARGV;
140             $config{no_untaint } or @ARGV = map { untaint_cmdline $_ } @ARGV;
141             $Untainted_Argv = [ @ARGV ];
142             keys %{ $splitters } and @ARGV = $_split_args->( $splitters );
143             ($opt, $Usage) = describe_options( $usage_opt, @options, \%gld_conf );
144             $Extra_Argv = [ @ARGV ];
145              
146             my ($params, @missing)
147             = $_extract_params->( \%args, \%config, \%data, $opt );
148              
149             if ($config{missing_fatal} and @missing) {
150             emit_err join( "\n", map { "Option '${_}' is missing" } @missing );
151             emit_err $Usage;
152             exit FAILED;
153             }
154              
155             return %{ $params };
156             };
157              
158             # Construction
159             sub new_with_options {
160 4     4 1 1890 my $self = shift; return $self->new( $self->$_parse_options( @_ ) );
  4         25  
161             }
162              
163             # Public methods
164             sub extra_argv {
165 3 50   3 1 15 return defined $_[ 1 ] ? $_extra_argv->( $_[ 0 ] )->[ $_[ 1 ] ]
166             : $_extra_argv->( $_[ 0 ] );
167             }
168              
169             sub next_argv {
170 1     1 1 3 return shift @{ $_extra_argv->( $_[ 0 ] ) };
  1         5  
171             }
172              
173             sub options_usage {
174 3     3 1 7232 return ucfirst $Usage;
175             }
176              
177             sub unshift_argv {
178 1     1 1 4 return unshift @{ $_extra_argv->( $_[ 0 ] ) }, $_[ 1 ];
  1         5  
179             }
180              
181             sub untainted_argv {
182 0 0   0 1   return defined $_[ 1 ] ? $_untainted_argv->( $_[ 0 ] )->[ $_[ 1 ] ]
183             : $_untainted_argv->( $_[ 0 ] );
184             }
185              
186             1;
187              
188             __END__
189              
190             =pod
191              
192             =head1 Name
193              
194             Class::Usul::TraitFor::UntaintedGetopts - Untaints @ARGV before Getopts processes it
195              
196             =head1 Synopsis
197              
198             use Moo;
199              
200             with 'Class::Usul::TraitFor::UntaintedGetopts';
201              
202             =head1 Description
203              
204             Untaints C<@ARGV> before Getopts processes it. Replaces L<MooX::Options>
205             with an implementation closer to L<MooseX::Getopt::Dashes>
206              
207             =head1 Configuration and Environment
208              
209             Modifies C<new_with_options> and C<options_usage>
210              
211             =head1 Subroutines/Methods
212              
213             =head2 extra_argv
214              
215             Returns an array ref containing the remaining command line arguments
216              
217             =head2 new_with_options
218              
219             Parses the command line options and then calls the constructor
220              
221             =head2 next_argv
222              
223             Returns the next value from L</extra_argv> shifting the value off the list
224              
225             =head2 options_usage
226              
227             Returns the options usage string
228              
229             =head2 _parse_options
230              
231             Untaints the values of the C<@ARGV> array before the are parsed by
232             L<Getopt::Long::Descriptive>
233              
234             =head2 unshift_argv
235              
236             Pushes the supplied argument back onto the C<extra_argv> list
237              
238             =head2 untainted_argv
239              
240             Returns all of the arguments passed, untainted, before L<Getopt::Long> parses
241             them
242              
243             =head1 Diagnostics
244              
245             None
246              
247             =head1 Dependencies
248              
249             =over 3
250              
251             =item L<Data::Record>
252              
253             =item L<Encode>
254              
255             =item L<Getopt::Long>
256              
257             =item L<Getopt::Long::Descriptive>
258              
259             =item L<JSON::MaybeXS>
260              
261             =item L<Moo::Role>
262              
263             =back
264              
265             =head1 Incompatibilities
266              
267             There are no known incompatibilities in this module
268              
269             =head1 Bugs and Limitations
270              
271             There are no known bugs in this module.
272             Please report problems to the address below.
273             Patches are welcome
274              
275             =head1 Acknowledgements
276              
277             Larry Wall - For the Perl programming language
278              
279             =head1 Author
280              
281             Peter Flanigan, C<< <pjfl@cpan.org> >>
282              
283             =head1 License and Copyright
284              
285             Copyright (c) 2017 Peter Flanigan. All rights reserved
286              
287             This program is free software; you can redistribute it and/or modify it
288             under the same terms as Perl itself. See L<perlartistic>
289              
290             This program is distributed in the hope that it will be useful,
291             but WITHOUT WARRANTY; without even the implied warranty of
292             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE
293              
294             =cut
295              
296             # Local Variables:
297             # mode: perl
298             # tab-width: 3
299             # End: