From f540dff1f55c5ff1396ffd22a84d888af14107af Mon Sep 17 00:00:00 2001 From: Elizabeth Mattijsen Date: Fri, 12 Oct 2018 16:15:35 +0200 Subject: [PATCH] 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. --- doc/Language/create-cli.pod6 | 283 +++++++++++++++++++++++++++++++++++ 1 file changed, 283 insertions(+) create mode 100644 doc/Language/create-cli.pod6 diff --git a/doc/Language/create-cli.pod6 b/doc/Language/create-cli.pod6 new file mode 100644 index 000000000..5d095a08e --- /dev/null +++ b/doc/Language/create-cli.pod6 @@ -0,0 +1,283 @@ +=begin pod :tag + +=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 + +This looks at the values in L<@*ARGS|/language/variables#index-entry-@*ARGS>, +interpretes these according to some policy, and creates a C 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 +is used to call the MAIN subroutine with the generated C object. +This means that your MAIN subroutine may be a C, 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
will be executed after all relevant entry +phasers (C, C, C, C
, C) 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.
+
+Any normal exit from the MAIN sub, will result in an exit code of C<0>,
+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
unit is +getting called. + +The signature of (the candidates of the multi) sub MAIN determines which +candidate will actually be called using the standard +L 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 +$ perl6 hello.p6 +Usage: + hello.p6 +=end code + +However, if give a default value for the parameter, running the script either +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 +$ perl6 hello.p6 +Hello bashful, how are you? +=end code + +=begin code :lang +$ perl6 hello.p6 Liz +Hello Liz, how are you? +=end code + +Another way to do this, is to make sub MAIN a C: + + # 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. + +A more complicated example using a single positional parameter, multiple +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 present, this will work this way: +=begin code :lang +$ perl6 frobnicate.p6 +24 +file.dat +Verbosity off +=end code + +Or this way with C<--verbose>: + +=begin code :lang +$ perl6 frobnicate.p6 -v +24 +file.dat +Verbosity on +=end code + +If the file C 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
sub:: + +=begin code :lang +$ perl6 frobnicate.p6 doesntexist.dat +Usage: + frobnicate.p6 [--length=] [--verbose] [] +=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 +$ perl6 frobnicate.p6 doesntexist.dat +Usage: + frobnicate.p6 [--length=] [--verbose] [] + + [] an existing file to frobnicate + --length= 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 by setting options in C<%*SUB-MAIN-OPTS> hash. Due to the +nature of dynamic variables, it is required to set up C<%*SUB-MAIN-OPTS> +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 + +By default, named arguments passed to the program (i.e., C
) +cannot appear after any positional argument. However, if +C«%*SUB-MAIN-OPTS» 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 +$ perl6 example.p6 1 --c=2 3 --d=4 +=end code + +=head2 X + +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 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 +$ perl6 hello.p6 --verbose +Usage: + hello.p6 -- the name by which you would like to be called +=end code + +Without the C trait on the first candidate, it would have +looked like this: + +=begin code :lang +$ perl6 hello.p6 --verbose +Usage: + hello.p6 + hello.p6 -- the name by which you would like to be called +=end code + +Which, although technically correct, doesn't read as well. + +=head2 X + +If the entire program body resides within C
, you can use the C +declarator as follows (adapting an earlier example): + +=begin code :skip-test +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 + +If no multi candidate of C
is found for the given command line +parameters, the sub C 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 via read-only +C<$*USAGE> variable. It will be generated based on available C +candidates and their parameters. As shown before, You can specify additional +extended description for each candidate using C<#|(...)> Pod block to set +L«C|/routine/WHY». + +=end pod + +# vim: expandtab softtabstop=4 shiftwidth=4 ft=perl6