Commit
- 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
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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.
Sorry, something went wrong. |
||
Any normal exit from the MAIN sub, will result in an exit code of C<0>, | ||
This comment has been minimized.
Sorry, something went wrong. |
||
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.
Sorry, something went wrong. |
||
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.
Sorry, something went wrong. |
||
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.
Sorry, something went wrong. |
||
A more complicated example using a single positional parameter, multiple | ||
This comment has been minimized.
Sorry, something went wrong. |
||
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.
Sorry, something went wrong. |
||
nature of dynamic variables, it is required to set up C<%*SUB-MAIN-OPTS> | ||
This comment has been minimized.
Sorry, something went wrong. |
||
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.
Sorry, something went wrong. |
||
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.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong. |
||
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zoffixznet: Test added with Raku/roast@8da39b89e6
@MasterDuke17: thanks for the proofreading, all issues done
"parsinng" -> "parsing"