Perinci::CmdLine::Manual::Examples - Collection of examples
This document describes version 0.27 of Perinci::CmdLine::Manual::Examples (from Perl distribution Perinci-CmdLine-Lite), released on 2014-09-03.
In the examples, Perinci::CmdLine::Any is used to show examples that are applicable to either Perinci::CmdLine or Perinci::CmdLine::Lite. For examples that are more appropriate or only applicable to specific implementation, the specific module will be used.
Perinci::CmdLine is hereby referred to as P::C, while Perinci::CmdLine as P::C::Lite.
P::C
P::C::Lite
Since Perinci::CmdLine is function- and metadata-based, you need to create at least one function and add some metadata for it. And you'll need to return the result as an enveloped response. The simplest is something like:
#!perl use strict; use warnings; use Perinci::CmdLine::Any; our %SPEC; $SPEC{hello} = { v => 1.1, summary => 'Say hello', }; sub hello { [200, "OK", "Hello, world!"]; } Perinci::CmdLine::Any->new(url => '/main/hello')->run;
The url attribute specifies the location of the function in URL format (see Riap for more details on the syntax of URL). It is basically a fully-qualified function name, with :: replaced with /. With this URL-based syntax, it is possible to use a remote and/or non-Perl function for the CLI application.
url
::
/
The hash in $SPEC{hello} is called a Rinci metadata. The keys are called properties. There are two properties: v (which is always required with the value of 1.1 to specify version) and summary (which is actually optional, to describe the function).
$SPEC{hello}
v
summary
In this example, the function and its metadata is put inside the same script. You can of course put them in a separate Perl module, and use them with e.g. url => '/Your/Module/func'. It is also worth mentioning that if you use the P::C framework, your functions can also be used directly by other Perl modules/code since they are just regular Perl functions.
url => '/Your/Module/func'
The function returns a 3-element array containing HTTP-like status code, a status message, and the actual result.
If you save the above script as hello run it on the command-line:
hello
% ./hello Hello, world!
Yup, not very interesting. You get help message for free:
% ./hello --help % ./hello -h
As well as some common options like --format to return the result in a different format:
--format
% ./hello --json [200,"OK","Hello, world!"] % ./hello --format perl; # only in P::C, not available in P::C::Lite [200, "OK", "Hello, world!"]
Function arguments map to command-line options. Example:
#!perl use strict; use warnings; use Perinci::CmdLine::Any; our %SPEC; $SPEC{hello} = { v => 1.1, summary => 'Say hello', args => { name => { summary => 'Name to say hello to', }, }, }; sub hello { my %args = @_; [200, "OK", "Hello, $args{name}!"]; } Perinci::CmdLine::Any->new(url => '/main/hello')->run;
When you run this:
% ./hello --name Jimmy Hello, Jimmy!
If you run ./hello --help, the option is now mentioned as well in the help message.
./hello --help
Unknown arguments will result in an error:
% ./hello --gender m ERROR 400: Unknown option '--gender'
To specify that an argument is required, add req property to the argument specification with a true value:
req
args => { name => { summary => 'Name to say hello to', req => 1, }, },
So when you run the app:
% ./hello ERROR 400: Missing required argument 'name'
To specify that an argument can also be specified via positional command-line argument instead of just command-line option, add pos property to the argument specification:
pos
args => { name => { summary => 'Name to say hello to', req => 1, pos => 0, }, },
So when you run the app you can specify:
as well as:
% ./hello Jimmy Hello, Jimmy!
Extra arguments will also result in an error:
% ./hello Jimmy Gideon ERROR 400: Extra argument 'Gideon'
Following up from the previous example, here's another example with more arguments. Also note that I use P::C since P::C::Lite doesn't do schema validation.
#!perl use 5.010; use strict; use warnings; use Perinci::CmdLine; our %SPEC; $SPEC{hello} = { v => 1.1, summary => 'Say hello', args => { name => { summary => 'Name(s) to say hello to', schema => [array => {of => 'str', min_len=>1}], req => 1, pos => 0, greedy => 1, }, gender => { summary => 'The gender of the name(s)', schema => [str => {in => ['m','f']}], }, }, }; sub hello { my %args = @_; my $g = $args{gender}; my @res; for my $name (@{ $args{name} // [] }) { push @res, join("", "Hello, ", (!$g ? "" : $g eq 'm' ? "Mr. " : "Mrs. "), $name, '!', ); } [200, "OK", \@res]; } Perinci::CmdLine->new(url => '/main/hello')->run;
If you run this program:
% ./hello Jimmy Sion Habil % ./hello --name Jimmy --name Sion --name Habil Hello, Jimmy! Hello, Sion! Hello, Habil! % ./hello --name-json '["Jimmy","Sion","Habil"]' --gender m Hello, Mr. Jimmy! Hello, Mr. Sion! Hello, Mr. Habil!
Some things you might notice. First, there is a schema property for each argument. name is specified as having a type of array of strings. To set this argument from the CLI, you can either specify multiple times (e.g. --name NAME1 --name NAME2 ...) or specify using JSON (i.e. --name-json JSONSTR).
schema
name
--name NAME1 --name NAME2 ...
--name-json JSONSTR
Second, the name argument specifies the greedy property. This is used in conjunction with the pos property. It declares that the argument will gobble up command-line arguments from pos to the end. So you can also specify the values of the name argument with ARG1 ARG2 ....
greedy
ARG1 ARG2 ...
Third, if you specify value that does not validate, an error will be produced.
% ./hello --name-json '[]' ERROR 400: Invalid value for argument 'name': Length must be at least 1 % ./hello --name Jimmy --name Sion --name Habil --gender x ERROR 400: Invalid value for argument 'gender': Must be one of ["m","f"]
See Data::Sah for more about the schema syntax.
Fourth, you return the result as a data structure (an array) instead of directly printing the result using print() or say(). This is done to make your function more reusable outside the context of CLI. P::C will format your data structure nicely using Data::Format::Pretty. Your array will be printed as a multicolumn ANSI table by default, on interactive mode. If you pipe the output of your program, you will by default get a simpler text output. This can be chosen explicitly using the --format common option.
print()
say()
% ./hello Jimmy Sion Habil --format text; # will output pretty or simple depending on whether interactive % ./hello Jimmy Sion Habil --format text-simple; # will still output simple table even when interactive % ./hello Jimmy Sion Habil --format text-pretty; # will still output pretty table even when piped
To add short options, you can use the cmdline_aliases property in the argument specification:
cmdline_aliases
name => { ... cmdline_aliases => { n => {} }, }, gender => { ... cmdline_aliases => { g => {} }, },
Now instead of:
% ./hello --name Jimmy --name Sion --name Habil --gender m
you can also use:
% ./hello -n Jimmy -n Sion -n Habil -g m
You are not limited to one alias, or one letter:
gender => { ... cmdline_aliases => { g => {}, sex => {} }, },
Now all these are equivalent:
% ./hello ... --gender m % ./hello ... -g m % ./hello ... --sex m
Suppose you want to create an alias -m to mean --gender m and -f to mean --gender f instead:
-m
--gender m
-f
--gender f
gender => { ... cmdline_aliases => { m => { schema=>'bool', code => sub {my $args=shift; $args->{gender} = 'm' } }, f => { schema=>'bool', code => sub {my $args=shift; $args->{gender} = 'f' } }, }, },
Now you can say:
% ./hello Jimmy Sion -m Hello, Mr. Jimmy! Hello, Mr. Sion! % ./hello Nunung Misye -f Hello, Mrs. Nunung! Hello, Mrs. Misye!
A default subcommand can be defined. This subcommand is selected without user specifying it the first command-line argument. A real-world example of this is from File::Trash::Undoable. The trash-u command is by default selecting the trash subcommand:
trash
% trash-u file1 file2
is equivalent to:
% trash-u --cmd trash file1 file2
To select another subcommand other than trash, an explicit option is needed:
% trash-u --list-contents ; # select the list_contents subcommand % trash-u --cmd empty ; # select the empty subcommand
This is done via something like:
Perinci::CmdLine->new( subcommands => { trash => { url=>... }, empty => { url=>... }, list_contents => { url=>... }, }, default_subcommand => 'trash', )->run;
There is also a choice to specify a default subcommand which is overrideable via first command-line argument. A real-world example of this is from App::GitUtils. If the gu command is specified without any argument:
% gu
then it is equivalent to:
% gu info
but user can specify other subcommands:
% gu post-commit
This is accomplished by setting:
Perinci::CmdLine::Any->new( subcommands => { info => {...}, run_hooks => {...}, post_commit => {...}, ... }, default_subcommand => 'info', get_subcommand_from_arg => 2, )->run;
In the function-centric world of Perinci::CmdLine, configuration is just another way to supply values to function arguments (before being potentially overriden by command-line arguments). Configuration files are written in IOD format, which is basically "INI with extra features". By default, configuration files are searched in /etc and then your home directory, with the name of program_name + .conf. So if you have:
/etc
.conf
# ~/prog.conf foo=1 bar=2
and:
# prog #!perl use Perinci::CmdLine::Any; $SPEC{prog} = { v => 1.1, args => { foo => {}, bar => {}, }, }; sub prog { my %args = @_; [200, "OK", "foo is $args{foo}, while bar is $args{bar}"]; } Perinci::CmdLine::Any->new(url=>'/main/prog')->run;
When you run:
% prog
you'll get:
foo is 1, while bar is 2
Multiple configuration files will be merged, so if you have:
# /etc/prog.conf foo=1 bar=2 # ~/prog.conf foo=10
foo is 10, while bar is 2
Configuration file can store more than one set of arguments, through specially named sections, called profiles:
# ~/prog.conf foo=1 bar=2 [profile=p1] foo=21 bar=22 [profile=p2] foo=31 bar=32
Running the program:
% prog foo is 1, while bar is 2 % prog --config-profile p1 foo is 21, while bar is 22 % prog --config-profile p2 foo is 31, while bar is 32
If you don't want to use any configuration files, you can use:
% prog --noconfig ...
Perinci::CmdLine::Manual
Please visit the project's homepage at https://metacpan.org/release/Perinci-CmdLine-Lite.
Source repository is at https://github.com/perlancar/perl-Perinci-CmdLine-Lite.
Please report any bugs or feature requests on the bugtracker website https://rt.cpan.org/Public/Dist/Display.html?Name=Perinci-CmdLine-Lite
When submitting a bug or request, please include a test-file or a patch to an existing test-file that illustrates the bug or desired feature.
perlancar <perlancar@cpan.org>
This software is copyright (c) 2014 by perlancar@cpan.org.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.
To install Perinci::CmdLine::Lite, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Perinci::CmdLine::Lite
CPAN shell
perl -MCPAN -e shell install Perinci::CmdLine::Lite
For more information on module installation, please visit the detailed CPAN module installation guide.