File Coverage

blib/lib/MooseX/Runnable/Util/ArgParser.pm
Criterion Covered Total %
statement 111 118 94.0
branch 32 44 72.7
condition 3 3 100.0
subroutine 15 15 100.0
pod 0 1 0.0
total 161 181 88.9


line stmt bran cond sub pod time code
1             package MooseX::Runnable::Util::ArgParser;
2             # ABSTRACT: parse @ARGV for C<mx-run>
3              
4             our $VERSION = '0.10';
5              
6 2     2   48710 use Moose;
  2         588861  
  2         10  
7 2     2   10709 use MooseX::Types::Moose qw(HashRef ArrayRef Str Bool);
  2         83961  
  2         15  
8 2     2   7776 use MooseX::Types::Path::Tiny qw(Path);
  2         197581  
  2         10  
9 2     2   3321 use Path::Tiny; # exports path()
  2         2  
  2         112  
10 2     2   1111 use List::SomeUtils qw(first_index);
  2         8801  
  2         134  
11 2     2   734 use FindBin;
  2         1506  
  2         80  
12              
13 2     2   9 use namespace::autoclean -also => ['_look_for_dash_something', '_delete_first'];
  2         3  
  2         54  
14              
15             has 'argv' => (
16             is => 'ro',
17             isa => ArrayRef,
18             required => 1,
19             auto_deref => 1,
20             );
21              
22             has 'class_name' => (
23             is => 'ro',
24             isa => Str,
25             lazy => 1,
26             builder => '_build_class_name',
27             );
28              
29             has 'modules' => (
30             is => 'ro',
31             isa => ArrayRef[Str],
32             lazy => 1,
33             builder => '_build_modules',
34             auto_deref => 1,
35             );
36              
37             has 'include_paths' => (
38             is => 'ro',
39             isa => ArrayRef[Path],
40             lazy => 1,
41             builder => '_build_include_paths',
42             auto_deref => 1,
43             );
44              
45             has 'plugins' => (
46             is => 'ro',
47             isa => HashRef[ArrayRef[Str]],
48             lazy => 1,
49             builder => '_build_plugins',
50             );
51              
52             has 'app_args' => (
53             is => 'ro',
54             isa => ArrayRef[Str],
55             lazy => 1,
56             builder => '_build_app_args',
57             auto_deref => 1,
58             );
59              
60             has 'is_help' => (
61             is => 'ro',
62             isa => Bool,
63             lazy => 1,
64             builder => '_build_is_help',
65             );
66              
67              
68             sub _build_class_name {
69 34     34   28 my $self = shift;
70 34         855 my @args = $self->argv;
71              
72 34         36 my $next_is_it = 0;
73 34         32 my $need_dash_dash = 0;
74              
75             ARG:
76 34         41 for my $arg (@args) {
77 127 100       159 if($next_is_it){
78 20         547 return $arg;
79             }
80              
81 107 100       156 if($arg eq '--'){
82 20         13 $next_is_it = 1;
83 20         19 next ARG;
84             }
85              
86 87 100       158 next ARG if $arg =~ /^-[A-Za-z]/;
87              
88 55 100       83 if($arg =~ /^[+]/){
89 20         19 $need_dash_dash = 1;
90 20         20 next ARG;
91             }
92              
93 35 100       397 return $arg unless $need_dash_dash;
94             }
95              
96 0 0       0 if($next_is_it){
97 0         0 confess 'Parse error: expecting ClassName, got EOF';
98             }
99 0 0       0 if($need_dash_dash){
100 0         0 confess 'Parse error: expecting --, got EOF';
101             }
102              
103 0         0 confess "Parse error: looking for ClassName, but can't find it; perhaps you meant '--help' ?";
104             }
105              
106             sub _look_for_dash_something($@) {
107             my ($something, @args) = @_;
108             my @result;
109              
110             my $rx = qr/^-$something(.*)$/;
111             ARG:
112             for my $arg (@args) {
113             last ARG if $arg eq '--';
114             last ARG unless $arg =~ /^-/;
115             if($arg =~ /$rx/){
116             push @result, $1;
117             }
118             }
119              
120             return @result;
121             }
122              
123             sub _build_modules {
124 25     25   22 my $self = shift;
125 25         586 my @args = $self->argv;
126 25         44 return [ _look_for_dash_something 'M', @args ];
127             }
128              
129             sub _build_include_paths {
130 25     25   19 my $self = shift;
131 25         616 my @args = $self->argv;
132 25         50 return [ map { path($_) } _look_for_dash_something 'I', @args ];
  17         101  
133             }
134              
135             sub _build_is_help {
136 27     27   24 my $self = shift;
137 27         638 my @args = $self->argv;
138             return
139 27   100     44 (_look_for_dash_something 'h', @args) ||
140             (_look_for_dash_something '\\?', @args) ||
141             (_look_for_dash_something '-help', @args) ;;
142             }
143              
144             sub _build_plugins {
145 21     21   22 my $self = shift;
146 21         519 my @args = $self->argv;
147 21         513 $self->class_name; # causes death when plugin syntax is wrong
148              
149 21         20 my %plugins;
150             my @accumulator;
151 21         23 my $in_plugin = undef;
152              
153             ARG:
154 21         22 for my $arg (@args) {
155 65 100       71 if(defined $in_plugin){
156 19 100       35 if($arg eq '--'){
    100          
157 8         15 $plugins{$in_plugin} = [@accumulator];
158 8         11 @accumulator = ();
159 8         218 return \%plugins;
160             }
161             elsif($arg =~ /^[+](.+)$/){
162 4         9 $plugins{$in_plugin} = [@accumulator];
163 4         7 @accumulator = ();
164 4         4 $in_plugin = $1;
165 4         4 next ARG;
166             }
167             else {
168 7         8 push @accumulator, $arg;
169             }
170             }
171             else { # once we are $in_plugin, we can never be out again
172 46 100       100 if($arg eq '--'){
    100          
173 3         94 return {};
174             }
175             elsif($arg =~ /^[+](.+)$/){
176 8         12 $in_plugin = $1;
177 8         11 next ARG;
178             }
179             }
180             }
181              
182 10 50       13 if($in_plugin){
183 0         0 confess "Parse error: expecting arguments for plugin $in_plugin, but got EOF. ".
184             "Perhaps you forgot '--' ?";
185             }
186              
187 10         261 return {};
188             }
189              
190             sub _delete_first($\@) {
191             my ($to_delete, $list) = @_;
192             my $idx = first_index { $_ eq $to_delete } @$list;
193             splice @$list, $idx, 1;
194             return;
195             }
196              
197             # this is a dumb way to do it, but i forgot about it until just now,
198             # and don't want to rewrite the whole class ;) ;)
199             sub _build_app_args {
200 13     13   14 my $self = shift;
201 13         308 my @args = $self->argv;
202              
203 13 50       336 return [] if $self->is_help; # LIES!!11!, but who cares
204              
205             # functional programmers may wish to avert their eyes
206 13         325 _delete_first $_, @args for map { "-M$_" } $self->modules;
  6         19  
207 13         361 _delete_first $_, @args for map { "-I$_" } $self->include_paths;
  7         54  
208              
209 13         11 my %plugins = %{ $self->plugins };
  13         333  
210              
211             PLUGIN:
212 13         21 for my $p (keys %plugins){
213 4         6 my $vl = scalar @{ $plugins{$p} };
  4         4  
214 4     4   13 my $idx = first_index { $_ eq "+$p" } @args;
  4         9  
215 4 50       13 next PLUGIN if $idx == -1; # HORRIBLE API!
216              
217 4         7 splice @args, $idx, $vl + 1;
218             }
219              
220 13 100       22 if($args[0] eq '--'){
221 5         5 shift @args;
222             }
223              
224 13 50       347 if($args[0] eq $self->class_name){
225 13         13 shift @args;
226             }
227             else {
228 0         0 confess 'Parse error: Some residual crud was found before the app name: '.
229             join ', ', @args;
230             }
231              
232 13         334 return [@args];
233             }
234              
235             # XXX: bad
236             sub guess_cmdline {
237 1     1 0 1718 my ($self, %opts) = @_;
238              
239 1 50       28 confess 'Parser is help' if $self->is_help;
240              
241 1 50       2 my @perl_flags = @{$opts{perl_flags} || []};
  1         4  
242 1 50       1 my @without_plugins = @{$opts{without_plugins} || []};
  1         4  
243              
244             # invoke mx-run
245             my @cmdline = (
246             $^X,
247 1         2 (map { "-I$_" } @INC),
  1         5  
248             @perl_flags,
249             $FindBin::Bin.'/'.$FindBin::Script,
250             );
251 1         28 push @cmdline, map { "-I$_" } $self->include_paths;
  1         34  
252 1         32 push @cmdline, map { "-M$_" } $self->modules;
  2         5  
253              
254             p:
255 1         1 for my $plugin (keys %{$self->plugins}){
  1         28  
256 2         3 for my $without (@without_plugins) {
257 2 100       7 next p if $without eq $plugin;
258             }
259 1 50       2 push @cmdline, "+$plugin", @{$self->plugins->{$plugin} || []};
  1         28  
260             }
261 1         2 push @cmdline, '--';
262 1         24 push @cmdline, $self->class_name;
263 1         24 push @cmdline, $self->app_args;
264              
265 1         6 return @cmdline;
266             }
267              
268             1;
269              
270             __END__
271              
272             =pod
273              
274             =encoding UTF-8
275              
276             =head1 NAME
277              
278             MooseX::Runnable::Util::ArgParser - parse @ARGV for C<mx-run>
279              
280             =head1 VERSION
281              
282             version 0.10
283              
284             =head1 SYNOPSIS
285              
286             my $parser = MooseX::Runnable::Util::ArgParser->new(
287             argv => \@ARGV,
288             );
289              
290             $parser->class_name;
291             $parser->modules;
292             $parser->include_paths;
293             $parser->plugins;
294             $parser->is_help;
295             $parser->app_args;
296              
297             =head1 SUPPORT
298              
299             Bugs may be submitted through L<the RT bug tracker|https://rt.cpan.org/Public/Dist/Display.html?Name=MooseX-Runnable>
300             (or L<bug-MooseX-Runnable@rt.cpan.org|mailto:bug-MooseX-Runnable@rt.cpan.org>).
301              
302             There is also a mailing list available for users of this distribution, at
303             L<http://lists.perl.org/list/moose.html>.
304              
305             There is also an irc channel available for users of this distribution, at
306             L<C<#moose> on C<irc.perl.org>|irc://irc.perl.org/#moose>.
307              
308             =head1 AUTHOR
309              
310             Jonathan Rockway <jrockway@cpan.org>
311              
312             =head1 COPYRIGHT AND LICENSE
313              
314             This software is copyright (c) 2009 by Jonathan Rockway.
315              
316             This is free software; you can redistribute it and/or modify it under
317             the same terms as the Perl 5 programming language system itself.
318              
319             =cut