Reviews by David Landgren


Text-Unidecode (0.04) *****

I've not had the chance to use this module (yet). But the documentation is a sheer delight to read, probably because I've been reading too much Ted Dziuba (see More modules should be this powerfully simple.

Data-Random (0.05) *****

I needed to take n random elements from a set, and I didn't feel like writing much code. I ran 'apropos Random' on my machine, and it spat out Data::Random, something that must have been installed as a prerequisite for something else.

I checked out the manpage, and cut and pasted a single line of code and I was done. The API is very nice and offers ways to generate random words, dates, times, characters, sets and selections.

For instance, just for fun, if you want to select next week's winning Lotto numbers, something like:

print for rand_set(set => [1..50], size => 6);

will print them out for you (adjust the values to respect your local specifications). If you win, I'll settle for a 2% cut.

Spreadsheet-ParseExcel (0.33) ****

Someone handed me a huge Excel file and said "We need to turn this into a web site".

I had a look at the results via Text::CVS_XS and realised I needed something else. I was aware of Spreadsheet::ParseExcel and so I thought I'd give it a try.

The documentation (as of 0.2603) was tedious to read, filled as it was with Hungarian notation. I see that Gábor has done a good job cleaning that up. The current 0.33 documentation reads much more easily.

Within an hour or so of exploring I had a program written that was reading the spreadsheet and populating a database with the information I need to produce the web site.

When I run my extraction program it spews out reams of "Character in 'C' format wrapped in pack" warnings. There were many threads in perl5-porters last year, in the run-up to 5.10, discussing the problems with pack 'C' and the abstraction leakage it causes, so I was a little concerned.

But as far as I can tell the extraction is accurate. There are some problems with apostrophes being mangled, but I think that's the perennial Microsoft feature/problem of smart quotes, and something I can live with. And again, for all I know, this may be fixed in 0.33.

All in all, this module saved me a huge amount of time and allowed me to get on with the more interesting aspects of the project. I simply do not know what I would have done if it wasn't available.

CGI-Ajax (0.701) *****

There are a number of contradictory reviews regarding this module, here is my own.

I have a lot of dynamic pages on my corporate LAN produced with good ol' There is no performance issue, insofar as they are a run with a frequency that is measured in hits per day, rather than hits per second. A number of the programs are over five years old and these days present a lot more information, which makes the click/submit/refresh cycle cumbersome for the end user. The main problem being that after clicking on submit at the end of the page, the refreshed pages doesn't reset itself to the last field updated/modified.

Thus I cast around for an easy way to "AJAXify" the pages, to retrofit some user friendliness and allow updates to be made leaving the page at the same position. I had a quick scan an CPAN, skimmed the documentation of this module, and figured it would do the job. So I downloaded it, and after an hour of futzing around (which would have taken 10 minutes had I read the documentation more carefully), I had my asynchronous updates up and running.

I have therefore been able to earn my old programs a reprieve. They can continue to do their job and I can put off having to rewrite them using a clean MVC template-based approach. Which would be fun, but not really justified since they aren't really broken. If you find yourself in a similar situation, this module is ideal.

WWW-Mechanize-Shell (0.44) *****

A truly five-star module.

I know what WWW::Mechanize does, and I use a couple of modules that in turn use it, but I've always found the idea of setting up my own mechanizer as something rather intimidating and so I have always put off learning it.

I needed to scrape a secure site and realised that it was time to bite the bullet and finally learn how to use WWW::Mechanize. I first started by downloading a number of modules that use W::M to study real-life examples of use by other people.

And then I stumbled across this module. Much like a session, you fire up an interactive shell. The first thing to do is to 'get' an URI, and from there you can look at the links and forms on the page. You can fill out form fields interactively, click on buttons, download page contents and save them to local files, follow links and generally behave like a web browser.

And when all is said and done, you can save your session as a perl program that replays your clickery wanderings as an instantiation of a WWW::Mechanize object, and with a little bit of polishing, your work is done.

A truly awesome piece of work that saved me heaps of time, and I enjoyed myself at the same time.

Something went wrong during the install, so I didn't get the online interactive help, but that was no problem, I just opened up documentation on in a browser tab.

Mail-IMAPClient (2.2.9) *****

I have a couple of moderately busy Postfix servers that spew a large amount of admin messages concerning incoming traffic that is bounced or rejected for various reasons. There is too much to handle manually, so I wrote a program using Mail::IMAPClient to go through the folders where these messages are stored, and delete all the simple cases that I don't care about (and writing a one-line summary about the transaction to a log file for later perusal).

This helps reduce the size of the IMAP folders and what is left are the potentially tricky messages that may require more careful investigation.

Mail::IMAPClient handled the aspects of connecting to the server and bringing each message (header and body) to my program in a straight-forward manner. The documentation was easy to understand, and all the functionality I needed to use was "just there".

I don't know what the other IMAP modules on CPAN are worth, but this one is quite probably all you need.

Interval (0.03)

I needed to munge some data, and remove records that had a begin and end date that overlapped partially or completely with records I had already seen. I therefore asked CPAN what modules exist to deal with date intervals.

I immediately discovered Date::Interval. I had a bit of trouble downloading it, because I tried to download the Date-Interval distribution, which doesn't exist. A closer look revealed that the name of the distribution is "Interval".

I then installed it sucessfully, except that it failed when I tried to use it, because the distribution Makefile.PL neglects to mention that Date::Manip is a pre-requisite.

So I went and installed that, again stumbling over the fact that the name of the distribution is DateManip, and not, as I would have expected, Date-Manip. I know this is a really minor issue, but modules that cut "against the grain" like this interrupt what would otherwise be a smooth user experience.

(update: I should point out that I was using a package manager (ppm for ActivePerl on Win32) that deals with distributions only, not modules. Hence the problem).

At this point I had both Date::Interval and Date::Manip installed. When I tried to run my program, I received another error message

"ERROR: Date::Manip unable to determine TimeZone"

It was about here that my software hate meter began to move into the red zone. I know I will not be dealing with time zones, so why is it even bothering to check? It should defer that test until it actually needs to.

I gave up on Date::Interval at this point. I had two other niggling suspicions about whether this module would be useful. Firstly, the constructor doesn't offer a simple ($year, $month, $day) constructor. It has a magic date parser constructor, but I felt it was a bit silly to sprintf up a date string just so that it could parse it.

Also, I am unsure that I'd be able to stick a series of dates into a Date::Interval object, and then see if a new date interval hits any of the existing intervals. I have the feeling that one has to maintain an array of Date::Interval objects, and then loop through it to see whether a new interval touches any of them. Which seems to be more make-work code that I care to write.

I shall now have a look at DateTime-Set.

SVK (1.08) *****

svk is most useful piece of new software I've encountered in a very long time. I regularly work on my modules on my laptop in odd places, so I take a mirror of my Subversion repositories into local SVK repositories, and then when I can back to the connected universe, I upload my changes, and the Subversion repository is updated with each distinct change as logged in svk.

What's more, as daunting as it may seem at the outset to figure out how to get all this up and running, all you need to remember is 'svk help intro' and you have all you need to know. I have only been using it for a couple of months, but already I could not live without it.

My only regret with this review is that I can't wind the star button up to 11.

NCBI (HASH(0xb8c5c0c)) *

There is no documentation, apart from a poorly word-wrapped README file, so I don't know what NCBI is, nor is there any explanation of what the modules do.

There are no tests, the code doesn't appear to run under strictures, since variables are package, rather than lexical, variables. It looks like there's lots of cut'n'paste programming going on.


DNS-TinyDNS (0.22) *****

Over the years, a number of printers at work have been retired, and/or replaced by different printers or multi-purpose copiers. I had to go through my printer hosts as defined in my DNS server, to see which ones were still alive.

I started to write a program to parse the tinydns data file (I had got as far as 'use strict;'...) when I thought to check CPAN, to see if anyone had already uploaded a module to take care of the fiddly details. In general, djb datafiles are dead simple to parse, but if someone's already gone to the trouble to do so, well, take it.

I installed the module without a hitch, and wrote my program in about two minutes flat.

My only gripe with the module as it stands is that you have to specify the directory of the tinydns configuration. If you have drunk the DJB kool-aid down to the last dregs and/or you haven't bothered to set it up to use a different directory, there is only one place you'll find it, and that's in /etc/tinydns (or /usr/local/etc/tinydns if you're running a BSD ports system).

