24 days of Perl code from RJBS! Feed

Glory to Pod in the Highest

Pod::Weaver - 2009-12-18

Glory? To Pod?

Oh, I don't know. I have really mixed feelings about Pod. It's really easy to write, most of the time, and the toolchain for it is pretty fantastic. You can convert Pod to text, man pages, HTML, LaTeX, PDF, and all kinds of other formats. Then again, there's too much vertical whitespace. Lists are a colossal pain to write. Beyond the annoyances of Pod itself, the Pod of Perl modules uploaded to the CPAN is expected to contain a bunch of boring filler that gets copied around.

A number of other documentation systems tempted me with the idea of writing less and getting information, like "this bit of documentation associates with this method," giving you things like Python's help builtin. I set about finding a way to write a lot less Pod that would tell me a lot more about my code and look indistinguishable to consumers. There were a few stops and starts and a lot of spun-off code, and a very motivating grant from The Perl Foundation. The end result was Pod::Weaver, which is something like a templating system for Pod.

Pod Templates with Pod::Weaver

Like Email::MIME::Kit, though, Pod::Weaver doesn't just take a document and fill in strings. It takes a description of the goal output and tries to build something matching that description. For example, here's roughly the default template for Pod::Weaver:


1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 

 

[@CorePrep]

[Name]
[Version]

[Region / prelude]

[Generic / SYNOPSIS]
[Generic / DESCRIPTION]
[Generic / OVERVIEW]

[Collect / ATTRIBUTES]
command = attr

[Collect / METHODS]
command = method

[Leftovers]

[Region / postlude]

[Authors]
[Legal]

 

This describes the document to be created; every section of the configuration will try to produce a section in the output Pod, except for @CorePrep, which is a plugin bundle, which could provide a bunch of sections or other kinds of plugins. In the case of CorePrep, we're adding plugins to ensure that we have an object tree that's something like what we expect from a Pod document, as Pod::Elemental starts with a very bare-bones structure that can be tedious to work with.

After that, we try to make sure there are NAME and VERSION sections, then we look for prelude region, and so on. The input document might look like this:


1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 

 

package Tree::Christmas;
# ABSTRACT: a binary tree decorated with baubles and popcorn

=begin :prelude

=head1 WARNING

This code may cause your home to smell like pine needles.

=end :prelude

=head1 DESCRIPTION

This library provides a framework for hanging tinsel, popcorn, and other
forms of garland. In some locales, pickles may also be nested in a
Tree::Christmas.

=method ignite_lights

This method...

=method new

...

=attr pickle_jar

This is an arrayref of pickles.

 

If we want to include things like a name, version, license, and author in the output, we'll need to provide those. We'll do that with something like this:


1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
10: 
11: 

 

my $input_pod = Pod::Elemental->read_string( $the_sample_above );

my $weaver = Pod::Weaver->new_with_default_config;

my $output_pod = $weaver->weave_document({
  authors => [ 'Ricardo Signes <rjbs@example.com>' ],
  license => Software::License::Perl_5->new({ ... }),
  version => '1.234',
  pod_document => $input_pod,
  ppi_document => $input_ppi, # (don't worry, keep reading)
});

 

That gets us a new Pod::Elemental::Document which, when turned into a Pod stirng, looks something like this:


1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 

 

=head1 NAME

Tree::Christmas - a binary tree decorated with baubles and popcorn

=head1 VERSION

version 1.234

=head1 WARNING

This code may cause your home to smell like pine needles.

=head1 DESCRIPTION

This library provides a framework for hanging tinsel, popcorn, and other
forms of garland. In some locales, pickles may also be nested in a
Tree::Christmas.

=head1 ATTRIBUTES

=head2 pickle_jar

This is an arrayref of pickles.

=head1 METHODS

=head2 ignite_lights

This method...

=head2 new

...

=head1 AUTHORS

Ricardo Signes <rjbs@example.com>

=head1 COPYRIGHT AND LICENSE

This document is copyright (C) 1991, Ricardo Signes.

This document is available under the blah blah blah.

 

Not bad! We avoid typing nearly half of the output document, by lines -- and that's only part of the benefit. If we have five modules in our distribution, all that boilerplate is added everywhere for us, so multiply that savings across all our modules. Then, if ever we want to change our license or add an author or even change the overall layout of the output document, we only need to change our configuration in one place.

If you're wondering about the ppi_document input above, it's there because some sections can be built by analyzing the Perl content of the document, so you can automatically document things like included packages, defined subroutines, and so on. (You could also write something to compile the code and look at the symbol table or meta layer, although no one has done that yet!) Because "load a .pm file, break it into Pod and Perl, rewrite the Pod, and write it back out" is such a common pattern, there's even Pod::Elemental::PerlMunger, which takes care of everything but the "rewrite the Pod" step for you, leaving you free to focus on configuring your weaver or other rewriting tool.

That's the interface on which Dist::Zila::Plugin::PodWeaver is built. It gets all its inputs from your Dist::Zilla configuration and then rewrites all your Perl modules with Pod::Weaver. If you're using Dist::Zilla, all you need to do to get all the rewriting seen above is add the line [PodWeaver] into your dist.ini.

Extending Pod::Weaver

Pod::Weaver tries to make it easy to add new behavior. For example, a simple plugin to add an "Acknowledgements" section is only about a dozen lines of code. A plugin to integrate Pod::Weaver with Pod::WikiDoc is only about six lines of code. This allows the transformation of the easy to type:


1: 
2: 
3: 
4: 

 

=for wikidoc
* bell
* book
* candle

 

...into the grumble-inducing:


1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
10: 
11: 
12: 
13: 
14: 
15: 

 

=over 2

=item *

bell

=item *

book

=item *

candle

=back

 

A recurring theme of this set of articles has been my hatred for needless typing and boilerplate content, and Pod::Weaver has gone a long way to letting me avoid both. Hopefully you can use it to avoid them now, too, and spend more time solving problems.

See Also