#title Perl Tricks
#author Stefan Hornburg (Racke)
#lang en
#topics perl; CPAN; Moo; Tests; DateTime; MySQL; PostgreSQL; MariaDB; Charts; Dancer; Git; Travis
#teaser Howto for using OO/Moo in Perl, writing tests and CPAN modules.
** OO in Perl
*** namespace::clean
It is a best practice to use
namespace::clean
in your class or your role
after
use ...
statements. Otherwise imported functions
double as methods for your class, which can have unexpected side effects which
are really hard to track down.
** Using Moo
*** Types for Moo
We can recommend to use [[https://metacpan.org/pod/Type::Tiny][Type::Tiny]] instead of [[https://metacpan.org/pod/MooX::Types::MooseLike][MooX::Types::Mooselike]]. The latter is not Moose-friendly, while
Type::Tiny types get inflated to full Moose types if you happen to play with them in Moose. Also they are faster and we get better coercion in Moo for free.
One thing you may not have spotted for Type::Tiny which is useful...
it overloads | and & for use in isa - very tidy.
use Moo;
use Types::Standard qw/ArrayRef HashRef InstanceOf/;
has fields => (
is => 'ro',
isa => ArrayRef [ InstanceOf ['Template::Flute::Form::Field'] ],
);
**** ArrayRef
has config_files => (
is => 'ro',
isa => ArrayRef,
default => sub { [] },
);
**** Maybe
Sometimes it makes sense to allow =undef= as a value, e.g. if the builder isn't able to produce the promised type:
{{{
has config => (
is => 'ro',
isa => Maybe[HashRef],
lazy => 1,
builder => '_build_config',
);
}}}
*** Speed ups
- [[https://metacpan.org/pod/Class::XSAccessor][Class::XSAccessor]]
- [[https://metacpan.org/pod/Type::Tiny::XS][Type::Tiny::XS]]
** UTF-8
Ensure that me use UTF-8 encoding for the standard input/output streams:
{{{
use open ':std', ':encoding(utf-8)';
}}}
** Using DateTime
*** Converting JSON dates
APIs often using JSON as output. A JSON date looks like that:
{{{
}}}
*** Formatting
{{{
DateTime->now->strftime('%Y-%m-%d');
}}}
*** Week
This function determines the first day of the week for a given year:
{{{
sub first_day_of_week
{
my ($year, $week) = @_;
# Week 1 is defined as the one containing January 4:
DateTime
->new( year => $year, month => 1, day => 4 )
->add( weeks => ($week - 1) )
->truncate( to => 'week' );
}
}}}
** Parsing
*** Text
Split text into an array of lines:
{{{
my @lines = split /\r?\n/, $content;
my %email_headers;
for my $line (@lines) {
if ($line =~ /^(From|To):\s(.*)/) {
$email_headers{lc($1)} = $2;
}
}
}}}
*** List of arguments
The simplest way to parse a list of arguments separated by whitespace is the
*split* function:
{{{
my $arglist = 'foo bar baz';
my @args = split (/\s+/, $arglist));
}}}
This doesn't work with a list of files when it includes filenames with
whitespaces:
- 'foo bar'
- 'baz'
You can use the *shellwords* function from the [[https://metacpan.org/pod/Text::ParseWords][Text::ParseWords]] module in this
case:
{{{
use Text::ParseWords;
my $arglist = '"foo bar" baz';
my @args = shellwords($arglist);
}}}
** Regular expresssions
*** Wrap all urls in a text into a HTML link
{{{
$text =~s#(https?://\S+?)(\.*[\s\<,])#$1$2#igs;
}}}
** Recipes
*** Print version of installed Perl modules
There is a very useful module called *V* for printing out version information about installed Perl modules:
{{{
perl -MV=Dancer2
Dancer2
/home/racke/perl5/perlbrew/perls/perl-5.24.0/lib/site_perl/5.24.1/Dancer2.pm: 0.205000
/home/racke/perl5/perlbrew/perls/perl-5.24.0/lib/site_perl/5.24.0/Dancer2.pm: 0.204001
}}}
** Convert images with Image::Magick
*** Resize by percents
{{{
$ convert mypic.jpg -resize 25% mypicsmall.jpg
}}}
*** Create transparent picture
{{{
$ convert -size 1000x1000 xc:transparent any-square.png
}}}
** CPAN modules - the good and the bad
File::Slurp
is deprecated, use for example Path::Tiny
instead.
*** Charts
[[https://metacpan.org/pod/Chart::Clicker][Chart::Clicker]]
** Writing tests
*** Test for warnings
Always use [[https://metacpan.org/pod/Test::Warnings][Test::Warnings]] to capture unexpected warnings in your tests:
use Test::Warnings;
*** Test whether result is one of multiple values
use Test::Deep;
my $status = $mws->GetServiceStatus;
cmp_deeply $status, any(qw/GREEN GREEN_I YELLOW RED/),
"Test response of GetServiceStatus API method";
*** Skipping tests
Often tests are using an optional module or external resources. In that case we just skip these tests if
we haven't all pieces in the right place.
For example, if an environment variable isn't set
properly:
if ($ENV{SOLR_URL}) {
$solr_url = $ENV{SOLR_URL};
}
else {
plan skip_all => "Please set environment variable SOLR_URL.";
}
*** Provide useful information with =diag=
Even if you think your tests won't fail, they might.
In this case it is very helpful to provide additional
information to trace down the source of the problem:
# check output
my @matches = $out =~ /Blue ball/g;
ok (@matches == 2, 'Test replacement in both lists')
|| diag "Matches: ", scalar(@matches), "Output: $out";
A good alternative is to use =is= instead of =ok=, which
automatically provides diagnostics:
# check output
my @matches = $out =~ /Blue ball/g;
is (scalar(@matches), 2, 'Test replacement in both lists');
Also other test functions like =isa_ok= print further information.
*** Test coverage
[[https://metacpan.org/pod/Devel::Cover][Devel::Cover]] makes test coverage easy in
your distribution:
cpanm Devel::Cover
perl Makefile.PL
make
cover -test
You can find more information in the blog post from [[http://blogs.perl.org/users/neilb/2014/08/check-your-test-coverage-with-develcover.html][Neil Bower]].
** Installing CPAN modules
*** cpanm
You can install Perl modules from Git repositories, e.g.
{{{
cpanm git@github.com:interchange/Amazon-MWS.git@topic/amazon-pay-2020-01-28
}}}
The part after the @ is a branch, tag or hashsum.
*** Debian prerequisites
#debianprequisites
Here we show you which libraries you need to install for binary Perl
modules (Debian and Ubuntu).
**** DBD::MySQL
This works for MySQL and MariaDB:
$ apt install libmysqlclient-dev
**** DBD::ODBC
$ apt install unixodbc-dev
**** DBD::Pg
$ apt install libpq-dev
**** File::LibMagic
$ apt install libmagic-dev
**** Imager
You need to install a number of development packages for the different
image types *before* installing Imager, e.g. on Debian:
$ apt install libjpeg-dev libpng-dev libgif-dev libtiff-dev libfreetype6-dev
**** Net::LibIDN2
$ apt install libidn2-dev
**** Net::SSLeay, Crypt::OpenSSL::Random, Crypt::OpenSSL::X509, ...
$ apt install libssl-dev
**** IO::Socket::SSL
$ apt install zlib1g-dev
**** XML::Parser, XML::Twig, ...
$ apt install libexpat1-dev
**** XML::LibXML
$ apt install libxml2-dev
*** Tips for other modules
**** Dancer2
For more speed, install recommended modules as well:
cpanm --installdeps --with-recommends Dancer2
These are XS modules replacing pure Perl modules, e.g. *HTTP::XSHeaders* or *Type::Tiny::XS*.
** Patching CPAN modules
If you want to make a patch for a CPAN module which doesn't provide a Git repository, [[https://metacpan.org/pod/Git::CPAN::Patch][Git::CPAN::Patch]] comes in very handy:
{{{
% git-cpan clone WebService::Xero
creating WebService-Xero
created tag 'v0.10' (7dbe0fdd5c54307ce6ea6d6943ddf529e1a7ab8c)
created tag 'v0.11' (082ce463c0dec679710d0eecfbbaf47262526bff)
}}}
Create a branch for your patch:
{{{
% git checkout -b topic/enable-put-method
}}}
After done with patching, you can submit the changes as follows:
1. add your changes and commit
2. push patch to CPAN
For example:
{{{
% git add -u
% git commit -m "Enable PUT method in Xero agent."
[topic/enable-put-method 76c60c1] Enable PUT method in Xero agent.
%
}}}
** Writing CPAN modules
*** Resources
Please add resources for [[https://metacpan.org/][meta::cpan]] to your Makefile.PL, e.g.
META_MERGE => {
resources => {
repository => 'https://github.com/interchange/interchange6-schema.git',
bugtracker => 'https://github.com/interchange/interchange6-schema/issues',
IRC => 'irc://irc.freenode.net/#interchange',
},
},
*** Prerequisites and minimum versions
**** Test::Deep
0.114 :: if you use =noneof=
**** Type::Tiny
0.008 :: if you use =InstanceOf=
*** Travis
Before you commit and push your .travis.yml
file,
make sure that file is valid:
travis lint .travis.yml
**** Perl versions
The lastest Perl version supported by Travis is =5.30= as of September 2020.
**** Coverage reports
language: perl
perl:
- "5.10"
- "5.12"
- "5.14"
- "5.16"
- "5.18"
- "5.20"
- "5.22"
- "5.24"
- "5.28"
- "dev" # installs latest developer release of perl
- "blead" # builds perl from git
matrix:
include:
- perl: 5.28
env: COVERAGE=1 # enables coverage+coveralls reporting
allow_failures:
- perl: blead # ignore failures for blead perl
sudo: false # faster builds as long as you don't need sudo access
before_install:
- eval $(curl https://travis-perl.github.io/init) --auto
Please see [[https://github.com/travis-perl/helpers][Travis helpers]] for complete
reference.
** Scripts
Add missing columns with default values to CSV file:
$ perl -i -pe 's/$/;0;/' data.csv
This one-liner removes carriage returns in a file:
{{{
$ perl -i -pe 's/\r//g' data.csv
}}}
Turn file with multiples lines into a comma separated list:
{{{
$ cat myfile.txt
FOO
BAR
BAZ
}}}
{{{
$ perl -pe 's/\r?\n/,/g' myfile.txt
FOO,BAR,BAZ
}}}