1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
|
=pod
=head1 NAME
MooseX::App::Tutorial - getting started with MooseX::App
=head1 GETTING STARTED
To create a simple command-line application with MooseX::App you need
=over
=item * A base class
=item * Multiple command classes (unless you use L<MooseX::App::Simple>)
=item * and an invocation script
=back
=head1 BASE CLASS
The simplest possible base class just contains a single use statement which
loads all roles and metaclasses you need to get started as well as Moose and
hence strictures as well as warnings.
package MyApp;
use MooseX::App;
1;
The base class can be customized by
=over
=item * adding MooseX-App plugins
=item * changing the command-class namespace
=item * defining global options/parameters used by all command classes (only
if command classes inherit from the base class)
=item * add documentation (either POD or via the app_usage and
app_description functions)
=item * and changing MooseX-App flags (eg. turn fuzzy matching off)
=item * Adding Moose attribute documentation and type constraints.
=back
It is also possible to add global options and parameters to your base class
and inherit your command classes from the base class (inheriting your command
classes from your base class is purely optional).
package MyApp;
use MooseX::App qw(Config Color); # Loads the Config and Color plugin
# This attribute will be available at the command line
option 'some_global_option' => (
is => 'rw',
isa => 'Str',
required => 1,
documentation => q[Some important global option],
);
# This attribute will not be exposed
has 'private_option' => (
is => 'rw',
isa => 'Str',
);
1;
When adding attributes make sure to include a documentation and possibly
a type constraint. MooseX-App will use this information to build a user
documentation for each attribute and command. The attribute documentation
can be customized by providing additional options (see
L<MooseX::App::Meta::Role::Attribute::Option>)
=head1 COMMAND CLASSES
After you created a base class it is time to create one class for each command
you want to provide (unless you are using L<MooseX::App::Simple>). The command
classes must reside in the namespace of the base class (eg. 'MyApp::SomeCommand').
You can also deeply nest classes in the main namespace to create subcommand.
The namespace for the command classes however can be changed via the
'app_namespace' function in the base class, or by simply registering command
classes manually via 'app_command_register'. Use 'app_exclude' to exclude certain
sub namespaces.
All command classes must use MooseX::App::Command, which will also load Moose.
package MyApp::SomeCommand;
use MooseX::App::Command;
If you want to use global options defined in the base class you can optionally
extend the base class with your command class.
package MyApp::SomeCommand;
use MooseX::App::Command;
extends qw(MyApp);
To provide a description for each command you need to set the
C<command_short_description>, C<command_long_description> and
optionally C<command_usage> information. The command descriptions may
contain linebreaks.
command_short_description q[This command is awesome];
command_long_description q[This command is so awesome, yadda yadda yadda];
If not provided, MooseX-App will try to parse the command description from
the POD. The NAME or ABSTRACT section will become the short description and
the DESCRIPTION or OVERVIEW section the long description.
The usage header can either be set by adding C<command_usage>
command_usage q[script some_command --some_option NUMBER];
or by adding a SYNOPSIS or USAGE section to the module' POD. If neither
command_usage nor SYNOPSIS/USAGE are set, then the usage header will
be autogenerated.
Attributes can be documented using the Moose built-in C<documentation> option
as well as C<cmd_tags>, C<cmd_flag> and C<cmd_aliases> which are defined by
MooseX-App (see L<MooseX::App::Meta::Role::Attribute::Option>)
option 'some_option' => (
is => 'rw',
isa => 'Integer',
required => 1,
documentation => q[Some important option],
cmd_tags => [qw(Important!)], # Extra tags. Displayed in square brackets
cmd_aliases => [qw(any)], # Alternative option name
cmd_flag => 'some', # Option should be called 'some' instead of 'some_option'
);
It is also possible to define positional parameters with the 'parameter'
keyword
# This attribute will become a positional parameter
parameter 'id' => (
is => 'rw',
isa => 'Int',
documentation => q[Some ID],
required => 1,
);
The help for this command would look something like this (with autogenerated
usage headers):
usage:
my_app some_command <ID> [long options...]
my_app help
my_app some_command --help
description:
This command is awesome, yadda yadda yadda
parameter:
ID Some ID [Integer; Required]
options:
--config Path to command config file
--some -s Some important option [Required; Integer; Important!]
--help --usage -? Prints this usage information. [Flag]
In case you want to include an attribute not defined with the 'option' or
'parameter' keyword (eg from a Role) you can use the 'AppOption' trait and 'cmd_type'
attribute. (see L<MooseX::App::Meta::Attribute::Option>).
has 'myoption' => (
is => 'rw',
traits => ['AppOption'], # only required if not definded in base or command class
cmd_type => 'option', # or 'parameter'
);
Finally your command classes will need a method which should be called
if the command is invoked by the user.
sub run {
my ($self) = @_;
# do something
}
If you need to implement only a single command you should use
L<MooseX::App::Simple> instead of L<MooseX::App>, and omit command classes.
In this case of course you have to declare all options and implement
the application logic in the base class:
package MyApp;
use MooseX::App::Simple qw(Config); # Loads the Config plugin
option 'some_option' => (
is => 'rw',
isa => 'Str',
documentation => q[Some important option],
);
sub run {
my ($self) = @_;
# do something
}
1;
=head1 INVOCATION SCRIPT
Once you have the base and command classes ready, you need to write a small
invocation script:
#!/usr/bin/env perl
use MyApp;
MyApp->new_with_command->run();
C<MyApp-E<gt>new_with_command> will try to instantiate a command class. If it
fails it will return a L<MooseX::App::Message::Envelope> object possibly
containing an error message and a usage message. Since
L<MooseX::App::Message::Envelope> follows the Null object pattern you can
call any method on it without checking the object type. Note that
MooseX::App::Message::Envelope objects may also have an exitcode set. In this
case whenever the object gets stringified, it prints on STDERR/STDOUT and
exits the program using the specified exitcode. Don't use the ovleroaded
stingification if you don't want this behaviour.
You can also pass default/fallback values to the constructor
#!/usr/bin/env perl
use MyApp;
MyApp->new_with_command( some_global_option => 'something' )->run();
If using L<MooseX::App::Simple> your invocation script needs some
modification and call C<new_with_options> instead of C<new_with_command>.
#!/usr/bin/env perl
use MyApp;
MyApp->new_with_options->run();
=head1 HOW TO CONTINUE
Once you have a basic working application you can make it more user friendly
by adding documentation (either by using the app_description, app_usage,
command_short_description, ... functions or by writing POD), Moose type
constraints and additional plugins (eg. colorise the output).
Make sure to invoke your script with APP_DEVELOPER=1 during development. This
will come with a starup penalty but perform additional checks for detecting wrong
attribute/type constraint combinations, name clashes, ...
If you want custom behaviour you could start writing your own
L<MooseX::App::WritingPlugins|plugins>.
=cut
|