#============================================================================ # # Module: Term::CLI::Tutorial # # Author: Steven Bakker (SBAKKER), # Created: 08/Feb/18 # # Copyright (c) 2018 Steven Bakker # # This module is free software; you can redistribute it and/or modify # it under the same terms as Perl itself. See "perldoc perlartistic." # # This software is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # #============================================================================ =head1 NAME Term::CLI::Tutorial - tips, tricks, and examples for Term::CLI =head1 VERSION version 0.053004 =head1 SYNOPSIS use Term::CLI; =head1 DESCRIPTION This manual shows how to use L to build a working CLI application with command-line editing capabilities, command history, command completion, and more. For an introduction in the object class structure, see L(3p). =head2 Note on Term::ReadLine Although L has been created to work with L in general, it works best when either L(3p) is present; failing that, L(3p) will also suffice, although there are some subtle differences (especially in signal handling). If neither is present on your system, L will load a "stub" interface (C), which supports neither command line editing nor completion. =head1 INTRODUCTION If you have ever found yourself needing to write a command-line (shell-like) interface to your program, then L may be for you. L provides a readline-based command line interface, including history, completion and input verification. The most notable features are: =over =item * syntax checking, including option parsing =item * command, filename, and parameter completion =item * command and parameter abbreviation =item * command callbacks =back Input syntax is specified by combining L and L objects, together with L-like option specifications, and providing L functions for command execution. In the following sections, we will embark on the journey to building a simple shell with a few basic commands, but one that looks quite polished. The F directory in the module's source tree has source code for all examples (F, F, etc.), that progressively build the final application. =head1 THE BSSH CONCEPT The Basically Simple SHell (BS Shell), is a command-line interpreter with a few simple commands: =over =item B I ... I Copy I to I. =item B [ I ... ] Print arguments to F and terminate with a newline. =item B [ I ] Exit with code I (0 if not given). =item B [ I ... ] See L(1). =item B {B|B} {B} A silly command for illustration purposes. =item B I Sleep for I seconds. =item B {B|B|B} Show some system information. =item B B I Set program verbosity. =item B B I Set word delimiter(s). =item B {B|B} B {B|B} Do something during another activity. =item B I {B|B} Turn interface I up or down. =back That's it. Now, let's start building something. =head1 THE REPL The basic design of an interactive interface follows the well-established REPL (Read, Evaluate, Print, Loop) principle: LOOP input = read_a_line output = evaluate_line( input ) print_result( output ) END-LOOP L provides a framework to make this happen: use 5.014_001; use warnings; use Term::CLI; my $term = Term::CLI->new( name => 'bssh', # A basically simple shell. ); say "\n[Welcome to BSSH]"; while (defined (my $line = $term->readline)) { $term->execute($line); } say "\n-- exit"; exit 0; This example is pretty much non-functional, since the L object is not aware of any command syntax yet: everything you type will result in an error, even empty lines and comments (i.e. lines starting with C<#> as the first non-blank character). bash$ perl tutorial/example_01_basic_repl.pl [Welcome to BSSH] ~> ERROR: missing command ~> # This is a comment! ERROR: unknown command '#' ~> exit ERROR: unknown command 'exit' ~> ^D -- exit =head2 Ignoring input patterns Let's first make sure that empty lines and comments are ignored. We I add a line to the C loop: while (my $line = $term->readline) { next if /^\s*(?:#.*)?$/; # Skip comments and empty lines. $term->execute($line); } But it's actually nicer to let L handle this for us: my $term = Term::CLI->new( name => 'bssh', # A basically simple shell. skip => qr/^\s*(?:#.*)?$/, # Skip comments and empty lines. ); Now we get: bash$ perl tutorial/example_02_ignore_blank.pl [Welcome to BSSH] ~> ~> # This is a comment! ~> exit ERROR: unknown command 'exit' ~> ^D -- exit =head2 Setting the prompt The default prompt for L is C<~E>. To change this, we can call the L method, or just specify it as an argument to the constructor: my $term = Term::CLI->new( name => 'bssh', # A basically simple shell. skip => qr/^\s*(?:#.*)?$/, # Skip comments and empty lines. prompt => 'bssh> ', # A more descriptive prompt. ); This gives us: bash$ perl tutorial/example_03_setting_prompt.pl [Welcome to BSSH] bssh> bssh> # This is a comment! bssh> exit ERROR: unknown command 'exit' bssh> ^D -- exit =head1 ADDING COMMANDS Adding a command to a L object is a matter of creating an array of L instances and passing it to the L's C method. my $term = Term::CLI->new( name => 'bssh', # A basically simple shell. skip => qr/^\s*(?:#.*)?$/, # Skip comments and empty lines. prompt => 'bssh> ', # A more descriptive prompt. ); $term->add_command( Term::CLI::Command->new( ... ), ... ); It is also possible to build the C list inside the constructor call: my $term = Term::CLI->new( ... commands => [ Term::CLI::Command->new( ... ), ... ] ); However, the code quickly becomes unwieldy when a large number of commands and options are added. You can also build a list first, and then call C: my $term = Term::CLI->new( ... ); my @commands; push @commands, Term::CLI::Command->new( ... ); ... $term->add_command(@commands); This is the method we'll use for this tutorial, and it coincidentally comes in handy further down the line. So, now that we have the basic mechanism out of the way, let's add our first command, the highly useful C. =head2 The C command (optional argument) X From L section above: =over B [ I ] =back This illustrates the use of a single, optional argument. Here's the code: push @commands, Term::CLI::Command->new( name => 'exit', callback => sub { my ($cmd, %args) = @_; return %args if $args{status} < 0; execute_exit($cmd, @{$args{arguments}}); return %args; }, arguments => [ Term::CLI::Argument::Number::Int->new( # Integer name => 'excode', min => 0, # non-negative inclusive => 1, # "0" is allowed min_occur => 0, # occurrence is optional max_occur => 1, # no more than once ), ], ); Let's unpack that, shall we? The L takes three attributes: =over =item B> 'exit' The name of the command. This is a mandatory attribute. =item B \&execute_exit> The function to call when the command is executed. =item B> [ ... ] A list of arguments that the command takes. =back =head3 The C function The callback function is called when the command is executed. callback => sub { my ($cmd, %args) = @_; return %args if $args{status} < 0; execute_exit($cmd, @{$args{arguments}}); return %args; }, In this case, we also have to define C: sub execute_exit { my ($cmd, $excode) = @_; $excode //= 0; say "-- exit: $excode"; exit $excode; } The callback function (see L) is called with a reference to the command object that owns the callback, along with a number of (I, I) pairs. It is expected to return a similar structure (while possibly modifying the C and/or C values). Since the callback function is called even in the face of parse errors, it is important to check the C flag. A negative value indicates a parse error, so we don't do anything in that case (the L default callback will print the error for us). The command arguments are found under the C key, as an ArrayRef of scalars. The exit code is the only (optional) argument, so that is found as the first element of the list: C<< $args{arguments}->[0] >>. If it is not given, we default to C<0>. =head3 The C list The C attribute is an ArrayRef made up of L instances, or more precisely, object classes derived from that. At this moment, we have a number of pre-defined sub-classes: L, L, L. L, L, L. In our case, we need an optional, non-negative integer, so: Term::CLI::Argument::Number::Int->new( # Integer name => 'excode', min => 0, # non-negative inclusive => 1, # "0" is allowed min_occur => 0, # occurrence is optional max_occur => 1, # no more than once ), The C and C can be left out in this case, as their defaults are C<1> anyway. =head3 Trying out the C command bash$ perl tutorial/example_04.pl [Welcome to BSSH] bssh> exit ok ERROR: arg#1, 'ok': not a valid number for excode bssh> exit 0 1 ERROR: arg#1, excode: too many arguments bssh> exit 2 -- exit: 2 Note that command abbreviation also works, i.e. you can type: e ex exi exit =head1 GETTING HELP Before adding more commands to our application, it's perhaps a good moment to look at the built-in help features of L. By default, there is no help available in a L application: bssh> help ERROR: unknown command 'help' However, there is a special L class (derived from L) that implements a C command, including command line completion: push @commands, Term::CLI::Command::Help->new(); If you add this to the application, you'll get: bash$ perl tutorial/example_05_add_help.pl [Welcome to BSSH] bssh> help Commands: exit [excode] help [cmd ...] show help bssh> help exit Usage: exit [excode] bssh> help h Usage: help [--pod] [--all] [-pa] [cmd ...] Description: Show help for any given command sequence (or a command overview if no argument is given. The "--pod" ("-p") option will cause raw POD to be shown. The "--all" ("-a") option will list help text for all commands. Note that we don't have to specify the full command to get help on: command abbreviation works here as well (C). Also, if you'd type C, then hit the I key, it would autocomplete to C. The C<--pod> option is handy if you want to copy the help text into a manual page: bssh> help --pod help =head2 Usage: B [B<--pod>] [B<--all>] [B<-pa>] [I ...] =head2 Description: Show help for any given command sequence (or a command overview if no argument is given. The C<--pod> (C<-p>) option will cause raw POD to be shown. The C<--all> (C<-a>) option will list help text for all commands. =head2 Fleshing out help text As you may have already seen, the help text for the C command is rather sparse (unlike that of the C command itself): it only shows a "usage" line. The L class is smart enough to construct a usage line from the given command (including its options, parameters and sub-commands), but it cannot magically describe what a command is all about. You'll have to specify that yourself, using the C and C attributes in the C command definition: push @commands, Term::CLI::Command->new( name => 'exit', summary => 'exit B', description => "Exit B with code I,\n" ."or C<0> if no exit code is given.", callback => sub { my ($cmd, %args) = @_; return %args if $args{status} < 0; execute_exit($cmd, @{$args{arguments}}); return %args; }, arguments => [ Term::CLI::Argument::Number::Int->new( # Integer name => 'excode', min => 0, # non-negative inclusive => 1, # "0" is allowed min_occur => 0, # occurrence is optional max_occur => 1, # no more than once ), ], ); The C text is what is displayed in the command summary, the C text is shown in the full help for the command: bash $perl tutorial/example_06_add_help_text.pl [Welcome to BSSH] bssh> help Commands: exit [excode] exit bssh help [cmd ...] show help bssh> help exit Usage: exit [excode] Description: Exit bssh with code excode, or 0 if no exit code is given. The help text is in POD format, translated for the screen using L(3p), and piped through an appropriate pager (see L for more details). =head1 ADDING MORE COMMANDS The following examples will show various types and combination of arguments: =over =item * The C command takes zero or more arbitrary string arguments (L). =item * The C command takes two string arguments, each from a set of pre-defined values. (L). =item * The C command demonstrates the use of file name arguments (L). =item * The C command demonstrates how to set up a variable number of arguments (L). =item * The C command demonstrates a numerical argument (L). =back =head2 The C command (optional arguments) Next up, the C command. From L section above: =over B [ I ... ] =back That is, the C command takes zero or more arbitrary string arguments. The implementation is straightforward: push @commands, Term::CLI::Command->new( name => 'echo', summary => 'print arguments to F', description => "The C command prints its arguments\n" . "to F, separated by spaces, and\n" . "terminated by a newline.\n", arguments => [ Term::CLI::Argument::String->new( name => 'arg', occur => 0 ), ], callback => sub { my ($cmd, %args) = @_; return %args if $args{status} < 0; say "@{$args{arguments}}"; return %args; } ); However, the C and C commands both start with the same prefix (C), so let's see what happens with the abbreviations: bash$ perl tutorial/example_07_echo_command.pl [Welcome to BSSH] bssh> e hello, world ERROR: ambiguous command 'e' (matches: echo, exit) bssh> ec hello, world hello, world bssh> ex -- exit: 0 =head2 The C command (enum arguments) From L section above: =over B {B|B} {B} =back Arguments with fixed set of values can be specified with L objects: push @commands, Term::CLI::Command->new( name => 'make', summary => 'make I at time I', description => "Make I at time I.\n" . "Possible values for I are:\n" . "C, C.\n" . "Possible values for I are:\n" . "C, C, C, or C.", arguments => [ Term::CLI::Argument::Enum->new( name => 'target', value_list => [qw( love money )], ), Term::CLI::Argument::Enum->new( name => 'when', value_list => [qw( now later never forever )], ), ], callback => sub { my ($cmd, %args) = @_; return %args if $args{status} < 0; my @args = @{$args{arguments}}; say "making $args[0] $args[1]"; return %args; } ); The "enum" parameters support completion, as well as abbreviations. Thus, C will expand to C, and C will fail because C is ambiguous: bash$ perl tutorial/example_08_make_command.pl [Welcome to BSSH] bssh> m m l making money later bssh> m l n ERROR: arg#2, 'n': ambiguous value (matches: never, now) for 'when' =head3 Command and parameter completion m make m l m love m l l m l later m l n m l n m l n (displays "never" and "now" as completions) =head2 The C command (file name arguments) The C command takes zero or more file name arguments. From L section above: =over B [ I ... ] =back The code for this: push @commands, Term::CLI::Command->new( name => 'ls', summary => 'list file(s)', description => "List file(s) given by the arguments.\n" . "If no arguments are given, the command\n" . "will list the current directory.", arguments => [ Term::CLI::Argument::Filename->new( name => 'arg', occur => 0 ), ], callback => sub { my ($cmd, %args) = @_; return %args if $args{status} < 0; my @args = @{$args{arguments}}; system('ls', @args); $args{status} = $?; return %args; } ); Output should look like: bash$ perl tutorial/example_09_ls_command.pl [Welcome to BSSH] bssh> ls blib lib MANIFEST t Copying Makefile MYMETA.json Term-CLI-0.01.tar.gz cover_db Makefile.old MYMETA.yml TODO examples Makefile.PL pm_to_blib tutorial bssh> _ Options are passed directly to the L(1) command. This is because we didn't specify any options in the command definition, so everything is assumed to be an argument, and the L class is not particularly picky about the arguments it gets, juost so long as they are not empty: bssh> ls -F lib/Term CLI/ CLI.pm bssh> _ =head3 File name completion ls t (lists "t/" and "tutorial/" as completions) ls tu ls tutorial ls tutorial e ls tutorial examples =head2 The C command (variable number of arguments) From L section above: =over B I ... I =back Ideally, we would like to specify this as: Term::CLI::Command->new( name => 'cp', arguments => [ Term::CLI::Argument::Filename->new( name => 'src-path', min_occur => 1, max_occur => 0 ), Term::CLI::Argument::Filename->new( name => 'dst-path', min_occur => 1, max_occur => 1 ), ], ... ) Unfortunately, that will not work. L can work with a variable number of arguments, but only if that variable number is at I. To see why this is the case, it is important to realise that L parses an input line strictly from left to right, without any backtracking (which proper recursive descent parsers typically do). So, suppose you enter C<< cp foo bar >>. The completion code now has to decide what this C is that needs to be completed. Since the first argument to C can be one or more file names, this C can be a I, but it can also be meant to be a I. There is no way to tell for certain, so the code will be "greedy", in the sense that it will classify all arguments as I arguments. There's no way around this, except by using options, but that's a separate topic. For now, there's no other way than to specify a single L, with a minimum occurrence of 2, and no maximum. The distinction between I and I needs to be made in the callback code. push @commands, Term::CLI::Command->new( name => 'cp', summary => 'copy files', description => "Copy files. The last argument in the\n" . "list is the destination.\n", arguments => [ Term::CLI::Argument::Filename->new( name => 'path', min_occur => 2, max_occur => 0 ), ], callback => sub { my ($cmd, %args) = @_; return %args if $args{status} < 0; my @src = @{$args{arguments}}; my $dst = pop @src; say "command: ".$cmd->name; say "source: ".join(', ', @src); say "destination: ".$dst; return %args; } ); Example: bash$ perl tutorial/example_10_cp_command.pl [Welcome to BSSH] bssh> cp ERROR: need at least 2 'path' arguments bssh> cp foo bar baz command: cp source: foo, bar destination: baz bssh> cp -r foo command: cp source: -r destination: foo bssh> ^D -- exit: 0 Note that this setup does not recognise options, so all options will be passed as regular arguments. =head2 The C command (single integer argument) From L section above: =over S I =back This is an almost trivial implementation: push @commands, Term::CLI::Command->new( name => 'sleep', summary => 'sleep for I