| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
# ============================================================================« |
|
2
|
|
|
|
|
|
|
package MooseX::App; |
|
3
|
|
|
|
|
|
|
# ============================================================================« |
|
4
|
|
|
|
|
|
|
|
|
5
|
10
|
|
|
10
|
|
3049740
|
use 5.010; |
|
|
10
|
|
|
|
|
111
|
|
|
6
|
10
|
|
|
10
|
|
5964
|
use utf8; |
|
|
10
|
|
|
|
|
149
|
|
|
|
10
|
|
|
|
|
57
|
|
|
7
|
10
|
|
|
10
|
|
312
|
use strict; |
|
|
10
|
|
|
|
|
22
|
|
|
|
10
|
|
|
|
|
198
|
|
|
8
|
10
|
|
|
10
|
|
48
|
use warnings; |
|
|
10
|
|
|
|
|
19
|
|
|
|
10
|
|
|
|
|
522
|
|
|
9
|
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
our $AUTHORITY = 'cpan:MAROS'; |
|
11
|
|
|
|
|
|
|
our $VERSION = '1.41'; |
|
12
|
|
|
|
|
|
|
|
|
13
|
10
|
|
|
10
|
|
5096
|
use MooseX::App::Meta::Role::Attribute::Option; |
|
|
10
|
|
|
|
|
36
|
|
|
|
10
|
|
|
|
|
577
|
|
|
14
|
10
|
|
|
10
|
|
5806
|
use MooseX::App::Exporter qw(app_usage app_description app_base app_fuzzy app_strict app_prefer_commandline app_permute option parameter); |
|
|
10
|
|
|
|
|
39
|
|
|
|
10
|
|
|
|
|
90
|
|
|
15
|
10
|
|
|
10
|
|
6197
|
use MooseX::App::Message::Envelope; |
|
|
10
|
|
|
|
|
45
|
|
|
|
10
|
|
|
|
|
460
|
|
|
16
|
10
|
|
|
10
|
|
93
|
use Moose::Exporter; |
|
|
10
|
|
|
|
|
23
|
|
|
|
10
|
|
|
|
|
62
|
|
|
17
|
10
|
|
|
10
|
|
537
|
use Scalar::Util qw(blessed); |
|
|
10
|
|
|
|
|
20
|
|
|
|
10
|
|
|
|
|
9393
|
|
|
18
|
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
my ($IMPORT,$UNIMPORT,$INIT_META) = Moose::Exporter->build_import_methods( |
|
20
|
|
|
|
|
|
|
with_meta => [ qw(app_usage app_description app_namespace app_exclude app_base app_fuzzy app_command_name app_command_register app_strict app_prefer_commandline option parameter app_permute) ], |
|
21
|
|
|
|
|
|
|
also => [ 'Moose' ], |
|
22
|
|
|
|
|
|
|
as_is => [ 'new_with_command' ], |
|
23
|
|
|
|
|
|
|
install => [ 'unimport','init_meta' ], |
|
24
|
|
|
|
|
|
|
); |
|
25
|
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
sub import { |
|
27
|
11
|
|
|
11
|
|
705
|
my ( $class, @plugins ) = @_; |
|
28
|
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
# Get caller |
|
30
|
11
|
|
|
|
|
51
|
my ($caller_class) = caller(); |
|
31
|
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
# Process plugins |
|
33
|
11
|
|
|
|
|
296
|
MooseX::App::Exporter->process_plugins($caller_class,@plugins); |
|
34
|
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
# Call Moose-Exporter generated importer |
|
36
|
11
|
|
|
|
|
64
|
return $class->$IMPORT( { into => $caller_class } ); |
|
37
|
|
|
|
|
|
|
} |
|
38
|
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
sub init_meta { |
|
40
|
11
|
|
|
11
|
0
|
1211
|
my ($class,%args) = @_; |
|
41
|
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
# Get required roles and metaroles |
|
43
|
11
|
|
|
|
|
40
|
$args{roles} = ['MooseX::App::Role::Base']; |
|
44
|
|
|
|
|
|
|
$args{metaroles} = { |
|
45
|
11
|
|
|
|
|
60
|
class => [ |
|
46
|
|
|
|
|
|
|
'MooseX::App::Meta::Role::Class::Base', |
|
47
|
|
|
|
|
|
|
'MooseX::App::Meta::Role::Class::Documentation' |
|
48
|
|
|
|
|
|
|
], |
|
49
|
|
|
|
|
|
|
attribute => [ |
|
50
|
|
|
|
|
|
|
'MooseX::App::Meta::Role::Attribute::Option' |
|
51
|
|
|
|
|
|
|
], |
|
52
|
|
|
|
|
|
|
}; |
|
53
|
|
|
|
|
|
|
|
|
54
|
11
|
|
|
|
|
100
|
return MooseX::App::Exporter->process_init_meta(%args); |
|
55
|
|
|
|
|
|
|
} |
|
56
|
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
sub app_command_name(&) { |
|
58
|
3
|
|
|
3
|
1
|
64
|
my ( $meta, $namesub ) = @_; |
|
59
|
3
|
|
|
|
|
131
|
return $meta->app_command_name($namesub); |
|
60
|
|
|
|
|
|
|
} |
|
61
|
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
sub app_command_register(%) { |
|
63
|
0
|
|
|
0
|
1
|
0
|
my ( $meta, %commands ) = @_; |
|
64
|
|
|
|
|
|
|
|
|
65
|
0
|
|
|
|
|
0
|
foreach my $command (keys %commands) { |
|
66
|
0
|
|
|
|
|
0
|
$meta->command_register($command,$commands{$command}); |
|
67
|
|
|
|
|
|
|
} |
|
68
|
0
|
|
|
|
|
0
|
return; |
|
69
|
|
|
|
|
|
|
} |
|
70
|
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
sub app_namespace(@) { |
|
72
|
0
|
|
|
0
|
1
|
0
|
my ( $meta, @namespaces ) = @_; |
|
73
|
0
|
|
|
|
|
0
|
return $meta->app_namespace( \@namespaces ); |
|
74
|
|
|
|
|
|
|
} |
|
75
|
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
sub app_exclude(@) { |
|
77
|
3
|
|
|
3
|
1
|
48
|
my ( $meta, @namespaces ) = @_; |
|
78
|
3
|
|
|
|
|
106
|
return $meta->app_exclude( \@namespaces ); |
|
79
|
|
|
|
|
|
|
} |
|
80
|
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
sub new_with_command { |
|
82
|
70
|
|
|
70
|
1
|
64795
|
my ($class,@args) = @_; |
|
83
|
|
|
|
|
|
|
|
|
84
|
70
|
100
|
66
|
|
|
576
|
Moose->throw_error('new_with_command is a class method') |
|
85
|
|
|
|
|
|
|
if ! defined $class || blessed($class); |
|
86
|
|
|
|
|
|
|
|
|
87
|
69
|
|
|
|
|
444
|
my $meta = $class->meta; |
|
88
|
69
|
|
|
|
|
2240
|
my $metameta = $meta->meta; |
|
89
|
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
# Sanity check |
|
91
|
69
|
100
|
66
|
|
|
1566
|
Moose->throw_error('new_with_command may only be called from the application base package:'.$class) |
|
92
|
|
|
|
|
|
|
if $metameta->does_role('MooseX::App::Meta::Role::Class::Command') |
|
93
|
|
|
|
|
|
|
|| ! $metameta->does_role('MooseX::App::Meta::Role::Class::Base'); |
|
94
|
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
$meta->command_check() |
|
96
|
68
|
50
|
33
|
|
|
55116
|
if $ENV{APP_DEVELOPER} || $ENV{HARNESS_ACTIVE}; |
|
97
|
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
# Extra args |
|
99
|
68
|
|
|
|
|
148
|
my %args; |
|
100
|
68
|
50
|
33
|
|
|
513
|
if (scalar @args == 1 |
|
|
|
100
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
&& ref($args[0]) eq 'HASH' ) { |
|
102
|
0
|
|
|
|
|
0
|
%args = %{$args[0]}; |
|
|
0
|
|
|
|
|
0
|
|
|
103
|
|
|
|
|
|
|
} elsif (scalar @args % 2 == 0) { |
|
104
|
67
|
|
|
|
|
178
|
%args = @args; |
|
105
|
|
|
|
|
|
|
} else { |
|
106
|
1
|
|
|
|
|
5
|
Moose->throw_error('new_with_command got invalid extra arguments'); |
|
107
|
|
|
|
|
|
|
} |
|
108
|
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
# Get ARGV |
|
110
|
67
|
|
|
|
|
262
|
my $argv = delete $args{ARGV}; |
|
111
|
67
|
|
|
|
|
159
|
my $parsed_argv; |
|
112
|
67
|
100
|
|
|
|
173
|
if (defined $argv) { |
|
113
|
7
|
|
|
|
|
230
|
$parsed_argv = MooseX::App::ParsedArgv->new( argv => $argv ); |
|
114
|
|
|
|
|
|
|
} else { |
|
115
|
60
|
|
|
|
|
306
|
$parsed_argv = MooseX::App::ParsedArgv->instance(); |
|
116
|
|
|
|
|
|
|
} |
|
117
|
|
|
|
|
|
|
|
|
118
|
67
|
|
|
|
|
310
|
my $first_argv = $parsed_argv->first_argv; |
|
119
|
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
# Requested help |
|
121
|
67
|
100
|
100
|
|
|
1121
|
if (defined $first_argv |
|
|
|
100
|
66
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
122
|
|
|
|
|
|
|
&& lc($first_argv) =~ m/^(help|h|\?|usage|-h|--help|-\?|--usage)$/) { |
|
123
|
2
|
|
|
|
|
17
|
return MooseX::App::Message::Envelope->new( |
|
124
|
|
|
|
|
|
|
$meta->command_usage_global(), |
|
125
|
|
|
|
|
|
|
); |
|
126
|
|
|
|
|
|
|
# No args |
|
127
|
|
|
|
|
|
|
} elsif (! defined $first_argv |
|
128
|
|
|
|
|
|
|
|| $first_argv =~ m/^\s*$/ |
|
129
|
|
|
|
|
|
|
|| $first_argv =~ m/^-{1,2}\w/) { |
|
130
|
2
|
|
|
|
|
23
|
return MooseX::App::Message::Envelope->new( |
|
131
|
|
|
|
|
|
|
$meta->command_message( |
|
132
|
|
|
|
|
|
|
header => "Missing command", # LOCALIZE |
|
133
|
|
|
|
|
|
|
type => "error", |
|
134
|
|
|
|
|
|
|
), |
|
135
|
|
|
|
|
|
|
$meta->command_usage_global(), |
|
136
|
|
|
|
|
|
|
127, # exitcode |
|
137
|
|
|
|
|
|
|
); |
|
138
|
|
|
|
|
|
|
# Looks like a command |
|
139
|
|
|
|
|
|
|
} else { |
|
140
|
63
|
|
|
|
|
389
|
my $return = $meta->command_find(); |
|
141
|
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
# Nothing found |
|
143
|
63
|
100
|
66
|
|
|
403
|
if (blessed $return |
|
144
|
|
|
|
|
|
|
&& $return->isa('MooseX::App::Message::Block')) { |
|
145
|
2
|
|
|
|
|
20
|
return MooseX::App::Message::Envelope->new( |
|
146
|
|
|
|
|
|
|
$return, |
|
147
|
|
|
|
|
|
|
$meta->command_usage_global(), |
|
148
|
|
|
|
|
|
|
127, # exitcode |
|
149
|
|
|
|
|
|
|
); |
|
150
|
|
|
|
|
|
|
# One command found |
|
151
|
|
|
|
|
|
|
} else { |
|
152
|
61
|
|
|
|
|
2564
|
my $command_class = $meta->command_get($return); |
|
153
|
61
|
|
|
|
|
421
|
return $class->initialize_command_class($command_class,%args); |
|
154
|
|
|
|
|
|
|
} |
|
155
|
|
|
|
|
|
|
} |
|
156
|
|
|
|
|
|
|
} |
|
157
|
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
|
|
159
|
10
|
|
|
10
|
|
88
|
no Moose; |
|
|
10
|
|
|
|
|
39
|
|
|
|
10
|
|
|
|
|
78
|
|
|
160
|
|
|
|
|
|
|
1; |
|
161
|
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
__END__ |
|
163
|
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
=encoding utf8 |
|
165
|
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
=head1 NAME |
|
167
|
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
MooseX::App - Write user-friendly command line apps with even less suffering |
|
169
|
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
171
|
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
In your base class: |
|
173
|
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
package MyApp; |
|
175
|
|
|
|
|
|
|
use MooseX::App qw(Color); |
|
176
|
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
option 'global_option' => ( |
|
178
|
|
|
|
|
|
|
is => 'rw', |
|
179
|
|
|
|
|
|
|
isa => 'Bool', |
|
180
|
|
|
|
|
|
|
documentation => q[Enable this to do fancy stuff], |
|
181
|
|
|
|
|
|
|
); # Global option |
|
182
|
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
has 'private' => ( |
|
184
|
|
|
|
|
|
|
is => 'rw', |
|
185
|
|
|
|
|
|
|
); # not exposed |
|
186
|
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
Write multiple command classes (If you have only a single command class |
|
188
|
|
|
|
|
|
|
you should use L<MooseX::App::Simple> instead). Packackes in the namespace may be |
|
189
|
|
|
|
|
|
|
deeply nested. |
|
190
|
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
package MyApp::SomeCommand; |
|
192
|
|
|
|
|
|
|
use MooseX::App::Command; # important (also imports Moose) |
|
193
|
|
|
|
|
|
|
extends qw(MyApp); # optional, only if you want to use global options from base class |
|
194
|
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
# Positional parameter |
|
196
|
|
|
|
|
|
|
parameter 'some_parameter' => ( |
|
197
|
|
|
|
|
|
|
is => 'rw', |
|
198
|
|
|
|
|
|
|
isa => 'Str', |
|
199
|
|
|
|
|
|
|
required => 1, |
|
200
|
|
|
|
|
|
|
documentation => q[Some parameter that you need to supply], |
|
201
|
|
|
|
|
|
|
); |
|
202
|
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
option 'some_option' => ( |
|
204
|
|
|
|
|
|
|
is => 'rw', |
|
205
|
|
|
|
|
|
|
isa => 'Int', |
|
206
|
|
|
|
|
|
|
required => 1, |
|
207
|
|
|
|
|
|
|
documentation => q[Very important option!], |
|
208
|
|
|
|
|
|
|
); # Option |
|
209
|
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
sub run { |
|
211
|
|
|
|
|
|
|
my ($self) = @_; |
|
212
|
|
|
|
|
|
|
# Do something |
|
213
|
|
|
|
|
|
|
} |
|
214
|
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
And then you need a simple wrapper script (called eg. myapp): |
|
216
|
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
#!/usr/bin/env perl |
|
218
|
|
|
|
|
|
|
use MyApp; |
|
219
|
|
|
|
|
|
|
MyApp->new_with_command->run; |
|
220
|
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
On the command line: |
|
222
|
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
bash$ myapp help |
|
224
|
|
|
|
|
|
|
usage: |
|
225
|
|
|
|
|
|
|
myapp <command> [long options...] |
|
226
|
|
|
|
|
|
|
myapp help |
|
227
|
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
global options: |
|
229
|
|
|
|
|
|
|
--global_option Enable this to do fancy stuff [Flag] |
|
230
|
|
|
|
|
|
|
--help --usage -? Prints this usage information. [Flag] |
|
231
|
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
available commands: |
|
233
|
|
|
|
|
|
|
some_command Description of some command |
|
234
|
|
|
|
|
|
|
another_command Description of another command |
|
235
|
|
|
|
|
|
|
help Prints this usage information |
|
236
|
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
or |
|
238
|
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
bash$ myapp some_command --help |
|
240
|
|
|
|
|
|
|
usage: |
|
241
|
|
|
|
|
|
|
myapp some_command <SOME_PARAMETER> [long options...] |
|
242
|
|
|
|
|
|
|
myapp help |
|
243
|
|
|
|
|
|
|
myapp some_command --help |
|
244
|
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
parameters: |
|
246
|
|
|
|
|
|
|
some_parameter Some parameter that you need to supply [Required] |
|
247
|
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
options: |
|
249
|
|
|
|
|
|
|
--global_option Enable this to do fancy stuff [Flag] |
|
250
|
|
|
|
|
|
|
--some_option Very important option! [Int,Required] |
|
251
|
|
|
|
|
|
|
--help --usage -? Prints this usage information. [Flag] |
|
252
|
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
254
|
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
MooseX-App is a highly customisable helper to write user-friendly |
|
256
|
|
|
|
|
|
|
command line applications without having to worry about most of the annoying |
|
257
|
|
|
|
|
|
|
things usually involved. Just take any existing L<Moose> class, add a single |
|
258
|
|
|
|
|
|
|
line (C<use MooseX-App qw(PluginA PluginB ...);>) and create one class |
|
259
|
|
|
|
|
|
|
for each command in an underlying namespace. Options and positional parameters |
|
260
|
|
|
|
|
|
|
can be defined as simple L<Moose> accessors using the C<option> and |
|
261
|
|
|
|
|
|
|
C<parameter> keywords respectively. |
|
262
|
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
MooseX-App will then |
|
264
|
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
=over |
|
266
|
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
=item * Find, load and initialise the command classes (see |
|
268
|
|
|
|
|
|
|
L<MooseX::App::Simple> for single class/command applications) |
|
269
|
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
=item * Create automated help and documentation from modules POD as well as |
|
271
|
|
|
|
|
|
|
attributes metadata and type constraints |
|
272
|
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
=item * Read, encode and validate the command line options and positional |
|
274
|
|
|
|
|
|
|
parameters entered by the user from @ARGV and %ENV (and possibly prompt |
|
275
|
|
|
|
|
|
|
the user for additional parameters see L<MooseX::App::Plugin::Term>) |
|
276
|
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
=item * Provide helpful error messages if user input cannot be validated |
|
278
|
|
|
|
|
|
|
(either missing or wrong attributes or Moose type constraints not satisfied) |
|
279
|
|
|
|
|
|
|
or if the user requests help. |
|
280
|
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
=back |
|
282
|
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
Commandline options are defined using the 'option' keyword which accepts |
|
284
|
|
|
|
|
|
|
the same attributes as Moose' 'has' keyword. |
|
285
|
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
option 'some_option' => ( |
|
287
|
|
|
|
|
|
|
is => 'rw', |
|
288
|
|
|
|
|
|
|
isa => 'Str', |
|
289
|
|
|
|
|
|
|
); |
|
290
|
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
This is equivalent to |
|
292
|
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
has 'some_option' => ( |
|
294
|
|
|
|
|
|
|
is => 'rw', |
|
295
|
|
|
|
|
|
|
isa => 'Str', |
|
296
|
|
|
|
|
|
|
traits => ['AppOption'], # Load extra metaclass |
|
297
|
|
|
|
|
|
|
cmd_type => 'option', # Set attribute type |
|
298
|
|
|
|
|
|
|
); |
|
299
|
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
Single letter options are treated as flags and may be combined with eachother. |
|
301
|
|
|
|
|
|
|
However such options must have a Boolean type constraint. |
|
302
|
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
option 'verbose' => ( |
|
304
|
|
|
|
|
|
|
is => 'rw', |
|
305
|
|
|
|
|
|
|
isa => 'Bool', |
|
306
|
|
|
|
|
|
|
cmd_flag => 'v', |
|
307
|
|
|
|
|
|
|
); |
|
308
|
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
Positional parameters are defined with the 'parameter' keyword |
|
310
|
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
parameter 'some_option' => ( |
|
312
|
|
|
|
|
|
|
is => 'rw', |
|
313
|
|
|
|
|
|
|
isa => 'Str', |
|
314
|
|
|
|
|
|
|
); |
|
315
|
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
This is equivalent to |
|
317
|
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
has 'some_option' => ( |
|
319
|
|
|
|
|
|
|
is => 'rw', |
|
320
|
|
|
|
|
|
|
isa => 'Str', |
|
321
|
|
|
|
|
|
|
traits => ['AppOption'], |
|
322
|
|
|
|
|
|
|
cmd_type => 'parameter', |
|
323
|
|
|
|
|
|
|
); |
|
324
|
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
All keywords are imported by L<Moosex::App> (in the app base class) and |
|
326
|
|
|
|
|
|
|
L<MooseX::App::Command> (in the command class) or L<MooseX::App::Simple> |
|
327
|
|
|
|
|
|
|
(single class application). |
|
328
|
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
Furthermore, all options and parameters can also be supplied via %ENV |
|
330
|
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
option 'some_option' => ( |
|
332
|
|
|
|
|
|
|
is => 'rw', |
|
333
|
|
|
|
|
|
|
isa => 'Str', |
|
334
|
|
|
|
|
|
|
cmd_env => 'SOME_OPTION', # sets the env key |
|
335
|
|
|
|
|
|
|
); |
|
336
|
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
Moose type constraints help MooseX::App to construct helpful error messages |
|
338
|
|
|
|
|
|
|
and parse @ARGV in a meaningful way. The following type constraints are |
|
339
|
|
|
|
|
|
|
supported: |
|
340
|
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
=over |
|
342
|
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
=item * ArrayRef: Specify multiple values ('--opt value1 --opt value2', |
|
344
|
|
|
|
|
|
|
also see L<app_permute> and L<cmd_split>) |
|
345
|
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
=item * HashRef: Specify multiple key value pairs ('--opt key=value --opt |
|
347
|
|
|
|
|
|
|
key2=value2', also see L<app_permute>) |
|
348
|
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
=item * Enum: Display all possibilities |
|
350
|
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
=item * Bool: Flags that do not require values |
|
352
|
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
=item * Int, Num: Used for proper error messages |
|
354
|
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
=back |
|
356
|
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
Read the L<Tutorial|MooseX::App::Tutorial> for getting started with a simple |
|
358
|
|
|
|
|
|
|
MooseX::App command line application. |
|
359
|
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
=head1 METHODS |
|
361
|
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
=head2 new_with_command |
|
363
|
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
my $myapp_command = MyApp->new_with_command(); |
|
365
|
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
This constructor reads the command line arguments and tries to create a |
|
367
|
|
|
|
|
|
|
command class instance. If it fails it returns a |
|
368
|
|
|
|
|
|
|
L<MooseX::App::Message::Envelope> object holding an error message. |
|
369
|
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
You can pass a hash of default/fallback params to new_with_command |
|
371
|
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
my $obj = MyApp->new_with_command(%default); |
|
373
|
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
Optionally you can pass a custom ARGV to this constructor |
|
375
|
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
my $obj = MyApp->new_with_command( ARGV => \@myARGV ); |
|
377
|
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
However, if you do so you must take care of propper @ARGV encoding yourself. |
|
379
|
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
=head2 initialize_command_class |
|
381
|
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
my $obj = MyApp->initialize_command_class($command_name,%default); |
|
383
|
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
Helper method to instantiate the command class for the given command. |
|
385
|
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
=head1 GLOBAL OPTIONS |
|
387
|
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
These options may be used to alter the default behaviour of MooseX-App. |
|
389
|
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
=head2 app_base |
|
391
|
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
app_base 'my_script'; # Defaults to $0 |
|
393
|
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
Usually MooseX::App will take the name of the calling wrapper script to |
|
395
|
|
|
|
|
|
|
construct the program name in various help messages. This name can |
|
396
|
|
|
|
|
|
|
be changed via the app_base function. |
|
397
|
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
=head2 app_fuzzy |
|
399
|
|
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
app_fuzzy 1; # default |
|
401
|
|
|
|
|
|
|
OR |
|
402
|
|
|
|
|
|
|
app_fuzzy 0; |
|
403
|
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
Enables fuzzy matching of commands and attributes. Is turned on by default. |
|
405
|
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
=head2 app_strict |
|
407
|
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
app_strict 0; # default |
|
409
|
|
|
|
|
|
|
OR |
|
410
|
|
|
|
|
|
|
app_strict 1; |
|
411
|
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
If strict is enabled the program will terminate with an error message if |
|
413
|
|
|
|
|
|
|
superfluous/unknown positional parameters are supplied. If disabled all |
|
414
|
|
|
|
|
|
|
extra parameters will be copied to the L<extra_argv> attribute. Unknown |
|
415
|
|
|
|
|
|
|
options (with leading dashes) will always yield an error message. |
|
416
|
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
The command_strict config in the command classes allows one to set this option |
|
418
|
|
|
|
|
|
|
individually for each command in the respective command class. |
|
419
|
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
=head2 app_prefer_commandline |
|
421
|
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
app_prefer_commandline 0; # default |
|
423
|
|
|
|
|
|
|
or |
|
424
|
|
|
|
|
|
|
app_prefer_commandline 1; |
|
425
|
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
Specifies if parameters/options supplied via @ARGV,%ENV should take precedence |
|
427
|
|
|
|
|
|
|
over arguments passed directly to new_with_command. |
|
428
|
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
=head2 app_namespace |
|
430
|
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
app_namespace 'MyApp::Commands', 'YourApp::MoreCommands'; |
|
432
|
|
|
|
|
|
|
OR |
|
433
|
|
|
|
|
|
|
app_namespace(); |
|
434
|
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
Usually MooseX::App will take the package name of the base class as the |
|
436
|
|
|
|
|
|
|
namespace for commands. This namespace can be changed and you can add |
|
437
|
|
|
|
|
|
|
multiple extra namespaces. |
|
438
|
|
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
If app_namespace is called with no arguments then autoloading of command |
|
440
|
|
|
|
|
|
|
classes will be disabled entirely. |
|
441
|
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
=head2 app_exclude |
|
443
|
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
app_exclude 'MyApp::Commands::Roles','MyApp::Commands::Utils'; |
|
445
|
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
A sub namespace included via app_namespace (or the default behaviour) can |
|
447
|
|
|
|
|
|
|
be excluded using app_exclude. |
|
448
|
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
=head2 app_command_name |
|
450
|
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
app_command_name { |
|
452
|
|
|
|
|
|
|
my ($package_short,$package_full) = @_; |
|
453
|
|
|
|
|
|
|
# munge package name; |
|
454
|
|
|
|
|
|
|
return $command_name; |
|
455
|
|
|
|
|
|
|
}; |
|
456
|
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
This coderef can be used to control how autoloaded package names should be |
|
458
|
|
|
|
|
|
|
translated to command names. If this command returns nothing the respective |
|
459
|
|
|
|
|
|
|
command class will be skipped and not loaded. |
|
460
|
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
=head2 app_command_register |
|
462
|
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
app_command_register |
|
464
|
|
|
|
|
|
|
do => 'MyApp::Commands::DoSomething', |
|
465
|
|
|
|
|
|
|
undo => 'MyApp::Commands::UndoSomething'; |
|
466
|
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
This keyword can be used to register additional commands. Especially |
|
468
|
|
|
|
|
|
|
useful in conjunction with app_namespace and disabled autoloading. |
|
469
|
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
=head2 app_description |
|
471
|
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
app_description qq[Description text]; |
|
473
|
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
Set the app description text. If not set this information will be taken from |
|
475
|
|
|
|
|
|
|
the Pod DESCRIPTION or OVERVIEW sections. (see command_description to set |
|
476
|
|
|
|
|
|
|
usage per command) |
|
477
|
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
=head2 app_usage |
|
479
|
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
app_usage qq[myapp --option ...]; |
|
481
|
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
Set a custom usage text. If not set this will be taken from the Pod SYNOPSIS |
|
483
|
|
|
|
|
|
|
or USAGE section. If both sections are not available, the usage information |
|
484
|
|
|
|
|
|
|
will be autogenerated. (see command_usage to set usage per command) |
|
485
|
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
=head2 app_permute |
|
487
|
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
app_permute 0; # default |
|
489
|
|
|
|
|
|
|
OR |
|
490
|
|
|
|
|
|
|
app_permute 1; |
|
491
|
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
Allows one to specify multiple values with one key. So instead of writing |
|
493
|
|
|
|
|
|
|
C<--list element1 --list element2 --list element3> one might write |
|
494
|
|
|
|
|
|
|
C<--list element1 element2 element3> for ArrayRef elements. HashRef elements |
|
495
|
|
|
|
|
|
|
may be expressed as <--hash key=value key2=value2>. |
|
496
|
|
|
|
|
|
|
|
|
497
|
|
|
|
|
|
|
=head1 GLOBAL ATTRIBUTES |
|
498
|
|
|
|
|
|
|
|
|
499
|
|
|
|
|
|
|
All MooseX::App classes will have two extra attributes |
|
500
|
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
=head2 extra_argv |
|
502
|
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
Carries all parameters from @ARGV that were not consumed (only if app_strict |
|
504
|
|
|
|
|
|
|
is turned off, otherwise superfluous parameters will raise an exception). |
|
505
|
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
=head2 help_flag |
|
507
|
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
Help flag that is set when help was requested. |
|
509
|
|
|
|
|
|
|
|
|
510
|
|
|
|
|
|
|
=head1 ATTRIBUTE OPTIONS |
|
511
|
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
Options and parameters accept extra attributes for customisation: |
|
513
|
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
=over |
|
515
|
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
=item * cmd_tags - Extra tags (as used by the help) |
|
517
|
|
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
=item * cmd_flag - Override option/parameter name |
|
519
|
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
=item * cmd_aliases - Additional option/parameter name aliases |
|
521
|
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
=item * cmd_split - Split values into ArrayRefs on this token |
|
523
|
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
=item * cmd_position - Specify option/parameter order in help |
|
525
|
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
=item * cmd_env - Read options/parameters from %ENV |
|
527
|
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
=item * cmd_count - Value of option equals to number of occurrences in @ARGV |
|
529
|
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
=item * cmd_negate - Adds an option to negate boolean flags |
|
531
|
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
=back |
|
533
|
|
|
|
|
|
|
|
|
534
|
|
|
|
|
|
|
Refer to L<MooseX::App::Meta::Role::Attribute::Option> for detailed |
|
535
|
|
|
|
|
|
|
documentation. |
|
536
|
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
=head1 METADATA |
|
538
|
|
|
|
|
|
|
|
|
539
|
|
|
|
|
|
|
MooseX::App will use your class metadata and POD to construct the commands and |
|
540
|
|
|
|
|
|
|
helpful error- or usage-messages. These bits of information are utilised |
|
541
|
|
|
|
|
|
|
and should be provided if possible: |
|
542
|
|
|
|
|
|
|
|
|
543
|
|
|
|
|
|
|
=over |
|
544
|
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
=item * Package names |
|
546
|
|
|
|
|
|
|
|
|
547
|
|
|
|
|
|
|
=item * L<required> options for Moose attributes |
|
548
|
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
=item * L<documentation> options for Moose attributes |
|
550
|
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
=item * Moose type constraints (Bool, ArrayRef, HashRef, Int, Num, and Enum) |
|
552
|
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
=item * Documentation set via app_description, app_usage, |
|
554
|
|
|
|
|
|
|
command_short_description, command_long_description and command_usage |
|
555
|
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
=item * POD (NAME, ABSTRACT, DESCRIPTION, USAGE, SYNOPSIS, OVERVIEW, |
|
557
|
|
|
|
|
|
|
COPYRIGHT, LICENSE, COPYRIGHT AND LICENSE, AUTHOR and AUTHORS sections) |
|
558
|
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
=item * Dzil ABSTRACT tag if no POD is available yet |
|
560
|
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
=back |
|
562
|
|
|
|
|
|
|
|
|
563
|
|
|
|
|
|
|
=head1 PLUGINS |
|
564
|
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
The behaviour of MooseX-App can be customised with plugins. To load a |
|
566
|
|
|
|
|
|
|
plugin just pass a list of plugin names after the C<use MooseX-App> statement. |
|
567
|
|
|
|
|
|
|
(Attention: order sometimes matters) |
|
568
|
|
|
|
|
|
|
|
|
569
|
|
|
|
|
|
|
use MooseX::App qw(PluginA PluginB); |
|
570
|
|
|
|
|
|
|
|
|
571
|
|
|
|
|
|
|
Currently the following plugins are shipped with MooseX::App |
|
572
|
|
|
|
|
|
|
|
|
573
|
|
|
|
|
|
|
=over |
|
574
|
|
|
|
|
|
|
|
|
575
|
|
|
|
|
|
|
=item * L<MooseX::App::Plugin::BashCompletion> |
|
576
|
|
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
Adds a command that generates a bash completion script for your application. |
|
578
|
|
|
|
|
|
|
See third party L<MooseX::App::Plugin::ZshCompletion> for Z shell completion. |
|
579
|
|
|
|
|
|
|
|
|
580
|
|
|
|
|
|
|
=item * L<MooseX::App::Plugin::Color> |
|
581
|
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
Colorful output for your MooseX::App applications. |
|
583
|
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
=item * L<MooseX::App::Plugin::Config> |
|
585
|
|
|
|
|
|
|
|
|
586
|
|
|
|
|
|
|
Config files for MooseX::App applications. |
|
587
|
|
|
|
|
|
|
|
|
588
|
|
|
|
|
|
|
=item * L<MooseX::App::Plugin::ConfigHome> |
|
589
|
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
Try to find config files in users home directory. |
|
591
|
|
|
|
|
|
|
|
|
592
|
|
|
|
|
|
|
=item * L<MooseX::App::Plugin::Term> |
|
593
|
|
|
|
|
|
|
|
|
594
|
|
|
|
|
|
|
Prompt user for options and parameters that were not provided via options or |
|
595
|
|
|
|
|
|
|
params. Prompt offers basic editing capabilities and non-persistent history. |
|
596
|
|
|
|
|
|
|
|
|
597
|
|
|
|
|
|
|
=item * L<MooseX::App::Plugin::Typo> |
|
598
|
|
|
|
|
|
|
|
|
599
|
|
|
|
|
|
|
Handle typos in command names and provide suggestions. |
|
600
|
|
|
|
|
|
|
|
|
601
|
|
|
|
|
|
|
=item * L<MooseX::App::Plugin::Version> |
|
602
|
|
|
|
|
|
|
|
|
603
|
|
|
|
|
|
|
Adds a command to display the version and license of your application. |
|
604
|
|
|
|
|
|
|
|
|
605
|
|
|
|
|
|
|
=item * L<MooseX::App::Plugin::Man> |
|
606
|
|
|
|
|
|
|
|
|
607
|
|
|
|
|
|
|
Display full manpage of application and commands. |
|
608
|
|
|
|
|
|
|
|
|
609
|
|
|
|
|
|
|
=item * L<MooseX::App::Plugin::MutexGroup> |
|
610
|
|
|
|
|
|
|
|
|
611
|
|
|
|
|
|
|
Allow for mutally exclusive options. |
|
612
|
|
|
|
|
|
|
|
|
613
|
|
|
|
|
|
|
=item * L<MooseX::App::Plugin::Depends> |
|
614
|
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
Adds dependent options. |
|
616
|
|
|
|
|
|
|
|
|
617
|
|
|
|
|
|
|
=back |
|
618
|
|
|
|
|
|
|
|
|
619
|
|
|
|
|
|
|
Refer to L<Writing MooseX-App Plugins|MooseX::App::WritingPlugins> |
|
620
|
|
|
|
|
|
|
for documentation on how to create your own plugins. |
|
621
|
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
=head1 CAVEATS & KNOWN BUGS |
|
623
|
|
|
|
|
|
|
|
|
624
|
|
|
|
|
|
|
Startup time may be an issue - escpecially if you load many plugins. If you do |
|
625
|
|
|
|
|
|
|
not require the functionality of plugins and ability for fine grained |
|
626
|
|
|
|
|
|
|
customisation (or Moose for that matter) then you should probably |
|
627
|
|
|
|
|
|
|
use L<MooX::Options> or L<MooX::Cmd>. |
|
628
|
|
|
|
|
|
|
|
|
629
|
|
|
|
|
|
|
In some cases - especially when using non-standard class inheritance - you may |
|
630
|
|
|
|
|
|
|
end up with command classes lacking the help attribute. In this case you need |
|
631
|
|
|
|
|
|
|
to include the following line in your base class or command classes. |
|
632
|
|
|
|
|
|
|
|
|
633
|
|
|
|
|
|
|
with qw(MooseX::App::Role::Common); |
|
634
|
|
|
|
|
|
|
|
|
635
|
|
|
|
|
|
|
When manually registering command classes (eg. via app_command_register) in |
|
636
|
|
|
|
|
|
|
multiple base classes with different sets of plugins (why would you ever want |
|
637
|
|
|
|
|
|
|
to do that?), then meta attributes may lack some attribute metaclasses. In |
|
638
|
|
|
|
|
|
|
this case you need to load the missing attribute traits explicitly: |
|
639
|
|
|
|
|
|
|
|
|
640
|
|
|
|
|
|
|
option 'argument' => ( |
|
641
|
|
|
|
|
|
|
depends => 'otherargument', |
|
642
|
|
|
|
|
|
|
trait => ['MooseX::App::Plugin::Depends::Meta::Attribute'], # load trait |
|
643
|
|
|
|
|
|
|
); |
|
644
|
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
=head1 SEE ALSO |
|
646
|
|
|
|
|
|
|
|
|
647
|
|
|
|
|
|
|
Read the L<Tutorial|MooseX::App::Tutorial> for getting started with a simple |
|
648
|
|
|
|
|
|
|
MooseX::App command line application. |
|
649
|
|
|
|
|
|
|
|
|
650
|
|
|
|
|
|
|
For alternatives you can check out |
|
651
|
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
L<MooseX::App::Cmd>, L<MooseX::Getopt>, L<MooX::Options>, L<MooX::Cmd>and L<App::Cmd> |
|
653
|
|
|
|
|
|
|
|
|
654
|
|
|
|
|
|
|
=head1 SUPPORT |
|
655
|
|
|
|
|
|
|
|
|
656
|
|
|
|
|
|
|
Please report any bugs or feature requests via |
|
657
|
|
|
|
|
|
|
L<https://github.com/maros/MooseX-App/issues/new>. I will be notified, and |
|
658
|
|
|
|
|
|
|
then you'll automatically be notified of progress on your report as I make |
|
659
|
|
|
|
|
|
|
changes. |
|
660
|
|
|
|
|
|
|
|
|
661
|
|
|
|
|
|
|
=head1 AUTHOR |
|
662
|
|
|
|
|
|
|
|
|
663
|
|
|
|
|
|
|
Maroš Kollár |
|
664
|
|
|
|
|
|
|
CPAN ID: MAROS |
|
665
|
|
|
|
|
|
|
maros [at] k-1.com |
|
666
|
|
|
|
|
|
|
|
|
667
|
|
|
|
|
|
|
http://www.k-1.com |
|
668
|
|
|
|
|
|
|
|
|
669
|
|
|
|
|
|
|
=head1 CONTRIBUTORS |
|
670
|
|
|
|
|
|
|
|
|
671
|
|
|
|
|
|
|
Special thanks to all contributors. |
|
672
|
|
|
|
|
|
|
|
|
673
|
|
|
|
|
|
|
In no particular order: Andrew Jones, George Hartzell, Steve Nolte, |
|
674
|
|
|
|
|
|
|
Michael G, Thomas Klausner, Yanick Champoux, Edward Baudrez, David Golden, |
|
675
|
|
|
|
|
|
|
J.R. Mash, Thilo Fester, Gregor Herrmann, Sergey Romanov, Sawyer X, Roman F., |
|
676
|
|
|
|
|
|
|
Hunter McMillen, Maik Hentsche, Alexander Stoddard, Marc Logghe, Tina Müller, |
|
677
|
|
|
|
|
|
|
Lisa Hare, Jose Luis Martinez |
|
678
|
|
|
|
|
|
|
|
|
679
|
|
|
|
|
|
|
You are more than welcome to contribute to MooseX-App. Please have a look |
|
680
|
|
|
|
|
|
|
at the L<https://github.com/maros/MooseX-App/issues?q=is%3Aissue+is%3Aopen+label%3AWishlist> |
|
681
|
|
|
|
|
|
|
list of open wishlist issues for ideas. |
|
682
|
|
|
|
|
|
|
|
|
683
|
|
|
|
|
|
|
=head1 COPYRIGHT |
|
684
|
|
|
|
|
|
|
|
|
685
|
|
|
|
|
|
|
MooseX::App is Copyright (c) 2012-17 Maroš Kollár. |
|
686
|
|
|
|
|
|
|
|
|
687
|
|
|
|
|
|
|
This library is free software and may be distributed under the same terms as |
|
688
|
|
|
|
|
|
|
perl itself. The full text of the licence can be found in the LICENCE file |
|
689
|
|
|
|
|
|
|
included with this module. |
|
690
|
|
|
|
|
|
|
|
|
691
|
|
|
|
|
|
|
=cut |