add another failing real-life example to test
[gitmo/MooseX-Runnable.git] / lib / MooseX / Runnable / Util / ArgParser.pm
CommitLineData
2503822b 1package MooseX::Runnable::Util::ArgParser;
2use Moose;
3use MooseX::Types::Moose qw(HashRef ArrayRef Str Bool);
4use MooseX::Types::Path::Class qw(Dir);
5use List::MoreUtils qw(first_index);
6
7use namespace::autoclean -also => ['_look_for_dash_something', '_delete_first'];
8
9has 'argv' => (
10 is => 'ro',
11 isa => ArrayRef,
12 required => 1,
13 auto_deref => 1,
14);
15
16has 'class_name' => (
17 is => 'ro',
18 isa => Str,
19 lazy_build => 1,
20);
21
22has 'modules' => (
23 is => 'ro',
24 isa => ArrayRef[Str],
25 lazy_build => 1,
26 auto_deref => 1,
27);
28
29has 'include_paths' => (
30 is => 'ro',
31 isa => ArrayRef[Dir],
32 lazy_build => 1,
33 auto_deref => 1,
34);
35
36has 'plugins' => (
37 is => 'ro',
38 isa => HashRef[ArrayRef[Str]],
39 lazy_build => 1,
40);
41
42has 'app_args' => (
43 is => 'ro',
44 isa => ArrayRef[Str],
45 lazy_build => 1,
46 auto_deref => 1,
47);
48
49has 'is_help' => (
50 is => 'ro',
51 isa => Bool,
52 lazy_build => 1,
53);
54
55
56sub _build_class_name {
57 my $self = shift;
58 my @args = $self->argv;
59
60 my $next_is_it = 0;
61 my $need_dash_dash = 0;
62
63 ARG:
64 for my $arg (@args) {
65 if($next_is_it){
66 return $arg;
67 }
68
69 if($arg eq '--'){
70 $next_is_it = 1;
71 next ARG;
72 }
73
74 next ARG if $arg =~ /^-[A-Za-z]/;
75
76 if($arg =~ /^[+]/){
77 $need_dash_dash = 1;
78 next ARG;
79 }
80
81 return $arg unless $need_dash_dash;
82 }
83
84 if($next_is_it){
85 confess 'Parse error: expecting ClassName, got EOF';
86 }
87 if($need_dash_dash){
88 confess 'Parse error: expecting --, got EOF';
89 }
90
3619e5d6 91 confess "Parse error: looking for ClassName, but can't find it; perhaps you meant '--help' ?";
2503822b 92}
93
94sub _look_for_dash_something($@) {
95 my ($something, @args) = @_;
96 my @result;
97
98 my $rx = qr/^-$something(.*)$/;
99 ARG:
100 for my $arg (@args) {
101 last ARG if $arg eq '--';
102 last ARG unless $arg =~ /^-/;
103 if($arg =~ /$rx/){
104 push @result, $1;
105 }
106 }
107
108 return @result;
109}
110
111sub _build_modules {
112 my $self = shift;
113 my @args = $self->argv;
114 return [ _look_for_dash_something 'M', @args ];
115}
116
117sub _build_include_paths {
118 my $self = shift;
119 my @args = $self->argv;
120 return [ map { Path::Class::dir($_) } _look_for_dash_something 'I', @args ];
121}
122
123sub _build_is_help {
124 my $self = shift;
125 my @args = $self->argv;
126 return
127 (_look_for_dash_something 'h', @args) ||
128 (_look_for_dash_something '\\?', @args) ||
129 (_look_for_dash_something '-help', @args) ;;
130}
131
132sub _build_plugins {
133 my $self = shift;
134 my @args = $self->argv;
135 $self->class_name; # causes death when plugin syntax is wrong
136
137 my %plugins;
138 my @accumulator;
139 my $in_plugin = undef;
140
141 ARG:
142 for my $arg (@args) {
143 if(defined $in_plugin){
144 if($arg eq '--'){
145 $plugins{$in_plugin} = [@accumulator];
146 @accumulator = ();
147 return \%plugins;
148 }
149 elsif($arg =~ /^[+](.+)$/){
150 $plugins{$in_plugin} = [@accumulator];
151 @accumulator = ();
152 $in_plugin = $1;
153 next ARG;
154 }
155 else {
156 push @accumulator, $arg;
157 }
158 }
159 else { # once we are $in_plugin, we can never be out again
160 if($arg eq '--'){
161 return {};
162 }
163 elsif($arg =~ /^[+](.+)$/){
164 $in_plugin = $1;
165 next ARG;
166 }
167 }
168 }
169
170 if($in_plugin){
171 confess "Parse error: expecting arguments for plugin $in_plugin, but got EOF. ".
172 "Perhaps you forgot '--' ?";
173 }
174
175 return {};
176}
177
178sub _delete_first($\@) {
179 my ($to_delete, $list) = @_;
180 my $idx = first_index { $_ eq $to_delete } @$list;
181 splice @$list, $idx, 1;
182 return;
183}
184
185# this is a dumb way to do it, but i forgot about it until just now,
186# and don't want to rewrite the whole class ;) ;)
187sub _build_app_args {
188 my $self = shift;
189 my @args = $self->argv;
190
191 return [] if $self->is_help; # LIES!!11!, but who cares
192
193 # functional programmers may wish to avert their eyes
194 _delete_first $_, @args for map { "-M$_" } $self->modules;
195 _delete_first $_, @args for map { "-I$_" } $self->include_paths;
196
197 my %plugins = %{ $self->plugins };
2123b3b8 198
636f1e06 199 PLUGIN:
2503822b 200 for my $p (keys %plugins){
201 my $vl = scalar @{ $plugins{$p} };
636f1e06 202 my $idx = first_index { $_ eq "+$p" } @args;
203 next PLUGIN if $idx == -1; # HORRIBLE API!
204
205 splice @args, $idx, $vl + 1;
2503822b 206 }
207
2503822b 208 if($args[0] eq '--'){
209 shift @args;
210 }
211
212 if($args[0] eq $self->class_name){
213 shift @args;
214 }
215 else {
216 confess 'Parse error: Some residual crud was found before the app name: '.
217 join ', ', @args;
218 }
219
220 return [@args];
221}
222
2231;
224
225__END__
226
227=head1 NAME
228
229MooseX::Runnable::Util::ArgParser - parse @ARGV for mx-run
230
231=head1 SYNOPSIS
232
233 my $parser = MooseX::Runnable::Util::ArgParser->new(
234 argv => \@ARGV,
235 );
236
237 $parser->class_name;
238 $parser->modules;
239 $parser->include_paths;
240 $parser->plugins;
241 $parser->is_help;
242 $parser->app_args;
243