Mar 4, 2012
Slides made for "French Perl Mongers" show
Let's see a simple tool that execute stuff with mysql command line.
Display databases
#!/usr/bin/env perl
use strict;
use warnings;
my @cmd = ('mysql', '-BNe', 'show databases');
open my $f, '-|', @cmd or die $cmd[0].' not found...';
while(my $line = readline $f) {
print $line;
}
close $f;
We need login/password
#!/usr/bin/env perl
use strict;
use warnings;
my ($user,$passwd) = split(/,/,$ARGV[0]) if @ARGV > 0;
my @cmd = ('mysql', '-BNe', 'show databases');
push @cmd, "-u'$user'" if defined $user;
push @cmd, "-p'$passwd'" if defined $passwd;
print join(' ', "Launch:", @cmd)."\n";
open my $f, '-|', @cmd or die $cmd[0].' not found...';
while(my $line = readline $f) {
print $line;
}
close $f;
#launch with mywrapper.pl LOGIN,PASSWD
Add a filter
#!/usr/bin/env perl
use strict;
use warnings;
my ($user,$passwd) = split(/,/,$ARGV[0]) if @ARGV > 0;
my $filter = $ARGV[1] if @ARGV > 1;
my @cmd = ('mysql', '-BNe', 'show databases');
push @cmd, "-u'$user'" if defined $user;
push @cmd, "-p'$passwd'" if defined $passwd;
print join(' ', "Launch:", @cmd)."\n";
print "Apply filter: $filter\n" if defined $filter;
open my $f, '-|', @cmd or die $cmd[0].' not found...';
while(my $line = readline $f) {
next if(defined $filter && $line !~ /$filter/);
print $line;
}
close $f;
#Launch with mywrapper.pl LOGIN,PASSWD FILTER
It's quite easy but hard to maintain when you got ton of options. We need something better, something that handle the options.
Extended processing of command line options. This module simplify the creation of tools in Perl.
Now use the module in our example
#!/usr/bin/env perl
use strict;
use warnings;
use Getopt::Long::Descriptive;
my ($opt, $usage) = describe_options(
"$0 %o",
["user|u=s", "user auth"],
["passwd|p=s", "passwd auth"],
["filter|f=s", "db filter"],
["help|h", "this help"],
);
print($usage->text), exit if $opt->help;
my ($user, $passwd, $filter) = ($opt->user, $opt->passwd, $opt->filter);
#same as before
Now we got this :
$ perl mywrapper.pl -h
mywrapperl.pl [-fhpu] [long options...]
-u --user user auth
-p --passwd passwd auth
-f --filter db filter
-h --help this help
Test it with args:
$ perl mywrapper.pl -u root -f ^t
Launch: mysql -BNe 'show databases' -u'root'
Apply filter: ^t
test
Pretty better, let's do it with a class.
It is better to use a class to store your params, it's easier to maintain.
The MyWrapper.pm, a package to handle your tool.
package MyWrapper;
use strict;
use warnings;
sub new {
my ($proto, %params) = @_; my $class = ref $proto || $proto;
my $self = \%params;
$self->{mysql} //= 'mysql';
bless $self, $class;
}
sub mysql {
my ($self, $mysql) = @_;
$self->{mysql} = $mysql if @_ > 1;
return $self->{mysql};
}
sub user {
my ($self, $user) = @_;
$self->{user} = $user if @_ > 1;
return $self->{user};
}
sub passwd {
my ($self, $passwd) = @_;
$self->{passwd} = $passwd if @_ > 1;
return $self->{passwd};
}
sub filter {
my ($self, $filter) = @_;
$self->{filter} = $filter if @_ > 1;
return $self->{filter};
}
sub run {
my ($self) = @_;
my $mysql = $self->mysql;
my $user = $self->user;
my $passwd = $self->passwd;
my $filter = $self->filter;
my @cmd = ('mysql', '-BNe', 'show databases');
push @cmd, "-u'$user'" if defined $user;
push @cmd, "-p'$passwd'" if defined $passwd;
print join(' ', "Launch:", @cmd)."\n";
print "Apply filter: $filter\n" if defined $filter;
open my $f, '-|', @cmd or die $cmd[0].' not found...';
while(my $line = readline $f) {
next if(defined $filter && $line !~ /$filter/);
print $line;
}
close $f;
}
1;
The tool now looks like :
#!/usr/bin/env perl
use strict;
use warnings;
use MyWrapper;
use Getopt::Long::Descriptive;
my ($opt, $usage) = describe_options(
"$0 %o",
["user|u=s", "user auth"],
["passwd|p=s", "passwd auth"],
["filter|f=s", "db filter"],
["help|h", "this help"],
);
print($usage->text), exit if $opt->help;
my $wrapper = MyWrapper->new(user => $opt->user, passwd => $opt->passwd, filter => $opt->filter);
$wrapper->run();
Let's run it :
$ perl MyWrapper.pl --user=root --filter=^t
Launch: mysql -BNe 'show databases' -u'root'
Apply filter: ^t
test
Quite better, but a little bit long and complex.
Moo is Minimalist Object Orientation.
Our package MyWrapper.pm with Moo:
package MyWrapper;
use strict;
use warnings;
use Moo;
has mysql => ( is => 'rw',
default => sub {
'mysql'
}
);
has user => ( is => 'rw' );
has passwd => ( is => 'rw' );
has filter => ( is => 'rw' );
#run method remain the same
1;
It's really simple now. But we can do more. Let's see a module that does everything.
MooX::Options, add 'option' keyword for your object.
Your MyWrapper.pm is still simple:
package MyWrapper;
use strict;
use warnings;
use Moo;
use MooX::Options;
#note than I don't use option here, it is not available at command line
has mysql => ( is => 'rw',
default => sub {
'mysql'
}
);
option user => ( is => 'rw', doc => 'user auth', format => 's' );
option passwd => ( is => 'rw', doc => 'passwd auth', format => 's' );
option filter => ( is => 'rw', doc => 'filter table', format => 's' );
#run remain the same
Your MyWrapper.pl is now really simpler:
#!/usr/bin/env perl
use strict;
use warnings;
use MyWrapper;
my $wrapper = MyWrapper->new_with_options();
$wrapper->run();
You got this, now :
$ perl MyWrapper.pl -h
USAGE: MyWrapper.pl [-h] [long options...]
--user user auth
--passwd passwd auth
--filter filter table
-h --help show this help message
Now you know how to do a command line tool in style.
Comparison between MooX::Options and MooseX::GetOpt
Why should you prefer MooX::Options over MooseX::GetOpt ?
You've got the idea. Ok, it's finished now.
Mar 4, 2012