Reviews by Chris Dolan

cpanratings
 

RSS

Math-RPN (1.09) *****

As of v1.09, Gabor Szabo cleaned up all of the problems I complained about 6 years ago in v1.08, so I give this simple library full marks. It could probably be implemented more efficiently with a hash of behaviors rather than if/elsif/elsif/elsif/.../else, but that's just an implementation detail.

Perl-Critic-Pulp (3) *****

This package has two straightforward and useful Perl::Critic plug-in policies: 1) ConstantBeforeLt warns about barewords before "<" because Perl can interpret the "<" as the beginning of a readline; 2) ProhibitNullStatements complains about extraneous semi-colons, which can be distracting.

The unit tests are quite thorough so I expect the false positive rate will be low.

Locale-Maketext-Lexicon (0.66) ****

This module lets you mix styles of localization in a single application. For example, my code all uses Locale::Maketext to insert localized strings into my code, but I use GNU Gettext .po files to store strings for the various languages because they're easier for translators than Locale::Maketext syntax. This module provides the glue to update the .po files from my loc(...) commands in my .tt and .pm files during development, and to transform the .po files into Locale::Maketext .pm files during deployment.

However, I found that I had some idiosyncracies in my workflow that didn't match the expectations of this module. I ended up using Locale::Maketext::Lexicon::Gettext->parse(), but wrote my own Locale::Maketext .pm exported from a Data::Dumper representation of that parsed data -- only about 20 lines of code, but a little unpleasant nonetheless. I use the parsed data to output my own homebrew .js localization files too.

Template-Toolkit (2.19) *****

CPAN is notorious for the number of different templating libraries available to let you insert dynamic content into static content. Template-Toolkit (aka TT) is the best of breed, in my opinion. I've tried a lot of them and, I'm embarrassed to say, even published my own template implementation.

The features that I like about TT are:

* It has a syntax that's just powerful enough to do complex presentation, but is deliberately not Perl to discourage you from writing real code in it.

* It compiles to real Perl code which makes it quite fast despite the complexity of it's syntax

* It has a fairly easy interface for adding new "VMethods" and plugins

* It has nice filter methods to let you do like so: [% var | html %], which lets you escape "<", ">" and "&" trivially. You can "pipe" your output through an arbitrary number of pluggable filters.

The biggest flaw in the implementation, in my opinion, is that all virtual methods are called in list context, even when it looks like you are working with scalars. This can produce surprising results when working with code that does "wantarray" tricks. For example you would expect the typical DBIx::Class code to count the number of database records would be: [% model.search(...).count %]. But, search() happens in list context so it returns rows instead of a ResultSet instance. Instead, you have to remember to write [% model.search_rs(...).count %] to force scalar context.

WebService-Validator-CSS-W3C (0.2) *****

This module lets you send some CSS (either standalone or embedded in HTML) to the jigsaw.w3.org CSS validation service. In principal, you can run your own local copy of that service and this module will let you point at it, but I've found Jigsaw to be too complication to run myself (unlike the W3C HTML validator).

The interface is fairly simple: send some CSS, and check $v->is_valid. If false, troll through $v->errors and $v->warnings.

This module's API is superficially similar to the one for WebService::Validator::HTML::W3C, but the small differences are frustrating, so that the two modules cannot be used polymorphically without a half-dozen conditionals to differentiate them. That's really my only complaint. Otherwise, this module deserves to be v1.0 instead of v0.2

WebService-Validator-HTML-W3C (0.22) *****

This package lets you send local HTML to the W3C validator and offers easy-to-use results to judge standards compliance of your markup. I use this as part of my test suite for web apps: I use Test::WWW::Mechanize::Catalyst and validate every page I touch. To do this, I run my own local copy of the W3C validator (to avoid burdening the real w3c server and to let me work offline) which this module supports quite nicely.

I've added my own MD5-keyed caching in front of this module since I check the same HTML over and over. I think it's appropriate that this module does not offer caching itself -- simple is good. This module made adding my own caching quite easy by letting me override the LWP::UserAgent instance that it uses internally with my own caching subclass.

