Tyil
Tyil

Reputation: 1807

How to require 1 or more of an argument in MAIN

Right now, I have a MAIN sub that can take one or more string arguments. But I am using two separate parameters for MAIN to do it:

sub MAIN (
    Str:D $file,
    *@files,
) {
    @files.prepend: $file;

    # Rest of the program
}

Now I am wondering if there's a more idiomatic way to achieve this, as my current solution feels a little clunky, and not very Perly.

Upvotes: 9

Views: 260

Answers (4)

Marty
Marty

Reputation: 2808

At the risk of "over answering" - my take on "Perly" is concise as possible without becoming obscure (perhaps I'm just replacing one subjective term with two others... :-)

If you have a "slurpy" array as the only parameter, then it will happily accept no arguments which is outside the spec you put in the comments. However, a positional parameter is compulsory by default and proto's are only necessary if you want to factor out constraints on all multi's - presumably overkill for what you want here. So, this is enough:

sub MAIN($file , *@others) {
    say "Received file, $file, and @others.elems() others."
} 

This is close to what mr_ron put - but why not go with the default Usage message that MAIN kindly whips up for you by examining your parameters:

$ ./f.pl
Usage:
  ./f.pl <file> [<others> ...]

Some might say I cheated by dropping the Str type constraint on the first parameter but it really doesn't buy you much when you're restricting to strings because numerics specified at the CLI come through as type IntStr (a kind-of hybrid type) that satisfies a Str constraint. OTOH, when constraining CLI parameters to Num or Int, Perl6 will check that you're actually putting digits there - or at least, what unicode considers digits.

If you're wanting actual filenames, you can save yourself a validation step by constraining to type IO(). Then it will only work if you name a file. And finally, putting where .r after the parameter will insist that it be readable to boot:

sub MAIN(IO() $file where .r, *@others) { ...

One short line that insists on one compulsory argument that is a filename referencing a readable file, with a varying number of other parameters and a useful Usage message auto generated if it all goes sideways...

Upvotes: 4

Brad Gilbert
Brad Gilbert

Reputation: 34120

You could do it with a proto sub

proto sub MAIN ( $, *@ ){*}

multi sub MAIN ( *@files ) {
    # Rest of the program
}

or with sub-signature deparsing

sub MAIN ( *@files ($,*@) ) {
    # Rest of the program
}

Upvotes: 10

jjmerelo
jjmerelo

Reputation: 23517

You can also try and use simply dynamic variables:

die "Usage: $*EXECUTABLE <file> <file2>*" if !+@*ARGS;
my @files =  @*ARGS;

where @*ARGS is an array with the arguments issued into the command line

You can even use @*ARGFILES, since they are actually files

Upvotes: 1

mr_ron
mr_ron

Reputation: 479

Perhaps good enough answer here:

sub MAIN(*@a where {.elems > 0 and .all ~~ Str}) {
    say "got at least 1 file name"
}

sub USAGE {
    say "{$*PROGRAM-NAME}: <file-name> [ <file-name> ... ]"
}

Based on docs here: https://docs.perl6.org/type/Signature#Constraining_Slurpy_Arguments

Upvotes: 4

Related Questions