Porting a very simple script to Perl6

To entertain myself during some of the not so interesting talks at German Perl Workshop 2015 I decided to try to port a very simple script from Perl5 to Perl6. Due to the rather wobbly internet connection (tethered through the organisers smartphones) I could not use any online resources. Luckily I installed Perl6 before I left (using rakudobrew), so I had at least some other Perl6 code to take a look at.

The Perl5 script

Here's the Perl5 script (which I wrote in 2010 and never touched since...):

#!/usr/bin/perl
use strict;
use warnings;
use 5.010;

use Path::Class;
use File::Copy;

my ($action, $in_file) = @ARGV;
my $out = Path::Class::dir('/home/domm/media/fotos/select',$action);
die "no such dir $out" unless -d $out;

my $out_file_name = $in_file;
$out_file_name =~ s{/+}{_}g;
copy($in_file,$out->file($out_file_name)->stringify);

This script is part of my Picture-of-the-day toolchain. It is run as a qiv-command from inside qiv. This means that when I press any number when viewing an image, qiv will pass the number I just pressed and the filename of the currently viewed image to qiv-command, i.e. this script.

The script is just a slightly enhanced cp that cleans up the filename a bit and has the target directory hardcoded. So if I hit 5 while viewing DCIM/131_PANA/P1310363.jpg, this file will be copied to /home/domm/media/fotos/select/5/DCIM_131_PANA_P1310363.jpg.

Quite boring.

Let's start

After a bit of ack'ing in some Perl6 code I found something called Shell::Command which seems to implement cp. I could not find anything about ARGV parsing, so I just hardcoded some values. I tried this:

#!/usr/bin/env perl6

use Shell::Command;

my $action = 5;
my $in_file = '/media/mmc/DCIM/131_PANA/P1310372.JPG';

my $out = '/home/domm/media/fotos/select/' . $action;
die "no such dir $out" unless -d $out;

my $out_file_name = $in_file;
$in_file =~ s{/+}{_}g;
copy($in_file,$out->file($out_file_name)->stringify);

I ran it via perl6 qiv-command.p6 and wasn't surprised that it did not work. But I was surprised by the very helpful error message:

=SORRY!= Error while compiling qiv-command.p6
Unsupported use of . to concatenate strings; in Perl 6 please use ~
at qiv-command.p6:10
------> out = '/home/domm/media/fotos/select/' . ⏏$action;

Wow, Perl6 tells me to use '~' instead of '.' to concatenate strings. I knew that the dot was wrong, but couldn't remember the replacement. I knew that '+' wasn't right (looking at you, JavaScript), but sort of remembered '_' from a long time ago. But in the end it was irrelevant, because Perl 6 just told me.

Next try

#!/usr/bin/env perl6

use Shell::Command;

my $action = 5;
my $in_file = '/media/mmc/DCIM/131_PANA/P1310372.JPG';

my $out = '/home/domm/media/fotos/select/' ~ $action;
die "no such dir $out" unless -d $out;

my $out_file_name = $in_file;
$in_file =~ s{/+}{_}g;
copy($in_file,$out->file($out_file_name)->stringify);

The only change is the '~' instead of the '.' in line 10. Running this version gets me:

=SORRY!= Error while compiling qiv-command.retry
Unsupported use of =~ to do pattern matching; in Perl 6 please use ~~
at qiv-command.p6:14
------> $in_file =~⏏ s{/+}{_}g;

(What you can't see here (because I'm to lazy) is that the error message is color-highlighted. And this fancy utf-char '⏏' points to the column where the error was encountered)

So let's fix that, and try again:

perl6 qiv-command.p6
=SORRY!=
Unrecognized regex metacharacter / (must be quoted to match literally)
at qiv-command.p6:14
------> $in_file ~~ s{⏏/+}{_}g;
Unrecognized regex metacharacter + (must be quoted to match literally)
at qiv-command.p6:14
------> $in_file ~~ s{/⏏+}{_}g;
Unsupported use of brackets around replacement; in Perl 6 please use assignment syntax
at qiv-command.p6:14
------> $in_file ~~ s{/+}⏏{_}g;

Huh, seems that using things other than '/' for regex is not supported (or as easy to use as in Perl5). So let's just use plain old '/':

$in_file ~~ s/\/+/_/g;

But I still get an error:

=SORRY!= Error while compiling qiv-command.p6
Unsupported use of /g; in Perl 6 please use :g
at qiv-command.p6:14
------> $in_file ~~ s/\/+/_/g⏏;

After some more acking through various Perl6 scripts I remember that regex modifiers like 'g' have moved from the end of the regex to the front, so you can read the regex from left to right and know how to set your brain before hitting the actual regex:

$in_file ~~ s:g/\/+/_/;
perl6 qiv-command.p6
=SORRY!= Error while compiling qiv-command.p6
Unsupported use of -> as postfix; in Perl 6 please use either . to call a method, or whitespace to delimit a pointy block
at qiv-command.p6:15
------> copy($in_file,$out->⏏file($out_file_name)->stringify);
    expecting any of:
        postfix

Ah, of course, file and stringify is Path::Class, which I'm not using here. So I need to figure out how to do this in Perl6. I again ack some of the Perl6 code coming with Perl6 and find this:

$*SPEC.catfile($dir, $template ~ $ext);

This looks usable, so I replace quite a bit of code with this:

#!/usr/bin/env perl6

use Shell::Command;

my $action = 5;
my $in_file = '/media/mmc/DCIM/131_PANA/P1310372.JPG';

my $out_file_name = $in_file;
$out_file_name ~~ s:g/\/+/_/;

my $out_file = $*SPEC.catfile('/home/domm/media/fotos/select/', $action, $out_file_name);

copy($in_file,$out_file);

Does it work yet?

Yes!

Now I needed to figure out how to handle ARGV. Luckily FROGGS was sitting right behind me, so I asked him for help during the next break. He told me about the rather magic MAIN subroutine. And implemented a rather basic, idiomatic Perl6 solution, which I mangled a bit more (to get better cleanup on the filename):

Perl6 rocks!

#!/usr/bin/env perl6

use Shell::Command;

sub MAIN ($action, $in) {
    my $filename = $in;
    $filename ~~ s:g/( \/ || \.\.)/_/;
    $filename ~~ s:g/_+/_/;
    $filename ~~ s:g/^_//;

    my $out = $*SPEC.catfile('/home/domm/media/fotos/select/', $action, $filename);
    copy($in, $out );
}

And it's fast! There is a very short lag when starting the script, but it's hardly noticeable. Definitely much shorter than when starting a Moose-powered script.

Summary

Perl6 is indeed usable now, and it's a lot of fun working with it. Not only because the error messages are awesome & helpful, but because the language really removes all the warts and disgusting bits from Perl5, while still staying very Perlish (much more than go, with which I've also played around a bit recently)

My plan for the next months: Port a few more of my various helper scripts and tools to Perl6!

Update

Here's the current version, including some suggestions by smls. And as of 2015-06-02T10:00:00 I'm using Perl6 in production

#!/usr/bin/env perl6

sub MAIN ($action, $in) {
    my $filename = $in;
    $filename ~~ s:g/['/' | '..']+/_/;
    $filename ~~ s:g/_+/_/;
    $filename ~~ s:g/^_//;

    my $out = $*SPEC.catfile('/home/domm/media/fotos/select/', $action, $filename);
    copy $in, $out;
}