17

Is there a more elegant way of processing input coming from either the command line arguments or STDIN if no files were given on the command line? I'm currently doing it like this:

sub MAIN(*@opt-files, Bool :$debug, ... other named options ...) {
    # note that parentheses are mandatory here for some reason
    my $input = @opt-files ?? ([~] .IO.slurp for @opt-files) !! $*IN.slurp;

    ... process $input ...
}

and it's not too bad, but I wonder if I'm missing some simpler way of doing it?

VZ.
  • 21,740
  • 3
  • 39
  • 42
  • Maybe lean on [`$*ARGFILES`](https://docs.raku.org/language/variables#$*ARGFILES)? Aiui, in `raku.d` or later, `$*ARGFILES` *inside* `MAIN` will be the same as `$*IN`. But *outside*, so presumably in, for example, `RUN-MAIN`, it'll be set to prospective-handles-to-be corresponding to each of the arguments on the command line interpreted as files, or to `$*IN` if the command line was empty. Anyhoo, I'm suggesting you read the doc I linked, and follow the "class documenation" link it provides. – raiph Aug 30 '20 at 17:52

3 Answers3

11

I would probably go for a multi sub MAIN, something like:

multi sub MAIN(Bool :$debug)
{
    process-input($*IN.slurp);
}

multi sub MAIN(*@opt-files, Bool :$debug)
{
    process-input($_.IO.slurp) for @opt-files;
}
mscha
  • 6,509
  • 3
  • 24
  • 40
  • Thanks, I should have added that I actually used this initially, but decided to merge them together once I had more than one named option (I should have been more explicit about the `...` part), as I didn't want to duplicate the options and their `#=` descriptions. – VZ. Aug 30 '20 at 15:48
  • One way to get around that (having multiple named options that are shared across multiple `MAIN`s) is to define them in a `proto MAIN` and process the shared behaviour there before passing on to the more specific methods defined as `multi`. Here's an example on a WIP project of mine: https://gitlab.com/jjatria/raket/-/blob/e1238be7d6d7989c711496f9f8459c611cc70739/scripts/rocket-bot#L17 – jja Sep 28 '20 at 16:21
7

I'd probably do two things to change this. I'd break up the ?? !! onto different lines, and I'd go for a full method chain:

sub MAIN(*@opt-files, Bool :$debug, ... other named options ...) {
    my $input = @opt-files 
                  ?? @opt-files».IO».slurp.join
                  !! $*IN.slurp;

    ... process $input ...
}

You can also map it by using @opt-files.map(*.IO.slurp).join

Edit: building on ugexe's answer, you could do

sub MAIN(*@opt-files, Bool :$debug, ... other named options ...) {

    # Default to $*IN if not files
    @opt-files ||= '-';

    my $input = @opt-files».IO».slurp.join

    ... process $input ...

}
user0721090601
  • 5,276
  • 24
  • 41
  • It's true that the inputs are slightly different, but this is pretty standard Unix behaviour (e.g. `cat`). Using `join` is probably indeed more readable than `[~]`, thanks. – VZ. Aug 30 '20 at 15:50
  • Thanks, the part after edit is the best solution IMO, I haven't realized I could just use `'-'` here. BTW, should it be `//=` rather than `||=`? – VZ. Aug 31 '20 at 14:23
  • 1
    @VZ. Actually no, you have to use `||=` because the slurpy array will be defined (and `//`is defined-or) but empty (which is falsey). – user0721090601 Aug 31 '20 at 15:27
2

Something that I might expect to work is to set @*ARGS to the list of file names in the signature.
And then just use $*ARGFILES.

sub MAIN( *@*ARGS, Bool :$debug, ... other named options ...) {

    my $input = slurp; # implicitly calls $*ARGFILES.slurp()

    ... process $input ...
}

It doesn't work though.


You could get Rakudo to update $*ARGFILES by nulling it with a low-level null before you use it.

sub MAIN( *@*ARGS, Bool :$debug, ... other named options ...) {

    { use nqp; $*ARGFILES := nqp::null }

    my $input = slurp;

    ... process $input ...
}

But that is using an implementation detail that may change in the future.


A better way is to just directly create a new instance of IO::ArgFiles yourself.

You can even store it in $*ARGFILES. Then slurp on its own would slurp in all of the file contents.

sub MAIN( *@opt-files, Bool :$debug, ... other named options ...) {

    my $*ARGFILES = IO::ArgFiles.new( @opt-files || $*IN );

    my $input = slurp;

    ... process $input ...
}

Note that IO::ArgFiles is just an empty subclass of IO::CatHandle. So you could write IO::CatHandle.new( @opt‑files || $*IN ) instead.

Brad Gilbert
  • 33,846
  • 11
  • 78
  • 129
  • Thanks, I'll keep the last sentence of your answer in mind, and it's useful to know, but I believe the idea with using `'-'` is even better/simpler (thanks @ugexe and @user0721090601!), so I'll use it in my next script. – VZ. Aug 31 '20 at 14:24