Permalink
Browse files

First stab at a separate CLI information document

- take the part about MAIN / USAGE from functions.pod6
- take some time to introduce features and simplify examples
  There is no need showing off all sorts of Signature tricks here
- document is hidden-from-USAGE

TODO: the new underlying MAIN implementation, and possibly also document
the old one for reference.  Also remove / alter documentation about MAIN
/USAGE in functions.
  • Loading branch information...
lizmat committed Oct 12, 2018
1 parent 39aed8d commit f540dff1f55c5ff1396ffd22a84d888af14107af
Showing with 283 additions and 0 deletions.
  1. +283 −0 doc/Language/create-cli.pod6
@@ -0,0 +1,283 @@
=begin pod :tag<perl6>
=TITLE Command Line Interface
=SUBTITLE Creating your own CLI in Perl 6
X<|command line arguments>
=head1 Command Line Interface - an overview
The default command line interface of Perl 6 scripts consists of 3 parts:
=item parsing the command line parameters into a L<capture|/type/Capture>
This looks at the values in L<@*ARGS|/language/variables#index-entry-@*ARGS>,
interpretes these according to some policy, and creates a C<Capture> object
out of that. An alternative way of parsing may be provided by the developer
or installed using a module.
=item calling a provided MAIN subroutine using that capture
Standard L<multi dispatch|/language/functions#index-entry-declarator_multi-Multi-dispatch>
is used to call the MAIN subroutine with the generated C<Capture> object.
This means that your MAIN subroutine may be a C<multi sub>, each candidate
of which responsible for some part of processing the given command line
arguments.
=item creating / showing USAGE information calling MAIN failed
If multi dispatch failed, then the user of the script should be informed as
well as possible as to why it failed. By default, this is done by inspecting
the signature of each MAIN candidate sub, and any associated pod information.
The result is then shown to the user on STDERR (or in STDOUT if C<--help>
was specified). An alternative way of generating the usage information may
be provided by the developer or installed using a module.
X<|MAIN>
=head1 sub MAIN
The sub with the special name C<MAIN> will be executed after all relevant entry
phasers (C<BEGIN>, C<CHECK>, C<INIT>, C<PRE>, C<ENTER>) have been run and
the mainline of the script have been executed. No error will occur if there
is no MAIN sub: your script will then just have to do the work, such as
argument parsinng, in the mainline of the script.

This comment has been minimized.

Show comment
Hide comment
@MasterDuke17

MasterDuke17 Oct 12, 2018

Contributor

"parsinng" -> "parsing"

@MasterDuke17

MasterDuke17 Oct 12, 2018

Contributor

"parsinng" -> "parsing"

Any normal exit from the MAIN sub, will result in an exit code of C<0>,

This comment has been minimized.

Show comment
Hide comment
@MasterDuke17

MasterDuke17 Oct 12, 2018

Contributor

