The other day, BooK posted the following to the module-authors mailing list:

[Y]ou could also simply expose the information in the documentation, and fetch it from there: (a trick I discovered thanks to Abigail’s additions to Acme::MetaSyntactic, see the upcoming Acme::MetaSyntactic::tour_de_france for an example):

my %const = map { s/\s+//; $_ } map { split /\s*=>\s*/ } grep { /=>/ } map { split /\n/ } << '=cut'; =pod This module uses the following constants: bang_eth => 1 biff => 2 krunch => 3 =cut

Now, that in itself is a damn cool hack.

But it immediately set my mind thinking about how to use it for the one thing that always annoys me about module maintenance: updating the module version in both your POD and on the $VERSION line.

Turns out, this is actually very tricky because you have to get the polyglot understood by three different tools:

  1. perl, which uses a very simple rule for what it regards as non-POD. Easy – see above.

  2. POD formatters, which use even simpler rules for what they regard as POD. No problems here, and it’s what makes Abigail’s trick possible.

  3. ExtUtils::MakeMaker – or to be precise, its MM->parse_version method –, which is what a lot of modules use to extract version information from modules. Oh dear.

    It skips POD using… shall we say, simplistic rules, much like POD formatters, so it will tend to successfully ignore precisely the things that a POD formatter will accept. It will also accept only a single line, which will be eval’ed in isolation.

In other words, something like this, which was my first thought, won’t work:

$VERSION = ( <<'=cut' =~ /\b\d+\.\d+\b/ ); =head1 VERSION This document describes Some::Module 0.1 =cut

If you try that, you will find that parse_version will eval just this:

$VERSION = ( <<'=cut' =~ /\b\d+\.\d+\b/ );


I had to resort to treachery: reading MakeMaker’s source to find its weaknesses. And the weakness, it turns out, is that it uses /^=cut/ to stop skipping. Notice something? That matches too many things… Gotcha! You’re going down.

Unfortunately, the single-line requirement means that the version number must be on the same line as the string $VERSION, which means we’ll have to have that in the POD:

eval "package Some::Module; $_" for grep m/ = /, split /\n/, <<'=cut'; =head1 VERSION =for fooling makemaker =cut-feigned This document describes Some::Module, $VERSION = 0.1 =cut

Here, the heredoc operator on the first line sets perl up to treat the entire following section as a string. In that string we look for a line with an equals operator, then eval it.

The =for line makes POD formatters ignore what’s on the next line, unless one of them thinks it’s the formatter for the output format called “fooling”, for which a formatter is unlikely to ever be written.

And what’s on the next line, the =cut-feigned, makes parse_version stop skipping and look for a line which sets $VERSION.

It works:

$ perldoc ./Some/ | grep VERSION This document describes Some::Module, $VERSION = 0.1 $ perl -MSome::Module \ -le'print Some::Module->VERSION' 0.1 $ perl -MExtUtils::MakeMaker \ -le'print MM->parse_version(shift)' Some/ 0.1

Would I use this is actual CPAN-published code? I don’t know. But you have to admit, it is really quite a fun hack.

Makeshifts last the longest.