brian has been a Perl user since 1994. He is founder of the first Perl Users Group, NY.pm, and Perl Mongers, the Perl advocacy organization. He has been teaching Perl through Stonehenge Consulting for the past five years, and has been a featured speaker at The Perl Conference, Perl University, YAPC, COMDEX, and Builder.com. Contact brian at [email protected].
Lately, I've been casting about for a better way to start a new module, which I have to do quite often for clients. Perl has tools to do this already, but none of them did what I needed them to do. At the moment, I'm using the tpage and ttree programs from Template Toolkit, and it's working nicely.
I've created modules in many different ways over the years, and it shows. If you look in the BackPAN (the historic record of all CPAN uploads), you can watch my modules develop and know where I learned about a certain feature or fell in love with a particular structure. For instance, I just started adding META.yml and README files to my distributions. I can get around the mish mash of styles if I create distributions the same way every time. Perl has several tools to do this.
The h2xs program was the first one that I used to create new distributions, and the mantra at the time was "Start with h2xs." Indeed, as of Perl 5.8.4, that phrase is still in perlnewmod. The h2xs utility started as a way to convert C header files to XS modules so a module could connect a Perl script to C code. As time went on and there were fewer C libraries to convert, people used h2xs to create module distribution skeletons.
$ h2xs -AX -n Stonehenge::Foo Writing Stonehenge/Foo/Foo.pm Writing Stonehenge/Foo/Makefile.PL Writing Stonehenge/Foo/README Writing Stonehenge/Foo/t/1.t Writing Stonehenge/Foo/Changes Writing Stonehenge/Foo/MANIFEST
I used h2xs for several years, and I went through the pain of converting this skeleton into what I actually wanted: moving the *.pm files into a lib directory, replacing the skeleton *.t file (with its nondescript name), and then editing all of the files. I really wasn't saving any time. Although I haven't used h2xs for a couple of years, it would be even more work now since it uses newer features that aren't compatible with older versions of Perl (our(), use warnings).
Surely other people must have the same problem. There are enough Perl programmers out there that one of them must have hated h2xs enough to create their own module-starter tool.
The ExtUtils::ModuleMaker module came along around 2001. R. Geoffrey Avery had the same complaints about h2xs, so he created something to fix it. It got rid of all of the XS and Autoloader functionality, so if you used the -AX switches with h2xs, you could use ExtUtils::ModuleMaker. It eventually added a modulemaker program to use interactively.
Unfortunately, modulemaker was too much work for me. It does a good job organizing and collecting the information it needs to collect, but my name and other information stay the same, so I don't want to enter it every time I run the program. I could use the ExtUtils::ModuleMaker module if I wanted to write a program or add to the module file, but there are easier ways to do that.
Most of my module distributions look the same at the start, or at least I want them, to. I use the same POD structure, the same contact information (just my e-mail address), and many other things. There really isn't much programming there, I just need to change a few things in the same, basic template.
For a short time, I created my own skeleton structure, and out of laziness (the bad kind, not the virtue), I would simply copy that entire directory to a new directory, then go through it to edit things. The downside is apparent, and it was apparent to me even as I was doing it: I had to ensure that I edited everything I needed to edit. I was manually processing templates. Leave it to a human to do something and it's not going to get done most of the time.
Last year, I wrote (and wrote about in TPJ) the scriptdist program, which I created to build distributions for scripts. I needed to build distributions around a lot of standalone scripts, and I created scriptdist to do that. I didn't make it flexible enough to create module distributions, though. While I was looking around for other code that might do the same thing, I ran into "mmm" (my module maker) by Mark Fowler. It's a short program that uses Template Toolkit to build the right files. Mark even steals a little bit from ExtUtils::ModuleMaker.
Before I can use mmm, though, I need to create a directory of templates that it can process. Once I have that, I run mmm from the command line to get my processed module directory. But if I'm going to create a directory of templates, I really don't need mmm.
./modules ./modules/lib ./modules/lib/Foo.pm ./modules/Makefile.PL ./modules/README ./modules/t ./modules/t/load.t ./modules/t/pod.t ./modules/t/prereq.t ./modules/t/test_manifest
My templates are very simple, and most only need to replace very simple tokens, such as my name, e-mail address, and the name of the distribution or module. My initial README template is about as complicated as it gets. The placeholders in [% %] are in Template Toolkit syntax:
$Id: README,v 1.1 2004/09/08 00:25:41 comdog Exp $ You can install this using in the usual Perl fashion: perl Makefile.PL make make test make install The documentation is in the module file. Once you install the file, you can read it with perldoc. perldoc [% namespace %] If you want to read it before you install it, you can use perldoc directly on the module file. perldoc lib/[% dist-name %] This module is also in CVS on SourceForge http://sourceforge.net/projects/brian-d-foy/ Enjoy, [% name %], [% email %]
The Template Toolkit comes with a wonderful tool called "ttree" that turns a directory of templates into a new directory of processed files. I put into a file everything I need to configure, including the source directory.
A configuration file has everything I need, and is just a Template Toolkit configuration. I tell it what to copy and what to ignore. I also define values for the template placeholders:
lib = includes ignore = .DS_Store ignore = \b(CVS|RCS)\b copy = \.(png|gif|jpg|ico|pdf)$ src = /Users/brian/templates/modules/stonehenge dest = /Users/brian/Desktop define [email protected] define name="brian d foy"
I can create different configuration files, even using different source directories (whereas mmm assumes a single source, which made it less attractive). I can have one set of templates for my public work that I release to CPAN, and another set that I use for Stonehenge (or even separate directories for each client):
ttree -f local-config depend name=Local::Baz ttree -f cpan-config depend name=CPAN::Bar ttree -f stonehenge-config depend name=Stonehenge::Foo
I can shorten these with little shell scripts to do most of the typing for me:
#!/bin/sh ttree -f stonehenge-config depend namespace=$1
I save that in a script named "shdist." When I need a new module for some Stonehenge work, I run my shell script
shdist Stonehenge::Foo
The only thing left is additional files that I add to a distribution. When I need to generate a single file, I can use Template Toolkit's tpage program. Indeed, the code example in the tpage program shows it processing a template file for a new module:
tpage define author="Andy Wardley" skeleton.pm > MyModule.pm
I can do that, too, with a little script:
#!/bin/sh tpage define namespace=$1 /Users/brian/templates/modules/stonehenge/lib/Foo.pm
That's not even good enough, though. Why should I have a bunch of different scripts to do the same thing?
#!/usr/bin/perl use File::Basename qw(basename); my $invoked_as = basename( $0 ); my %configs = ( shmod => 'stonehenge-config', mymod => 'my-config', cpanmod => 'cpan-config', default => 'cpan-config', ); my $config = $configs{$invoked_as} || $configs{default}; exec "/usr/local/bin/ttree", "-f $config", "depend namespace=$ARGV[0]\n";
I save this script, then create symlinks to it. The name of the script that I invoked shows up in $0. If I use one of the symlink names to invoke it, the symlink name shows up in $0, so I can tell how I invoked the program. I get the basename of that and use it to pull a configuration file out of the %configs hash. I then use exec() to turn my Perl process into a ttree process. The module name is the first argument and shows up in $ARGV[0]. This way, the command
shmod Foo::Bar
is really just
/usr/local/bin/ttree -f stonehenge-config depend namespace=Foo::Bar
So far, nothing in my solution really cares what is in the templates directory, so this process doesn't care why I'm processing the templates. I can use the same thing, with different templates and configuration files, to do something else, such as processing a template-based website, writing a book, or generating customer invoices. I don't need the tools that only generate modules when I have Template Toolkit.
References
ExtUtils::ModuleMaker:
http://search.cpan.org/dist/ExtUtils-ModuleMaker.
Template Toolkit: http://search.cpan.org/dist/Template.
h2xs: http://www.perldoc.com/perl5.8.4/bin/h2xs.html.
mmm: http://unixbeard.net/svn/mark/homedir/bin/mmm.
TPJ