perlmeditation
Aristotle
<p>The other day, [BooK] [http://www.mail-archive.com/module-authors@perl.org/msg04426.html|posted the following] to the [http://lists.cpan.org/showlist.cgi?name=module-authors|<tt>module-authors</tt> mailing list]:</p>
<readmore>
<blockquote cite="http://www.mail-archive.com/module-authors@perl.org/msg04426.html">
<p>&#91;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 [cpan://Acme::MetaSyntactic], see the upcoming <tt>Acme::MetaSyntactic::tour_de_france</tt> for an example):</p>
<code>
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
</code>
</blockquote>
</readmore>
<p>Now, that in itself is a damn cool hack.</p>
<p>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 <tt>$VERSION</tt> line.</p>
<p>Turns out, this is actually very tricky because you have to get the [wp://Polyglot (computing)|polyglot] understood by three different tools:</p>
<readmore>
<ol>
<li><p><tt>perl</tt>, which uses a very simple rule for what it regards as non-POD. Easy – see above.</p></li>
<li><p>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.</p></li>
<li>
<p>[cpan://ExtUtils::MakeMaker] – or to be precise, its <tt>MM->parse_version</tt> method –, which is what a lot of modules use to extract version information from modules. Oh dear.</p>
<p>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 <em>a single line</em>, which will be [doc://eval]’ed in isolation.</p>
</li>
</ol>
<p>In other words, something like this, which was my first thought, won’t work:</p>
<code>
$VERSION = ( <<'=cut' =~ /\b\d+\.\d+\b/ );
=head1 VERSION
This document describes Some::Module 0.1
=cut
</code>
<p>If you try that, you will find that <tt>parse_version</tt> will [doc://eval] just this:</p>
<blockquote><code>
$VERSION = ( <<'=cut' =~ /\b\d+\.\d+\b/ );
</code></blockquote>
<p>Useless.</p>
<p>I had to resort to treachery: reading MakeMaker’s source to find its weaknesses. And the weakness, it turns out, is that it uses <tt>/^=cut/</tt> to stop skipping. Notice something? That matches too many things… Gotcha! You’re going down.</p>
<p>Unfortunately, the single-line requirement means that the version number <strong>must</strong> be on the same line as the string <tt>$VERSION</tt>, which means we’ll <em>have</em> to have that in the POD:</p>
<code>
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
</code>
<p>Here, the heredoc operator on the first line sets <tt>perl</tt> up to treat the entire following section as a string. In that string we look for a line with an equals operator, then [doc://eval] it.</p>
<p>The <tt>=for</tt> 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 “<tt>fooling</tt>”, for which a formatter is unlikely to ever be written.</p>
<p>And what’s on the next line, the <tt>=cut-feigned</tt>, makes <tt>parse_version</tt> stop skipping and look for a line which sets <tt>$VERSION</tt>.</p>
<p>It works:</p>
<code>
$ 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
</code>
</readmore>
<p>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.</p>
<p align="right" class="pmsig pmsig-114691"><i>Makeshifts last the longest.</i></p>