.\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.40) .\" .\" Standard preamble: .\" ======================================================================== .de Sp \" Vertical space (when we can't use .PP) .if t .sp .5v .if n .sp .. .de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .\" Set up some character translations and predefined strings. \*(-- will .\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left .\" double quote, and \*(R" will give a right double quote. \*(C+ will .\" give a nicer C++. Capital omega is used to do unbreakable dashes and .\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, .\" nothing in troff, for use with C<>. .tr \(*W- .ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' .ie n \{\ . ds -- \(*W- . ds PI pi . if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch . if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch . ds L" "" . ds R" "" . ds C` "" . ds C' "" 'br\} .el\{\ . ds -- \|\(em\| . ds PI \(*p . ds L" `` . ds R" '' . ds C` . ds C' 'br\} .\" .\" Escape single quotes in literal strings from groff's Unicode transform. .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" .\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. .\" .\" Avoid warning from groff about undefined register 'F'. .de IX .. .nr rF 0 .if \n(.g .if rF .nr rF 1 .if (\n(rF:(\n(.g==0)) \{\ . if \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. . if !\nF==2 \{\ . nr % 0 . nr F 2 . \} . \} .\} .rr rF .\" ======================================================================== .\" .IX Title "CLI::Framework::Tutorial 3pm" .TH CLI::Framework::Tutorial 3pm "2021-01-09" "perl v5.32.0" "User Contributed Perl Documentation" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l .nh .SH "NAME" CLI::Framework::Tutorial \- "HOWTO" develop CLIF applications using best practices .SH "CLIF DOCUMENTATION" .IX Header "CLIF DOCUMENTATION" This is a guide to developing \s-1CLIF\s0 applications. It is a supplement to the documentation in CLI::Framework, CLI::Framework::Application and CLI::Framework::Command, which have more thorough coverage of some finer points. .PP It is suggested that new users start by reading this document, then use the other documentation for reference as necessary. .SH "INTRODUCTION" .IX Header "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. .PP The CLI::Framework documentation enumerates many advantages to using \s-1CLIF\s0 instead of writing yet-another-getopt-based-script. \s-1CLIF\s0 comes with a lot of documentation, but don't take that to mean that using \s-1CLIF\s0 is complicated. \s-1CLIF\s0 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. .PP This document will first demonstrate a very simple \s-1CLIF\s0 application. Next, a complete application will be shown to demonstrate more advanced \s-1CLIF\s0 features. .PP 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. .PP 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 \*(L"on the same page\*(R" and new people learning your tools must have lengthy, verbal, one-on-one code tours. .PP Instead, a set of related scripts could be combined into a \s-1CLIF\s0 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. .SH "FROM P.O.S. TO CLIF IN A FEW EASY STEPS" .IX Header "FROM P.O.S. TO CLIF IN A FEW EASY STEPS" A \*(L"P.O.S.\*(R" is a \*(L"Plain Old Script.\*(R" This section shows you how to reform an old P.O.S., creating a shiny new \s-1CLIF\s0 application! .PP Please see working code for this example included with the \f(CW\*(C`CLI::Framework\*(C'\fR distribution (\fIexamples/demo\-simple.pl\fR). .PP This example demonstrates the following features: .IP "\(bu" 4 inline application definition .IP "\(bu" 4 basics (app, commands, command options and args) .IP "\(bu" 4 the relationship between plain scripts and \s-1CLIF\s0 applications (including how to convert between them) .PP To understand \s-1CLIF\s0 commands, imagine converting a legacy script to a \s-1CLIF\s0 application. First, create a Perl class that inherits from CLI::Framework::Command. Place the main body of the script in a \f(CW\*(C`run()\*(C'\fR method. Add the functions that the script defines, if any. .PP .Vb 3 \& # Your Command subclass... \& package Converted::Script::Command::LegacyScript; \& use base qw( CLI::Framework::Command ); \& \& # main body of former script goes inside run(): \& sub run { ... } .Ve .PP Next, create a Perl class (creating a separate package file for the class is totally optional) that inherits from CLI::Framework::Application (or you can use \f(CW\*(C`CLI::Framework\*(C'\fR as a shorthand) and define a method, \f(CW\*(C`command_map()\*(C'\fR, that links command names with classes that implement the commands: .PP .Vb 3 \& # Your Application class... \& package Converted::Script; \& use base qw( CLI::Framework ); \& \& sub command_map { \& \*(Aqlegacy\-script\*(Aq => \*(AqConverted::Script::Command::LegacyScript\*(Aq, \& } .Ve .PP The code that provides a friendly usage message (if the legacy script provided one) can be replaced by defining the \f(CW\*(C`usage_text\*(C'\fR method: .PP .Vb 5 \& sub usage_text { \& qq{ \& $0 [\-\-verbose|v] [\-\-help|h]: how to use this application... \& } \& } .Ve .PP 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 Getopt::Long::Descriptive): .PP .Vb 4 \& sub option_spec { \& [ \*(Aqhelp|h\*(Aq => \*(Aqshow help\*(Aq ], \& [ \*(Aqverbose|v\*(Aq => \*(Aqbe verbose\*(Aq ], \& } .Ve .PP \&...and that's all it takes to convert a simple script to a \s-1CLIF\s0 app. This contrived example demonstrates the mechanics, but let me point out a few advantages (see \&\s-1DESIGN GOALS AND FEATURES\s0 for the long list): .IP "Clear division of responsibilities" 4 .IX Item "Clear division of responsibilities" Using packages, subroutines, and separate files (if desired), \s-1CLIF\s0 apps follow established convention and provide a new pattern for creating tools. .IP "Easy to test" 4 .IX Item "Easy to test" Now that functional units of code are subroutines in packages, you can unit test each component independently. .IP "Easy to maintain" 4 .IX Item "Easy to maintain" Instead of puzzling over a several-thousand-line script, maintaining a \s-1CLIF\s0 application is like maintaining any other well-engineered application code. .IP "Easy to extend" 4 .IX Item "Easy to extend" Related tools frequently occur in groups. Instead of awkwardly forcing loosely-related behaviors into the same script, \s-1CLIF\s0 makes it easy to add additional commands in a modular way. .SH "WHEN \fBNOT\fP TO USE CLIF" .IX Header "WHEN NOT TO USE CLIF" \&\s-1CLIF\s0 could be used for the simplest of needs, but it may be overkill in very simple situations. .PP You may want to avoid \s-1CLIF\s0 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. .SH "CONCEPTS AND DEFINITIONS" .IX Header "CONCEPTS AND DEFINITIONS" See \s-1CONCEPTS AND DEFINITIONS\s0. .SH "UNDERSTANDING THE APPLICATION RUN SEQUENCE" .IX Header "UNDERSTANDING THE APPLICATION RUN SEQUENCE" See \s-1APPLICATION RUN SEQUENCE\s0. .PP \&\fBUnderstanding this is important to building more complex apps\fR. You need, at the least, to understand how \s-1CLIF\s0 differentiates between options and arguments that are meant for the application itself and those options and arguments that are meant for individual commands. .PP 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). .PP \&\s-1FORM\s0 #1 (without subcommands) \*(-- command requests that involve \s-1NO\s0 subcommands take the following form: .PP .Vb 1 \& [\-\-app\-opt] [\-\-cmd\-opt] [cmd\-arg] ... .Ve .PP (notice how the position of options and arguments determines whether they are meant for the application as a whole or for the specific command). .PP \&\s-1FORM\s0 #2 (with subcommands) \*(-- Command requests that involve A \s-1SINGLE\s0 subcommand take this form: .PP .Vb 1 \& [\-\-app\-opt] [\-\-cmd\-opt] [\-\-subcmd\-opt] [subcmd\-arg] ... .Ve .PP Command requests that involve \s-1MULTIPLE\s0 subcommands follow the same form: .PP .Vb 1 \& [\-\-app\-opt] [\-\-cmd\-opt] [\-\-subcmd1\-opt] [\-\-subcmd2\-opt] [subcmd2\-arg] ... .Ve .PP (notice that the final arguments apply to the final subcommand. The only command that can receive arguments is the final subcommand). .SH "A MORE INVOLVED EXAMPLE" .IX Header "A MORE INVOLVED EXAMPLE" Please see working code for this example included with the \f(CW\*(C`CLI::Framework\*(C'\fR distribution (\fIexamples/queue\fR). .PP The next example demonstrates the following features: .IP "\(bu" 4 inline application definition .IP "\(bu" 4 basics (app, commands, command options and args) .IP "\(bu" 4 subcommands .IP "\(bu" 4 validation of application and command arguments .IP "\(bu" 4 interactive mode and non-interactive mode .PP 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 \*(L"properties\*(R" can be set to restrict the contents added to the queue. The interface should work interactively. .PP The following usage demonstrates the desired behavior: .PP .Vb 1 \& [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= [\-\-tag= [...] ] ] [ ... ]: add item(s) to queue \& \& > enqueue \-\-tag=x "something" \& \& > property set \-\-evens \& \& > e 1 21 514 937 18 .Ve .PP The working example in \fIexamples/queue\fR accomplishes this goal in a single inline application containing the Application class and multiple Command Classes. .PP 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. .PP 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. .PP Of course, \s-1CLIF\s0 applications can always be used in non-interactive mode: .PP .Vb 10 \& # \-\-\-\- non\-interactive mode \-\-\-\- \& $ examples/queue \-\-qout=/tmp/qfile enqueue \*(Aqfirst\*(Aq \& $ examples/queue \-\-qin=/tmp/qfile \-\-qout=/tmp/qfile enqueue \-\-tag=x \-\-tag=y \*(Aqsecond\*(Aq \& $ 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 .Ve .SH "PLANNING A COMPLEX CLIF APPLICATION" .IX Header "PLANNING A COMPLEX CLIF APPLICATION" Little additional thought (beyond that needed for business logic) is required to create a basic \s-1CLIF\s0 app \*(-- the strategy explained in \&\*(L"\s-1FROM P.O.S. TO CLIF IN A FEW EASY STEPS\*(R"\s0 demonstrates how \s-1CLIF\s0 differs from a \&\*(L"Plain Old Script\*(R". .PP A more sophisticated command line application will benefit from a wider variety of the features \s-1CLIF\s0 provides. The extra features are easy to use, but the additional complexity warrants careful planning. .PP After the initial learning curve, applying interface design principles and implementing business rules will become the only challenging aspects to developing your \s-1CLIF\s0 applications. This is as it should be \*(-- the framework handles application-independent aspects, leaving you to focus on the unique features of your application. .PP Here are some considerations: .IP "Basic interface" 4 .IX Item "Basic interface" .RS 4 .PD 0 .IP "What commands and subcommands should be available?" 4 .IX Item "What commands and subcommands should be available?" .IP "What options and arguments will they support?" 4 .IX Item "What options and arguments will they support?" .IP "What kind of validation should be done on the provided command requests?" 4 .IX Item "What kind of validation should be done on the provided command requests?" .IP "Which built-in commands will be used?" 4 .IX Item "Which built-in commands will be used?" .IP "Will an interactive mode be provided?" 4 .IX Item "Will an interactive mode be provided?" .IP "If so, will a custom menu be created?" 4 .IX Item "If so, will a custom menu be created?" .IP "Do any commands need to directly access or modify the application itself or the other commands (these will be metacommands)?" 4 .IX Item "Do any commands need to directly access or modify the application itself or the other commands (these will be metacommands)?" .RE .RS 4 .RE .IP "High-level code layout" 4 .IX Item "High-level code layout" .PD Which components of the application will be defined in their own package files? Which will be defined inline? .IP "Separation of concerns using \s-1MVC\s0 strategy" 4 .IX Item "Separation of concerns using MVC strategy" How will the model be separated from the rest of the application? What about the view? .IP "Data sharing between application and commands" 4 .IX 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? .PP Read on for possible answers to some of these questions. .SH "HOW CAN I ...?" .IX Header "HOW CAN I ...?" This section briefly highlights how \s-1CLIF\s0 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 \s-1CLIF\s0 could be set up to support novel cases. .SS "How can I quickly create a very simple application?" .IX Subsection "How can I quickly create a very simple application?" For a demonstration of how to create a very simple \s-1CLIF\s0 app, see \&\*(L"\s-1FROM P.O.S. TO CLIF IN A FEW EASY STEPS\*(R"\s0. \s-1CLIF\s0 applications require, at the minimum: .IP "\(bu" 4 An Application class that inherits from CLI::Framework::Application (or \&\f(CW\*(C`CLI::Framework\*(C'\fR). For anything useful to happen, it should override the \&\f(CW\*(C`command_map()\*(C'\fR hook and include a new command. .IP "\(bu" 4 A Command Class that inherits from CLI::Framework::Command. It should override the \f(CW\*(C`run()\*(C'\fR hook (or have a subcommand that overrides \f(CW\*(C`run()\*(C'\fR). .IP "\(bu" 4 An Application Script that calls the \f(CW\*(C`run()\*(C'\fR method in your application. .PP 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. .SS "How can I add an interactive mode to my application?" .IX Subsection "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 CLI::Framework::Command::Console to the command_map in your Application class. .SS "How can I include logging in my application?" .IX Subsection "How can I include logging in my application?" In your Application class, define \f(CW\*(C`init()\*(C'\fR 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. .SS "How can I include database connectivity in my application?" .IX Subsection "How can I include database connectivity in my application?" In your Application class, define \f(CW\*(C`init()\*(C'\fR 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. .PP 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 \s-1CLIF\s0 tests: \&\fIt/lib/My/Journal/Model.pm\fR. .SS "How can I support an application configuration file?" .IX Subsection "How can I support an application configuration file?" In your Application class, define \f(CW\*(C`init()\*(C'\fR to load your configuration file and save the resulting configuration object in the cache using the cache, where the object will be available to your application and command objects. .SS "How can I use templates for more flexible output?" .IX Subsection "How can I use templates for more flexible output?" In your Application class, override the \f(CW\*(C`render()\*(C'\fR method. .PP For instance, you could write an application where all commands return a data structure to be used in processing a template. Your \f(CW\*(C`render()\*(C'\fR 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. .SS "How can I create an application-aware command?" .IX Subsection "How can I create an application-aware command?" In exceptional cases, you may need to create a command that \*(L"knows about\*(R" the application and needs access to some of its data (which may include the data of other commands in the application). .PP To create an application-aware command, inherit from CLI::Framework::Command::Meta. The command will then have an accessor that will provide access to the application object. .PP 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 CLI::Framework::Command. .SS "How can I use alternative \s-1CLI\s0 prompting techniques and terminal I/O convenience functions?" .IX Subsection "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. .PP Create a CLI::Framework::Command subclass (say, \f(CW\*(C`Your::Command\*(C'\fR) that implements your convenience functions or uses a \s-1CPAN\s0 module such as Term::Prompt. Then all of your commands can inherit from \f(CW\*(C`Your::Command\*(C'\fR and will all have access to the functions. .PP You may also want to override read_cmd. .ie n .SS "How can I create an app without a ""help"" command?" .el .SS "How can I create an app without a ``help'' command?" .IX Subsection "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 \f(CW\*(C`run\*(C'\fR method. .SS "How can I dynamically determine whether or not to run interactively based on command-line options?" .IX Subsection "How can I dynamically determine whether or not to run interactively based on command-line options?" You may wish to provide an application option (\f(CW\*(C`\-\-interactive\*(C'\fR) to start interactive mode. One way to do this is to use your application's \f(CW\*(C`init\*(C'\fR method to determine whether or not to invoke the built-in console command. For example: .PP .Vb 8 \& sub init { \& my ($app, $opts) = @_; \& # imagine fancy logic to determine whether or not to run interactively... \& if( $opts\->{interactive} ) { \& $app\->set_current_command(\*(Aqconsole\*(Aq); \& } \& return 1; \& } .Ve .PP 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. .PP This was considered in greater detail on the discussion forum: . .SH "TROUBLESHOOTING" .IX Header "TROUBLESHOOTING" The following solutions may be helpful when working with \s-1CLIF.\s0 .IP "\(bu" 4 Don't forget to inherit from CLI::Framework::Application in your Application class and CLI::Framework::Command in your command class .IP "\(bu" 4 Don't forget to override \fBcommand_map()\fR in your Application class .IP "\(bu" 4 Don't forget to override \fBrun()\fR in your Command class .IP "\(bu" 4 If in doubt, run \*(L"perl \-wc \*(R" .Sp If a user-defined command class does not compile, your \s-1CLIF\s0 application will fail silently. Running \f(CW\*(C`perl \-wc Class.pm\*(C'\fR will report compilation problems for \fIClass.pm\fR. .SH "LICENSE AND COPYRIGHT" .IX Header "LICENSE AND COPYRIGHT" Copyright (c) 2009 Karl Erisman (kerisman@cpan.org). All rights reserved. .PP This is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See perlartistic. .SH "AUTHOR" .IX Header "AUTHOR" Karl Erisman (kerisman@cpan.org)