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