"sub, will" -> "sub will" (I don't think the comma is needed)

@MasterDuke17

MasterDuke17 Oct 12, 2018

Contributor

"sub, will" -> "sub will" (I don't think the comma is needed)

indicating success. Any return value of the MAIN sub will be ignored.
If an exception is thrown that is not handled inside the MAIN sub, then the
exit code will be C<1>. If the dispatch to MAIN failed, a usage message
will be displayed on STDERR and the exit code will be C<2>.
The command line parameters are present in the C<@*ARGS> dynamic variable
and may be altered in the mainline of the script before the C<MAIN> unit is
getting called.

This comment has been minimized.

Show comment
Hide comment
@MasterDuke17

MasterDuke17 Oct 12, 2018

Contributor

I think the "getting" is not needed.

@MasterDuke17

MasterDuke17 Oct 12, 2018

Contributor

I think the "getting" is not needed.

The signature of (the candidates of the multi) sub MAIN determines which
candidate will actually be called using the standard
L<multi dispatch|/language/glossary#index-entry-Multi-Dispatch> semantics.
A simple example:
# inside file 'hello.p6'
sub MAIN($name) {
say "Hello $name, how are you?"
}
If you call that script without any parameters:
=begin code :lang<shell>
$ perl6 hello.p6
Usage:
hello.p6 <name>
=end code
However, if give a default value for the parameter, running the script either

This comment has been minimized.

Show comment
Hide comment
@MasterDuke17

MasterDuke17 Oct 12, 2018

Contributor

"if give" -> "if you give"

@MasterDuke17

MasterDuke17 Oct 12, 2018

Contributor

"if give" -> "if you give"

with or without specifying a name will always work:
# inside file 'hello.p6'
sub MAIN($name = 'bashful') {
say "Hello $name, how are you?"
}
=begin code :lang<shell>
$ perl6 hello.p6
Hello bashful, how are you?
=end code
=begin code :lang<shell>
$ perl6 hello.p6 Liz
Hello Liz, how are you?
=end code
Another way to do this, is to make sub MAIN a C<multi sub>:
# inside file 'hello.p6'
multi sub MAIN() { say "Hello bashful, how are you?" }
multi sub MAIN($name) { say "Hello $name, how are you?" }
Which would give the same output as the examples above. Whether you should
use either method to achive the desired goal, is entirely up to you.

This comment has been minimized.

Show comment
Hide comment
@MasterDuke17

MasterDuke17 Oct 12, 2018

Contributor

"achive" -> "achieve"

@MasterDuke17

MasterDuke17 Oct 12, 2018

Contributor

"achive" -> "achieve"

A more complicated example using a single positional parameter, multiple

This comment has been minimized.

Show comment
Hide comment
@MasterDuke17

MasterDuke17 Oct 12, 2018

Contributor

"parameter, multiple" -> "parameter and multiple" maybe?

@MasterDuke17

MasterDuke17 Oct 12, 2018

Contributor

"parameter, multiple" -> "parameter and multiple" maybe?

named parameters:
# inside "frobnicate.p6"
sub MAIN(
Str $file where *.IO.f = 'file.dat',
Int :$length = 24,
Bool :$verbose
) {
say $length if $length.defined;
say $file if $file.defined;
say 'Verbosity ', ($verbose ?? 'on' !! 'off');
}
With C<file.dat> present, this will work this way:
=begin code :lang<shell>
$ perl6 frobnicate.p6
24
file.dat
Verbosity off
=end code
Or this way with C<--verbose>:
=begin code :lang<shell>
$ perl6 frobnicate.p6 -v
24
file.dat
Verbosity on
=end code
If the file C<file.dat> is not present, or you've specified another filename
that doesn't exist, you would get the standard usage message created from
introspection of the C<MAIN> sub::
=begin code :lang<shell>
$ perl6 frobnicate.p6 doesntexist.dat
Usage:
frobnicate.p6 [--length=<Int>] [--verbose] [<file>]
=end code
Although you don't have to do anything in your code to do this, it may still
be regarded as a bit terse. But there's an easy way to make that usage
message better: by providing hints using pod features:
# inside "frobnicate.p6"
sub MAIN(
Str $file where *.IO.f = 'file.dat', #= an existing file to frobnicate
Int :$length = 24, #= length needed for frobnication
Bool :$verbose, #= required verbosity
) {
say $length if $length.defined;
say $file if $file.defined;
say 'Verbosity ', ($verbose ?? 'on' !! 'off');
}
Which would improve the usage message like this:
=begin code :lang<shell>
$ perl6 frobnicate.p6 doesntexist.dat
Usage:
frobnicate.p6 [--length=<Int>] [--verbose] [<file>]
[<file>] an existing file to frobnicate
--length=<Int> length needed for frobnication
--verbose required verbosity
=end code
=head2 C<%*SUB-MAIN-OPTS>
It's possible to alter how arguments are processed before they're passed
to C<sub MAIN {}> by setting options in C<%*SUB-MAIN-OPTS> hash. Due to the

This comment has been minimized.

Show comment
Hide comment
@MasterDuke17

MasterDuke17 Oct 12, 2018

Contributor

"in" -> "in the", or just remove "hash"

@MasterDuke17

MasterDuke17 Oct 12, 2018

Contributor

"in" -> "in the", or just remove "hash"

nature of dynamic variables, it is required to set up C<%*SUB-MAIN-OPTS>

This comment has been minimized.

Show comment
Hide comment
@MasterDuke17

MasterDuke17 Oct 12, 2018

Contributor

ibid.

@MasterDuke17

MasterDuke17 Oct 12, 2018

Contributor

ibid.

hash and fill it with the appropriate settings. For instance:
my %*SUB-MAIN-OPTS =
:named-anywhere, # allow named variables at any location
# other possible future options / custom options
;
sub MAIN ($a, $b, :$c, :$d) {
say "Accepted!"
}
Available options are:
=head3 C<named-anywhere>
By default, named arguments passed to the program (i.e., C<MAIN>)
cannot appear after any positional argument. However, if
C«%*SUB-MAIN-OPTS<named-anywhere>» is set to a true value, named arguments
can be specified anywhere, even after positional parameter. For example,
the above program can be called with:
=begin code :lang<shell>
$ perl6 example.p6 1 --c=2 3 --d=4
=end code
=head2 X<is hidden-from-USAGE|hidden-from-USAGE>
Sometimes you want to exclude a MAIN candidate from being shown in any
automatically generated USAGE message. This can be achieved by adding
a C<hidden-from-USAGE> trait to the specfication of the MAIN candidate
you do not want to show. Expanding on an earlier example:
# inside file 'hello.p6'
multi sub MAIN() is hidden-from-USAGE {
say "Hello bashful, how are you?"
}
multi sub MAIN($name) { #= the name by which you would like to be called
say "Hello $name, how are you?"
}
So, if you would call this script with just a named variable, you would get
the following usage:
=begin code :lang<shell>
$ perl6 hello.p6 --verbose
Usage:
hello.p6 <name> -- the name by which you would like to be called
=end code
Without the C<hidden-from-USAGE> trait on the first candidate, it would have
looked like this:
=begin code :lang<shell>
$ perl6 hello.p6 --verbose
Usage:
hello.p6
hello.p6 <name> -- the name by which you would like to be called
=end code
Which, although technically correct, doesn't read as well.
=head2 X<Unit-scoped definition of MAIN|declarator,unit (MAIN)>
If the entire program body resides within C<MAIN>, you can use the C<unit>
declarator as follows (adapting an earlier example):
=begin code :skip-test<unit>
unit sub MAIN(
Str $file where *.IO.f = 'file.dat',
Int :$length = 24,
Bool :$verbose,
); # <- note semi-colon here
say $length if $length.defined;
say $file if $file.defined;
say 'Verbosity ', ($verbose ?? 'on' !! 'off');
# rest of script is part of MAIN
=end code
Note that this is only appropriate if you can get by with just a single
(only) sub MAIN.
X<|USAGE>X<|$*USAGE>
=head1 sub C<USAGE>
If no multi candidate of C<MAIN> is found for the given command line
parameters, the sub C<USAGE> is called. If no such method is found,
the compiler will output a default generated usage message.
#|(is it the answer)
multi MAIN(Int $i) { say $i == 42 ?? 'answer' !! 'dunno' }
#|(divide two numbers)
multi MAIN($a, $b){ say $a/$b }
sub USAGE() {
print Q:c:to/EOH/;
Usage: {$*PROGRAM-NAME} [number]
Prints the answer or 'dunno'.
EOH
}
The default usage message is available inside C<sub USAGE> via read-only

This comment has been minimized.

Show comment
Hide comment
@MasterDuke17

MasterDuke17 Oct 12, 2018

Contributor

"via" -> "via the"

@MasterDuke17

MasterDuke17 Oct 12, 2018

Contributor

"via" -> "via the"

C<$*USAGE> variable. It will be generated based on available C<sub MAIN>
candidates and their parameters. As shown before, You can specify additional

This comment has been minimized.

Show comment
Hide comment
@MasterDuke17

MasterDuke17 Oct 12, 2018

Contributor

"You" -> "you"

@MasterDuke17

MasterDuke17 Oct 12, 2018

Contributor

"You" -> "you"

This comment has been minimized.

Show comment
Hide comment
@MasterDuke17

MasterDuke17 Oct 12, 2018

Contributor

"additional" -> "an additional"
"using" -> "using a"

@MasterDuke17

MasterDuke17 Oct 12, 2018

Contributor

"additional" -> "an additional"
"using" -> "using a"

extended description for each candidate using C<#|(...)> Pod block to set
L«C<WHY>|/routine/WHY».
=end pod
# vim: expandtab softtabstop=4 shiftwidth=4 ft=perl6

2 comments on commit f540dff

@zoffixznet

This comment has been minimized.

Show comment
Hide comment
@zoffixznet

zoffixznet Oct 12, 2018

Member

<lizmat> please leave comments at the commit or create an issue :-)

My only comment is is hidden-from-USAGE should be added to roast, if it's being documented.

Member

zoffixznet replied Oct 12, 2018

<lizmat> please leave comments at the commit or create an issue :-)

My only comment is is hidden-from-USAGE should be added to roast, if it's being documented.

@lizmat

This comment has been minimized.

Show comment
Hide comment
@lizmat

lizmat Oct 12, 2018

Member

@zoffixznet: Test added with perl6/roast@8da39b8

@MasterDuke17: thanks for the proofreading, all issues done

Member

lizmat replied Oct 12, 2018

@zoffixznet: Test added with perl6/roast@8da39b8

@MasterDuke17: thanks for the proofreading, all issues done

Please sign in to comment.