<?xml version="1.0" encoding="windows-1252"?>
<node id="562735" title="Don’t Repeat Your… version number" created="2006-07-20 19:01:33" updated="2006-07-20 15:01:33">
<type id="120">
perlmeditation</type>
<author id="114691">
Aristotle</author>
<data>
<field name="doctext">
&lt;p&gt;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|&lt;tt&gt;module-authors&lt;/tt&gt; mailing list]:&lt;/p&gt;

&lt;readmore&gt;
&lt;blockquote cite="http://www.mail-archive.com/module-authors@perl.org/msg04426.html"&gt;
&lt;p&gt;&amp;#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 &lt;tt&gt;Acme::MetaSyntactic::tour_de_france&lt;/tt&gt; for an example):&lt;/p&gt;

&lt;code&gt;
my %const = map  { s/\s+//; $_ }
            map  { split /\s*=&gt;\s*/ }  
            grep { /=&gt;/ }              
            map  { split /\n/ } &lt;&lt; '=cut';

=pod

This module uses the following constants:
										
	bang_eth =&gt; 1                       
	biff     =&gt; 2
	krunch   =&gt; 3

=cut
&lt;/code&gt;
&lt;/blockquote&gt;
&lt;/readmore&gt;

&lt;p&gt;Now, that in itself is a damn cool hack.&lt;/p&gt;

&lt;p&gt;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 &lt;tt&gt;$VERSION&lt;/tt&gt; line.&lt;/p&gt;

&lt;p&gt;Turns out, this is actually very tricky because you have to get the [wp://Polyglot (computing)|polyglot] understood by three different tools:&lt;/p&gt;

&lt;readmore&gt;
&lt;ol&gt;

&lt;li&gt;&lt;p&gt;&lt;tt&gt;perl&lt;/tt&gt;, which uses a very simple rule for what it regards as non-POD. Easy – see above.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;[cpan://ExtUtils::MakeMaker] – or to be precise, its &lt;tt&gt;MM-&gt;parse_version&lt;/tt&gt; method –, which is what a lot of modules use to extract version information from modules. Oh dear.&lt;/p&gt;
&lt;p&gt;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 &lt;em&gt;a single line&lt;/em&gt;, which will be [doc://eval]’ed in isolation.&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

&lt;p&gt;In other words, something like this, which was my first thought, won’t work:&lt;/p&gt;

&lt;code&gt;
$VERSION = ( &lt;&lt;'=cut' =~ /\b\d+\.\d+\b/ );

=head1 VERSION

This document describes Some::Module 0.1

=cut
&lt;/code&gt;

&lt;p&gt;If you try that, you will find that &lt;tt&gt;parse_version&lt;/tt&gt; will [doc://eval] just this:&lt;/p&gt;

&lt;blockquote&gt;&lt;code&gt;
$VERSION = ( &lt;&lt;'=cut' =~ /\b\d+\.\d+\b/ );
&lt;/code&gt;&lt;/blockquote&gt;

&lt;p&gt;Useless.&lt;/p&gt;

&lt;p&gt;I had to resort to treachery: reading MakeMaker’s source to find its weaknesses. And the weakness, it turns out, is that it uses &lt;tt&gt;/^=cut/&lt;/tt&gt; to stop skipping. Notice something? That matches too many things… Gotcha! You’re going down.&lt;/p&gt;

&lt;p&gt;Unfortunately, the single-line requirement means that the version number &lt;strong&gt;must&lt;/strong&gt; be on the same line as the string &lt;tt&gt;$VERSION&lt;/tt&gt;, which means we’ll &lt;em&gt;have&lt;/em&gt; to have that in the POD:&lt;/p&gt;

&lt;code&gt;
eval "package Some::Module; $_" for grep m/ = /, split /\n/, &lt;&lt;'=cut';

=head1 VERSION

=for fooling makemaker
=cut-feigned

This document describes Some::Module,
$VERSION = 0.1

=cut
&lt;/code&gt;

&lt;p&gt;Here, the heredoc operator on the first line sets &lt;tt&gt;perl&lt;/tt&gt; 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.&lt;/p&gt;

&lt;p&gt;The &lt;tt&gt;=for&lt;/tt&gt; 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 “&lt;tt&gt;fooling&lt;/tt&gt;”, for which a formatter is unlikely to ever be written.&lt;/p&gt;

&lt;p&gt;And what’s on the next line, the &lt;tt&gt;=cut-feigned&lt;/tt&gt;, makes &lt;tt&gt;parse_version&lt;/tt&gt; stop skipping and look for a line which sets &lt;tt&gt;$VERSION&lt;/tt&gt;.&lt;/p&gt;

&lt;p&gt;It works:&lt;/p&gt;

&lt;code&gt;
$ perldoc ./Some/Module.pm | grep VERSION
       This document describes Some::Module, $VERSION = 0.1
$ perl -MSome::Module \
       -le'print Some::Module-&gt;VERSION'
0.1
$ perl -MExtUtils::MakeMaker \
       -le'print MM-&gt;parse_version(shift)' Some/Module.pm
0.1
&lt;/code&gt;
&lt;/readmore&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p align="right" class="pmsig pmsig-114691"&gt;&lt;i&gt;Makeshifts last the longest.&lt;/i&gt;&lt;/p&gt;</field>
</data>
</node>