There's also a corresponding WebService::Validator::CSS::W3C package which does similar work for CSS. They have fairly similar APIs, but some significant differences (e.g. one returns a list from $v->warnings, one returns an arrayref -- grr). It would be nice if those two packages matched their APIs up a little better to let scaffold code use them polymorphically.

SQL-Translator (0.09000) *****

Also known as "sql-fairy", SQL::Translator reads and writes many, many dialects of SQL and translates between. As such, this is profoundly useful. I use it to write database schemas in near-ANSI syntax and then translate that to 1) MySQL (which has a peculiar foreign key syntax), 2) SQLite and 3) DBIx-Class code. This saves me a ton of duplication and coordination.

Even better, there is SQL::Translator::Diff which lets you compare a schema.sql to the schema behind a live DBI connection and outputs CREATE, DROP and ALTER commands that you can run. I use this to generate the SQL to update servers to newer schemas when I upgrade applications. It's a little slow to run the diff, but it's much more convenient than keeping track of what version of the database schema is running on what dev machine.

I also love the included 'sqlt-graph' tool which can generate a PNG, SVG, etc. representation of your schema. This is profoundly useful when discussing the data relations with developers/users who are not SQL-fluent.

Although the version number is only '0.09000', this package is definitely production-ready. I've been using it continuously for almost two years now.

Chart (2.4.1) *****

This package is easy to learn and is handy for making simple (or not-so-simple) charts and graphs of arbitrary datasets with just a little bit of coding. It uses GD as a backend, so it gets a lot of choices for output image format for free. It has convenience methods for PNG and JPEG output -- for others, you request a GD::Image instance and do it yourself. That option also allows you to do arbitrary additional scribbling on the image as you like, which can be handy.

My small complaints are:

* The complete docs are in PDF format. Only a summary is in POD. However, this makes some sense because it isn't feasible to embed images in POD.

* Doesn't support GD's stringFT routines for working with the FreeType library to use arbitrary TrueType fonts (I have a private patch to rectify this if anyone is interested)

* Spews version number warnings on startup

* The behavior of some of the axis options (like include_zero) are highly non-intuitive

* The scalar_png method is broken, so you have to write to a file and then read back to send out the web if you can't send straight to a filehandle.

* The source code has a lot of copy-paste redundancy and could use a thorough refactoring pass, IMHO.

IO-stringy (2.110) *****

I'm just reviewing IO::ScalarArray, one of several modules in the IO-stringy package. This module emulates a filehandle, but reads from or writes to an in-memory array. Like most of the IO:: modules, the interface is trivial because it behaves quite like a glob filehandle. The only unique method you need to know is aref().

When writing a large amount of data a bit at a time, it's more efficient to append to a list and join() at the end than to repeatedly append to a string. So, I'm using IO::ScalarArray instead of IO::String or IO::Scalar for my current task of writing a potentially large file in memory. In this case, I'm handing my IO::ScalarArray instance to Text::CSV_XS and it works great.

Catalyst-Plugin-CustomErrorMessage (0.02) *****

This replaces the default Catalyst HTTP 500 error page. I almost wrote my own, but checked CPAN at the last minute. Yay! This plugin is simple but completely adequate to the task.

Fuse (0.09) ****

Once you get used to it and read some of the libfuse documentation, Fuse.pm is easy to use and quite Perlish. As of v0.09 this module won't pass tests on Mac OS X but it works just fine with MacFUSE. A significant point of confusion (and not truly Fuse.pm's fault) is that the FUSE API itself is evolving and is accreting new methods. It's not at all clear without trial-and-error what's the minimum number of methods that you must implement to get your filesystem to work.

The unit tests are hopelessly Linux-centric. I suspect that they will have to be largely rewritten to support other operating systems (if, say, WinFUSE ever appears). As is, I had to do a ton of work just to get them to almost-pass on Mac.

UNIVERSAL-can (1.03) ***

Update: In the months since I wrote the review below, I've become convinced that I was partially wrong. I'll keep the review below intact, but revise my comments.

I think chromatic et al. are right about the problem. The use of UNIVERSAL::can and isa as a function is a very bad practice. I closes several doors for creative implementations of facades and the like.

