Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Web Development

Making New Distributions


December, 2004: Making New Distributions

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


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.