Therefore, the module should try the usual locations to find the data files it wants to operate on if the user fails to supply the "dir" parameter to the new() constructor. This makes for less work, and would make the module Do The Right Thing.

Anyway, the program came out short, sharp and sweet (view source to get the indented view):

#! /usr/bin/perl -w

use strict;
use DNS::TinyDNS;
use Net::IP::Match::XS;
use IO::Socket;
use Socket 'inet_aton';

my $dns = DNS::TinyDNS->new(type => 'dnsserver', dir => '/etc/tinydns');

peek($_->{ip}) or print "$_->{host}($_->{ip}) dead\n"


sort {inet_aton($a->{ip}) cmp inet_aton($b->{ip})}

grep {match_ip($_->{ip}, '')}

$dns->list( type=>'host', zone => '' );

sub peek {

my $s = IO::Socket::INET->new(

Timeout => 2,

PeerHost => $_[0],

Proto => 'tcp',

PeerPort => 9100,

) or return 0;

close $s;

return 1;

And now I know I have 38 obsolete DNS records that may be removed! The ease in writing this code came about in large part due to DNS::TinyDNS. Having to parse the file myself would have added a proportionally large amount of make-work code. Thanks Anarion.

Net-IP-Match-XS (0.03) ***

After having dismissed Net-IP-Match for its strange function name, I turned to this module instead. It compiled without a hitch and installed correctly.

It does exactly what the label on the tin says it does, in the way you would expect. I note that there is a nasty bug open on RT (lookups against /1 netblocks don't work), but for my purpose it works well.

The test suite is very skimpy, and could do with a more exhaustive approach (which no doubt would have caught the above bug).

Net-IP-Match (0.01) **

I was looking for a module that deals with checking whether a given IPv4 address is contained within a netblock. There are several modules out there (that I have used in the past) that do just this, and I couldn't remember their exact names.

So I ran a search, and noticed this module, which sounds like it does exactly what I was looking for, and nothing else.

Looking at the documentation, it offers a single function, which returns a true/false value. Unfortunately, the name of the function is __MATCH_IP, which, apart from the screaming uppercase, may also be considered a private function since it begins with an underscore. The rationale for this choice is not explained.

This seemed sufficiently odd for me to pass it over, and look for something else. Looking more closely, the test suite does not try to explore the problem space very much.

I moved onto Net-IP-Match-XS instead.

File-Mosaic (0.01)

This module appears to solve an itch that I have had in the past. The documentation example (constructing a dhcpd.conf file from little pieces) is a bit too terse though, for me to be sure. It is not clear to me how the mosaics are ordered.

What I do know is that these days, I solve these sorts of problems with either Template::Toolkit or Data::Locations. It would be helpful if the documentation explained how it works in relation to these other modules.

The module code itself is clear and easy to follow. The synopsis needs to be indented in order to have the code segment rendered literally (which in turn could benefit from the use of heredocs to make the example less noisy).

Acme-Phlegethoth (1.02) *****

use Acme::Phlegethoth;
ia ia! f'IlYaaagl k'yaRNAk oOboSHU n'GhaYaR Uh'eOTH f'YA nogYaR
EBumna n'gHa fhtAGN Ch' nnnS'UHNog sYha'N gEb NiLgh'rI!
vuLGtLaGlNotH f'lLoIg VulgTlAGLn N'gHa OObOSHu huPaDgh throD gOkA geb
hUpADgHnyTh LLoIG yzHRO ya ah ph'Mnahn'AGl LLlL N'GHa ZHrOAGL eP
NnNEp FtAGhU ZhrOYAr ph'orr'E! athg GOtHAaGl hAfH'DrN NnNnGLuI lLoiG
Y'hah 'ai bUg nNnnog VUlgtmog TharANaK PH'gRaH'n nAEe nAflNILgH'riOG
S'Uhn hrii F'stElL'bSNA yA! Mg yA FHtAgNnyTH thROd n'GHfT kAdISHtu
uH'EoR Gof'nn naFlgRaH'N nGLlOIG GoTHayaR mg! R'LuH s'UhN H'Nog
nGHaFH'dRn Ah fHTagN NIlGh'ri NiLgh'ri yA oRr'eyaR k'yArnAkog gebYar
ep iLyAA cH' grah'N YhuPAdGH HuPaDgh 'FHalma llOigOR CH' f'CHteNFfyAR
k'yaRnAKAgL uAaaH! NwyAR NnNK'yarnakOth ah nAfLAtHg gotHA phlEgeTH
wgah'n uAaah eHyeAGl ThaRaNAk nAShaGGotH Kn'a CK'yArnAk 'FHaLMayaR
GoThA eBUMna lLOiGOr ceBuMNA eHye HLIRgh ron sHogGNyth R'LuH
VULgTLagLN NoG Gof'NN nAEP iLyaAaGL ph'HUPaDGhNYth y'BThNk
sHtUNggLIAgL ShAGG! shAGg f'y'hah Ehye uLn LW'Nafh nNnHAfH'dRn
sYHa'n HrIi phlEgEth r'Luh yS'UhN YshoGg tHRoDnYth ROn nGuaAAH
naflk'yArnAK! Kn'a f'HlIRGH hAFh'drN yvULgTM kn'A ULnoth chTenff
GoF'NN hAI VUlGTmnyth eE gof'nn NaN'GhFt CbuG IlyaA NgLUiagl oOBOshu!
s'uhN ooBoSHu YMnAhN'agL TharanaKagL GEB kN'A! yA lLoIG CphlEgETHoth
ph''bThnK haFh'DRn tHARaNAk! gnaiiHagl k'YaRNAk nafLk'YArNaK kn'A
EbumnaoG! mnAhN' Fm'LAtGh r'LuHYar gNaiIH ch' eBumNA ebuMNa!
NIlGh'RiyAR 'fhalmaAgl! GNAIiHOth! PHleGEthYaR gothA shagG
VulgtlaGln y'hah! NAShAgg roN Nog R'luH SHtunGgLI SyHa'n GoTHa
PHlegEThOtH nAFLgnaIIh NW aHagl n'GHft Kn'a MNAhn' lW'NaFH tHroD!
PH'Lloig atHg nnnY'haH FhTagN! nOG naFLmnAHn'oR ShuGG gNaiIh NGoRR'e
syHA'n F'fTaGHUnytH GoKA shoGg NGLi'hee phLegetH haFH'dRn F'sgN'WahL
nog GOtHa SyHA'N eBUMna HUpADGh hUPaDghNyTH 'btHNK sHAgGOR thROd
ulnor Fm'lATghNYTh FtAghUAgL R'luH YEe! NNnsHAGg SteLl'BSnA!
CHTeNFf nAFLtHAraNAK uAaah N'gHftoth ShOgg haiAGL ah NaFL'FHaLMa
VULgTM CfhtAGn Lloig ebuMnaOr shaGG ilYaa N'GHa Nw HAiAGl F'kAdiSHtu
gOf'nNOr h'sll'hA shTUNgGLI mG clW'nAFhagL nGLUi ebuMnAAgl r'luh
Li'hee K'yARNak ygOtHAoR goThAYaR H'noG! GOf'nnor hUpaDgHAgL
WgAH'NOtH Gof'nN naAH nNn'FhALMa naR'LuH Ng'Ai ShuGG gokAOg Ep
ynilgh'Ri oOboShu Naflsll'Ha f'ilyAa GEb Shogg LLll KAdISHtUoR
ooBOshu nggRah'n 'FHAlMA gRAH'NyAR atHg N'ghayAr! PH'zHRo SHTUnGGli
H'vuLGTlAgLn gOKa fhtagN n'ghA HRiIAgL gOthA KaDiShtU FhtaGn sYHa'n
ahotH naflShAGG gOF'NN cHTenff! n'gHft LI'HeEAGl S'UHNoTH kn'Aog
NAFloRr'e SYHa'N h'aTHG HrIi phlEgeth nnnNog VulgtlAglnNYtH Ron
'fHAlMA nw naflWgaH'n! NW tHArAnaK GRAh'noth uH'e nw chaFH'Drn
VulGTLaGLN! WgAH'n sLl'ha NAflk'YArNAk SgN'waHLoTh LLoIG sTEll'BSNa
'fhalmA grAh'n vuLGtlAglN PH'zHrO shTUngGlIagl k'YArnAK YhRIIOr
SHtunGGlI NasLL'hA HAi 'Ai CzhRO sgn'WahL EhyE AHaGl shugg lI'hee
cHtEnFf f'mg BuG LlOig kn'AOr fM'LAtGH pH'Lw'NaFHoTH n'gHAYar
sll'hAnytH nnNvUlGTM EhYE nILgh'rI! SgN'Wahl NGuAAAH gNAiIh
ph'sTell'BsNa ah thRodNyTh GoThaoTh hAFh'DrN oOboShu GeB
nAflsTeLL'BsNaOth Ee Ysyha [Text ends abruptly.]

(please note, I considered it important enough that the Elder Gods be able to read this review. Should you wish to as well, you will have to run above program).

Quota (1.5.1) *****

This is one of the truly venerable modules available on the CPAN, created some time around the release of 5.000 or 5.001, which is part of the reason why it has a single word name. These days, it would probably wind up with a name like Filesys::Quota, so when you look upon this module, respect its heritage.

The interface also shows its age. Like the stat function, the main quota query function returns a long list of values whose place and purpose can never be remembered; you have to look at the documentation. But that's ok too, because the documentation is well written and easy to understand. It does assume that you already have an understanding of how quotas work. Where needed, the documentation points out issues with specific operating systems.

It also handles newer architectures that allow quotas to be associated with groups (and not just users).

At some point a couple of years ago, I tried to use Quota on a Solaris 5.8 box, and the compilation failed with some sort of error to do with header files. I sent a message off to the author explaining the situation. He replied a couple of days later suggesting something to try (adding an #ifdef in a header file of the package, if memory serves). As it happened, I had fallen back to screen-scraping the output of repquota(1), so I wasn't particularly fussed. And in any event, when I had another shot at installing it some months down the track, it installed on 5.8 without a hitch.

All in all, a very useful module in its own right, while having a remarkable historical interest at the same time. Ask yourself, will you still be supporting your modules in 2017?