The right solution to the problem is for everyone write your code like:

print eval { $obj->can('foo') } ? 'yes' : 'no';
The eval is the key that I overlooked when I wrote my earlier review.

However, I still think that the implementations of UNIVERSAL::isa and UNIVERSAL::can are highly unfortunate and aggressive, leading to lots of bad feelings instead of solving the problem. I believe that the authors are trying to solve a legacy and social problem via questionable technical means. My primary complaint is the breakage that arises from the emitted warnings, especially in test code. I really don't want to have to say "no warnings 'UNIVERSAL::can'" in *every* program I write...

---------

This is a poor module that should either be rewritten or moved to the ACME namespace. It's better than it's sibling, UNIVERSAL::isa, in that it's documention is good, but it's still a very bad solution to a known problem.

This module asserts that it's better that programs should crash than that authors call UNIVERSAL::can as a function. The rationale is good -- that calling can() as a function breaks OO-style modules which may override the can() method call -- but given that Perl 5 cannot call methods on unblessed objects means that the functional form is the only useful form of can() without a lot of extra code.

Here's a case where can() called as a function works where the method syntax fails:

my $foo = undef;

print UNIVERSAL::can($foo, "new") ? "yes" : "no"; # prints "no"

print $foo->can("new") ? "yes" : "no"; # dies with "Can't call method "can" on an undefined value"

Test-WWW-Mechanize (1.08) *****

This module is a highly useful tool for testing web applications. The documentation has a few weaknesses and I had to go to the source code a couple of times to figure out how to use the methods correctly. But otherwise, I have nothing but praise for this module.

The author is VERY responsive. How refreshing! :-)

WWW-Mechanize-CGI (0.2) *****

This is a very clever extension to WWW::Mechanize that allows you to simulate a CGI environment without actually running a web server. Instead, the module (via the HTTP::Request::CGI helper) sets up the proper CGI envvars and runs either a CGI file or a wrapper function.

My only complaint (and that's high praise for a v0.2!) is that there is no cookbook for commonly used CGI idioms. I've posted a couple of hints myself at annocpan.org/

Acme-Test-Weather (0.2) *

This comical module allows you to add some unpredictability to your regression tests. For example, if you want your tests to fail on rainy days, you would add the following to your test.pl or t/*.t file:

SKIP: {

eval { require Acme::Test::Weather; };

skip("can't determine the current weather", 1) if ($@);

isnt_rainy();

}

This module looks very easy to use and the code is obvious and well-documented. Like many of its Acme siblings, this module makes good reuse of other CPAN code.

Unfortunately, it doesn't install properly because it has a dependency that no longer exists on CPAN, CAIDA::NetGeoClient. I recommend to the author that it be updated to use Geo::IP instead. If that is done, I will happily give this module 5 stars.

Devel-Cover (0.55) *****

Devel::Cover watches your program run and tells you which lines of code are executed. It's especially handy for helping determine if your regressions tests actually exercise the code or not. It's also useful for profiling code for performance bottlenecks.

The easiest way to employ Devel::Cover is to install a recent version Module::Build and call the "Build testcover" action. Then open cover_db/coverage.html in your browser to get a summary of the results.

If you don't use Module::Build, then you can simply run arbitrary Perl code with "-MDevel::Cover". When execution is finished, you can run
a (poorly named, IMHO) command line tool called "cover" to emit reports.

UNIVERSAL-isa (0.04) *

This is a poor module that should either be rewritten or moved to the ACME namespace.

This module asserts that it's better that programs should crash than that authors call UNIVERSAL::isa as a function. The rationale is good -- that calling isa() as a function breaks OO-style modules which may override the isa() method call -- but given that Perl 5 cannot call methods on unblessed objects means that the functional form is the only useful form of isa() without a lot of extra code.

Here's a case where isa() called as a function works where the method syntax fails:

my $foo = undef;

print UNIVERSAL::isa($foo, "Foo") ? "yes" : "no"; # prints "no"

print $foo->isa("Foo") ? "yes" : "no"; # dies with "Can't call method "isa" on an undefined value"