Stories
Slash Boxes
Comments
NOTE: use Perl; is on undef hiatus. You can read content, but you can't post it. More info will be forthcoming forthcomingly.

All the Perl that's Practical to Extract and Report

use Perl Log In

Log In

[ Create a new account ]

Aristotle (5147)

Aristotle
  pagaltzis@gmx.de
http://plasmasturm.org/

Blah blah blah blah blah [technorati.com]

Journal of Aristotle (5147)

Thursday July 20, 2006
06:27 PM

Don’t Repeat Your… version number

[ #30358 ]

The other day, Philippe Bruhat (AKA 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/ );

Useless.

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”, which 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/Module.pm | 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/Module.pm
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.

The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.
 Full
 Abbreviated
 Hidden
More | Login | Reply
Loading... please wait.
  • It's a cool hack, but I use something else to avoid having to modify the version number twice. I don't modify the version number at all - I let CVS do that for me:

    our ($VERSION) = q $Revision: 1.0$ =~ /[\d.]+/g;

    =head1 VERSION

    $Revision: 1.0$
    The previous line starts with a significant space, but <ecode> decided to ignore it.

    =cut

    And since I use a template to start a module with, I never have to remember to set the version number.

    Not a cool hack, but something I do use for CPAN modules.

    • The trouble with that is that you're letting CVS control your version numbers. That's not something I'm particularly comfortable with doing.

      -Dom

    • Yeah, over on PerlMonks, David Golden suggested something conceptually very similar, where he uses Pod::WikiDoc [cpan.org] to handle POD that looks like this:

      =begin wikidoc

      This documentation refers to version %%VERSION%%.

      =end wikidoc

      And then he has a few custom rules in Build.PL as documented in Pod::WikiDoc::Cookbook [cpan.org], so that Some::Module->VERSION is substituted into place at ./Build dist time.

      I like that approach a little better for the reason Dominic mentioned – I don’t want to leave versio

  • Have you tested this hack with Module::Build?