/usr/share/perl5/CLI/Framework/Tutorial.pod is in libcli-framework-perl 0.05-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
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 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 | =head1 NAME
CLI::Framework::Tutorial - "HOWTO" develop CLIF applications using best practices
=head1 CLIF DOCUMENTATION
This is a guide to developing CLIF applications. It is a supplement to the
documentation in L<CLI::Framework>, L<CLI::Framework::Application> and
L<CLI::Framework::Command>, which have more thorough coverage of some finer
points.
It is suggested that new users start by reading this document, then use the
other documentation for reference as necessary.
=head1 INTRODUCTION
Developers have been reluctantly writing ad-hoc, disposable scripts for too
long or struggling to decide how not to do so. There is a better alternative.
The L<CLI::Framework> documentation enumerates many advantages to using CLIF
instead of writing yet-another-getopt-based-script. CLIF comes with a lot of
documentation, but don't take that to mean that using CLIF is complicated. CLIF
apps with simple needs are very easy to build. Apps with complex needs are a
bit more work, but much easier to build (and far easier to test and maintain)
than doing that work from scratch.
This document will first demonstrate a very simple CLIF application. Next, a
complete application will be shown to demonstrate more advanced CLIF features.
Think of a typical command-line script. It needs to parse command-line
options and arguments, check that any required external resources (files,
databases, etc.) are available, fail nicely if something is missing or
inconsistent, then do something application-specific that depends on the
options, arguments, and external resources.
What happens when new scripts are created to do something similar? All too
often, they end up with different option names for conceptually the same
purpose. It is common for functionality needed by several scripts to be
duplicated in each similar script. This rapidly gets out of hand, becoming a
maintenance frustration. Your team members are not "on the same page" and new
people learning your tools must have lengthy, verbal, one-on-one code tours.
Instead, a set of related scripts could be combined into a CLIF application.
Consistent naming conventions and sharing of common code is naturally
encouraged. The commands are easy to test. New commands can be added with
ease.
=head1 FROM P.O.S. TO CLIF IN A FEW EASY STEPS
A "P.O.S." is a "Plain Old Script." This section shows you how to reform an
old P.O.S., creating a shiny new CLIF application!
Please see working code for this example included with the C<CLI::Framework>
distribution (F<examples/demo-simple.pl>).
This example demonstrates the following features:
=over
=item *
inline application definition
=item *
basics (app, commands, command options and args)
=item *
the relationship between plain scripts and CLIF applications (including
how to convert between them)
=back
To understand CLIF commands, imagine converting a legacy script to a CLIF
application. First, create a Perl class that inherits from
L<CLI::Framework::Command>. Place the main body of the script in a C<run()>
method. Add the functions that the script defines, if any.
# Your Command subclass...
package Converted::Script::Command::LegacyScript;
use base qw( CLI::Framework::Command );
# main body of former script goes inside run():
sub run { ... }
Next, create a Perl class (creating a separate package file for the class is
totally optional) that inherits from L<CLI::Framework::Application> (or you can
use C<CLI::Framework> as a shorthand) and define a method, C<command_map()>,
that links command names with classes that implement the commands:
# Your Application class...
package Converted::Script;
use base qw( CLI::Framework );
sub command_map {
'legacy-script' => 'Converted::Script::Command::LegacyScript',
}
The code that provides a friendly usage message (if the legacy script provided
one) can be replaced by defining the C<usage_text> method:
sub usage_text {
qq{
$0 [--verbose|v] [--help|h]: how to use this application...
}
}
Back in your Command subclass, the option/argument processing code will be
replaced with a method defining what options will be recognized (the data
structure to be returned is exactly as documented in
L<Getopt::Long::Descriptive|Getopt::Long::Descriptive/opt_spec>):
sub option_spec {
[ 'help|h' => 'show help' ],
[ 'verbose|v' => 'be verbose' ],
}
...and that's all it takes to convert a simple script to a CLIF app. This
contrived example demonstrates the mechanics, but let me point out a few
advantages (see
L<DESIGN GOALS AND FEATURES|CLI::Framework/DESIGN GOALS AND FEATURES> for the
long list):
=over
=item Clear division of responsibilities
Using packages, subroutines, and separate files (if desired), CLIF apps follow
established convention and provide a new pattern for creating tools.
=item Easy to test
Now that functional units of code are subroutines in packages, you can unit
test each component independently.
=item Easy to maintain
Instead of puzzling over a several-thousand-line script, maintaining a CLIF
application is like maintaining any other well-engineered application code.
=item Easy to extend
Related tools frequently occur in groups. Instead of awkwardly forcing
loosely-related behaviors into the same script, CLIF makes it easy to add
additional commands in a modular way.
=back
=head1 WHEN B<NOT> TO USE CLIF
CLIF could be used for the simplest of needs, but it may be overkill in very
simple situations.
You may want to avoid CLIF for very basic scripts that have a single behavior
and are completely independent from other such tools. However, if there's a
chance that the scripts might grow to become more complex or if you would
simply like a pattern to follow, it may still be worth considering.
=head1 CONCEPTS AND DEFINITIONS
See L<CONCEPTS AND DEFINITIONS|CLI::Framework/CONCEPTS AND DEFINITIONS>.
=head1 UNDERSTANDING THE APPLICATION RUN SEQUENCE
See L<APPLICATION RUN SEQUENCE|CLI::Framework/APPLICATION RUN SEQUENCE>.
B<Understanding this is important to building more complex apps>. You need,
at the least, to understand how CLIF differentiates between options and
arguments that are meant for the application itself and those options and
arguments that are meant for individual commands.
The following examples demonstrate the alternative command request forms.
Note that in all cases, any number of (sub)command options and arguments can
be passed (these examples show only one of each for brevity).
FORM #1 (without subcommands) -- command requests that involve NO subcommands
take the following form:
<app> [--app-opt] <cmd> [--cmd-opt] [cmd-arg] ...
(notice how the position of options and arguments determines whether they are
meant for the application as a whole or for the specific command).
FORM #2 (with subcommands) -- Command requests that involve A SINGLE
subcommand take this form:
<app> [--app-opt] <cmd> [--cmd-opt] <subcmd> [--subcmd-opt] [subcmd-arg] ...
Command requests that involve MULTIPLE subcommands follow the same form:
<app> [--app-opt] <cmd> [--cmd-opt] <subcmd1> [--subcmd1-opt] <subcmd2> [--subcmd2-opt] [subcmd2-arg] ...
(notice that the final arguments apply to the final subcommand. The only
command that can receive arguments is the final subcommand).
=head1 A MORE INVOLVED EXAMPLE
Please see working code for this example included with the C<CLI::Framework>
distribution (F<examples/queue>).
The next example demonstrates the following features:
=over
=item *
inline application definition
=item *
basics (app, commands, command options and args)
=item *
subcommands
=item *
validation of application and command arguments
=item *
interactive mode and non-interactive mode
=back
Suppose we need to write a command-line application that provides an interface to a
queue. Strings can be added to or removed from the queue, queue contents can be
displayed, and queue "properties" can be set to restrict the contents added to
the queue. The interface should work interactively.
The following usage demonstrates the desired behavior:
[somebody@somewhere]$ examples/queue --qin=/tmp/qfile --qout=/tmp/qfile console
# ---- interactive mode ----
1) dequeue
2) cmd-list
3) enqueue
4) print
5) alias
6) property
> help enqueue
enqueue [--tag=<tag1> [--tag=<tag2> [...] ] ] <item1> [<item2> ... <itemN>]: add item(s) to queue
> enqueue --tag=x "something"
> property set --evens
> e 1 21 514 937 18
The working example in F<examples/queue> accomplishes this goal in a
single inline application containing the Application class and multiple
Command Classes.
This application is created in fundamentally the same way as the simple one
presented earlier. It uses more commands, more Application class/Command
Class hooks, and subcommands. The code is much longer but almost all of
it is for business logic -- very little additional CLIF-specific code is needed.
The example code shows how various commands can be managed by an Application
subclass. The code is commented thoroughly to explain the various hooks that
are available for Application class and Command Classes.
Of course, CLIF applications can always be used in non-interactive mode:
# ---- non-interactive mode ----
$ examples/queue --qout=/tmp/qfile enqueue 'first'
$ examples/queue --qin=/tmp/qfile --qout=/tmp/qfile enqueue --tag=x --tag=y 'second'
$ examples/queue --qin=/tmp/qfile --qout=/tmp/qfile property list
$ examples/queue --qin=/tmp/qfile --qout=/tmp/qfile property set --evens
$ examples/queue --qin=/tmp/qfile --qout=/tmp/qfile property list
$ examples/queue --qin=/tmp/qfile --qout=/tmp/qfile enqueue 17
$ examples/queue --qin=/tmp/qfile --qout=/tmp/qfile enqueue 4
$ examples/queue --qin=/tmp/qfile --qout=/tmp/qfile enqueue 2
$ examples/queue --qin=/tmp/qfile --qout=/tmp/qfile dequeue
$ examples/queue --qin=/tmp/qfile --qout=/tmp/qfile dequeue
$ examples/queue --qin=/tmp/qfile --qout=/tmp/qfile dequeue
$ examples/queue --qin=/tmp/qfile --qout=/tmp/qfile enqueue 3
$ examples/queue --qin=/tmp/qfile print
=head1 PLANNING A COMPLEX CLIF APPLICATION
Little additional thought (beyond that needed for business logic) is required
to create a basic CLIF app -- the strategy explained in
L<FROM P.O.S. TO CLIF IN A FEW EASY STEPS> demonstrates how CLIF differs from a
"Plain Old Script".
A more sophisticated command line application will benefit from a wider variety
of the features CLIF provides. The extra features are easy to use, but the
additional complexity warrants careful planning.
After the initial learning curve, applying interface design principles and
implementing business rules will become the only challenging aspects to
developing your CLIF applications. This is as it should be -- the framework
handles application-independent aspects, leaving you to focus on the unique
features of your application.
Here are some considerations:
=over
=item Basic interface
=over
=item What commands and subcommands should be available?
=item What options and arguments will they support?
=item What kind of validation should be done on the provided command requests?
=item Which built-in commands will be used?
=item Will an interactive mode be provided?
=item If so, will a custom menu be created?
=item Do any commands need to directly access or modify the application itself or the other commands (these will be metacommands)?
=back
=item High-level code layout
Which components of the application will be defined in their own package
files? Which will be defined inline?
=item Separation of concerns using MVC strategy
How will the model be separated from the rest of the application? What about
the view?
=item Data sharing between application and commands
What data will data be shared between the application and the commands? Will
this be arranged by using the cache, using a Command superclass (a generic
command class that all of your commands inherit from), or by some other means?
=back
Read on for possible answers to some of these questions.
=head1 HOW CAN I ...?
This section briefly highlights how CLIF could be used to support various common
goals. Even if your particular situation does not appear here, reading this short
section will give you an understanding of how CLIF could be set up to support novel
cases.
=head2 How can I quickly create a very simple application?
For a demonstration of how to create a very simple CLIF app, see
L<FROM P.O.S. TO CLIF IN A FEW EASY STEPS>. CLIF applications require, at
the minimum:
=over
=item *
An Application class that inherits from L<CLI::Framework::Application> (or
C<CLI::Framework>). For anything useful to happen, it should override the
C<command_map()> hook and include a new command.
=item *
A Command Class that inherits from L<CLI::Framework::Command>. It should
override the C<run()> hook (or have a subcommand that overrides C<run()>).
=item *
An Application Script that calls the C<run()> method in your application.
=back
These can all be defined in one file or each class can be placed in a separate
file. Do whatever works best for your particular needs.
=head2 How can I add an interactive mode to my application?
The built-in console command can be used to enable your application to run
interactively. To do this, simply add the built-in command
L<CLI::Framework::Command::Console> to the
L<command_map|CLI::Framework::Application/command_map()> in your Application
class.
=head2 How can I include logging in my application?
In your Application class, define C<init()> to initialize your logging
object and save the resulting object in the cache, where the object will be
available to your application and command objects.
=head2 How can I include database connectivity in my application?
In your Application class, define C<init()> to connect to your database and
save the resulting object or database handle in the cache, where the
object/handle will be available to your application and command objects.
Of course, for proper Separation of Concerns, you should not simply store a
connected database handle in the cache and use it directly in your Command
classes. You should instead store an object of another class that
encapsulates your data model layer code. An example of this is the model
class for the demo journal application included with CLIF tests:
F<t/lib/My/Journal/Model.pm>.
=head2 How can I support an application configuration file?
In your Application class, define C<init()> to load your configuration file
and save the resulting configuration object in the cache using the
L<cache|CLI::Framework::Application/cache()>, where the object will be
available to your application and command objects.
=head2 How can I use templates for more flexible output?
In your Application class, override the C<render()> method.
For instance, you could write an application where all commands return a data
structure to be used in processing a template. Your C<render()> method
could determine which template file to process (e.g. based on which command is
being run) and then process it using the received data structure.
=head2 How can I create an application-aware command?
In exceptional cases, you may need to create a command that "knows about" the
application and needs access to some of its data (which may include the data
of other commands in the application).
To create an application-aware command, inherit from
L<CLI::Framework::Command::Meta>. The command will then have an accessor that
will provide access to the application object.
You should generally not need to do this -- your commands should usually be
decoupled from your application. This will occur by default when you inherit
from L<CLI::Framework::Command>.
=head2 How can I use alternative CLI prompting techniques and terminal I/O convenience functions?
You may, for example, want to present a menu of options from a variety of
choices based on content from a database. Or perhaps you want to prompt the
user for a list of numbers and you want to support a comma-separated list with
ranges, etc.
Create a CLI::Framework::Command subclass (say, C<Your::Command>) that
implements your convenience functions or uses a CPAN module such as
L<Term::Prompt>. Then all of your commands can inherit from C<Your::Command>
and will all have access to the functions.
You may also want to override
L<read_cmd|CLI::Framework::Application/read_cmd()>.
=head2 How can I create an app without a "help" command?
The 'help' command is fundamental to most applications. If you really want to
build an application without a 'help' command, simply create a custom Help
command with an empty C<run> method.
=head2 How can I dynamically determine whether or not to run interactively based on command-line options?
You may wish to provide an application option (C<--interactive>) to start
interactive mode. One way to do this is to use your application's C<init>
method to determine whether or not to invoke the built-in console command.
For example:
sub init {
my ($app, $opts) = @_;
# imagine fancy logic to determine whether or not to run interactively...
if( $opts->{interactive} ) {
$app->set_current_command('console');
}
return 1;
}
This will cause the interactive console to be launched during initialization.
This technique could be used to launch the built-in console command or a
custom interactive command.
This was considered in greater detail on the discussion forum:
L<http://cpanforum.com/posts/12426>.
=head1 TROUBLESHOOTING
The following solutions may be helpful when working with CLIF.
=over
=item *
Don't forget to inherit from CLI::Framework::Application in your Application class and CLI::Framework::Command in your command class
=item *
Don't forget to override command_map() in your Application class
=item *
Don't forget to override run() in your Command class
=item *
If in doubt, run "perl -wc <your command class file>"
If a user-defined command class does not compile, your CLIF application will
fail silently. Running C<perl -wc Class.pm> will report compilation problems
for F<Class.pm>.
=back
=head1 LICENSE AND COPYRIGHT
Copyright (c) 2009 Karl Erisman (kerisman@cpan.org). All rights reserved.
This is free software; you can redistribute it and/or modify it under the same
terms as Perl itself. See perlartistic.
=head1 AUTHOR
Karl Erisman (kerisman@cpan.org)
=cut
|