Day 10 – Perl 6 Pod

Whenever you write code, you also write documentation, right? Wait, don’t answer that! Let me just pretend that you said “yes”.

As with Perl 5, Perl 6 has a built-in documentation format called Pod. In Perl 5, POD was short for “Plain Ol’ Documentation”, but in Perl 6 we just call it Pod and there’s no acronym. One of the great things about Pod in Perl 6 is that the compiler parses Pod for you. The resulting structure is essentially a tree (really, an Array of trees).

Let’s take a look at a few examples …

use v6;

=begin pod

=head1 NAME

example1.p6 - It's a script to show you how Pod works in Perl 6!

=end pod

sub MAIN {
    say $=pod.WHAT;
    say $=pod[0].WHAT;
}

When I run this script I get:

$ perl6 ./bin/example1.p6 
(Array)
(Named)

The $=pod variable is a reference to the parsed Pod from the current file.

From the code above we can see that the $=pod variable contains an Array. Each element of that array is a Pod node of some sort, and those nodes in turn will usually contain more Pod nodes, and so on. Essentially, Pod is parsed into a tree that you can recursively descend and examine.

The $=pod variable is an array is because you can have several distinct chunks of Pod in a single file …

use v6;

=begin pod

=head1 NAME

example2.p6 - It's a script to show you how Pod works in Perl 6!

=end pod

sub MAIN {
    say $=pod.elems;
    say $=pod[0].WHAT;
    say $=pod[1].WHAT;
}

=begin pod

=head1 DESCRIPTION

This is where I'd put some more stuff about the script if it did anything.

=end pod

When we run this script we get:

$ perl6 ./bin/example2.p6 
2
(Named)
(Named)

So there are now two elements in $=pod, one for each chunk of Pod delimited by our =begin pod/=end pod pairs.

Pod Syntax in Perl 6

Up until this point I’ve shown you some Pod without actually explaining what it is. Let’s take a look at some valid Pod constructs in Perl 6 so you can get a better sense of how it looks, both when you write it and when you’re writing code to process it.

Perl 6 Pod is purely semantic. In other words, you describe what something is, rather than how to present it. It’s left up to code that turns Pod into something else (HTML, Markdown, man pages) to decide how to present any given construct.

Perl 6 Pod has quite a bit more to it than we can cover here, so see Synopsis 26 for all the details. We’ll cover some of the highlights and then look more closely at how to process Pod programmatically.

Blocks

Pod supports several different ways to declare a block. You can use =begin and =end markers …

=begin head1
Heading Goes Here
=end head1

Or you can use a =for marker …

=for head1
Heading Goes Here

These block styles allow you to include configuration information:

=begin head3 :include-in-toc
This will show up in TOC
=end head3

=for head4 :!include-in-toc
Not in the TOC

With a =for block, only the lines immediately following the =for is part of that block. Once there is an empty line, anything that follows is separate.

You can use an “abbreviated” block …

=head1 Heading Goes Here

… but abbreviated blocks don’t allow for configuration information. Like =for blocks, abbreviated blocks end at the first empty line.

There are many types of blocks, including table, code, I/O blocks (input and output), lists, and more.

Paragraphs

Text that isn’t explicitly part of a block is considered to be a paragraph block …

use v6;

=begin pod

=begin head1

NAME

=end head1

This is a paragraph block.

=end pod

sub MAIN {
    say $=pod[0].contents[1].WHAT;
}

The output will be (Para), because the second element of contents is a plain paragraph.

Lists

Lists are much simpler in Perl 6 than they were in Perl 5 …

=begin pod

=item1 The first item
=item1 The second item
=item2 This is a sub-item of the second item
=item1 The third item

Of course, you can also use the =for and =begin/=end form with list items too.

Formatting Codes

There are a variety of formatting codes available …

See L<http://design.perl6.org/S26.html> for details.

Some times you need to mark some text as C<code>,
B<being the basis of the sentence>,
or I<important>.

And More

There are a lot of new things with Perl 6 Pod, and if you want to learn more I encourage you to read Synopsis 26 for all of the gory details.

Processing Pod with Perl 6

As I said before, the Perl 6 compiler parses Pod in your program and makes it available to you via various variables. We already saw the use of $=pod, which contains all of the Pod in the current file. That pod is an array of Pod::Block objects. In future versions of Rakudo, you will also be able to get at specific types of Pod blocks by name with variables such as $=head1 or $=TITLE, but this is not yet implemented.

Let’s write a simple program to show us an outline of the Pod in a given file. For example, given some Pod like this …

=head1 NAME

...

=head1 DESCRIPTION

...

=head1 METHODS

...

=METHOD $obj.foo

...

=METHOD $obj.bar

...

 

… we want to print this output …

NAME
DESCRIPTION
METHODS
  $obj.foo (METHOD)
  $obj.bar (METHOD)

Here’s the code to do just that …

use v6;

sub MAIN {
    for $=pod.kv -> $i, $block {
        say "Block $i";
        recurse-blocks($block);
        print "\n";
    }
}

sub recurse-blocks (Pod::Block $block) {
    given $block {
        when Pod::Block::Named {
            if $block.name eq 'pod' {
                for $block.contents.values -> $b {
                    recurse-blocks($b);
                }
            }
            else {
                my $output = $block.contents[0].contents[0]
                    ~ " ({$block.name})";
                depth-say( 2, $output );
            }
        }
                
        when Pod::Heading {
            depth-say( $block.level,
                       $block.contents[0].contents[0] );
        }
    }
}

sub depth-say (Int $depth, Str $thing) {
    print '  ' x $depth;
    say $thing;
}

 

This code takes some shortcuts wherever it uses $block.contents[0].contents[0] by making the very big assumption that a block contains one element which is a single Pod::Block::Para. It then assumes that the Para block contains one element which is plain text.

In practice, blocks may contain arbitrarily nested blocks because of things like formatting codes, but this gives you an idea of how you might walk through the Pod tree.

Of course, because Perl 6 is still Perl, there’s a module for that! I’ve sketched out a preliminary module called Pod::TreeWalker that will do the tree walking for you, generating events as it finds different nodes. You provide a listener object that is called for each event.

GitHub Repo

All of the code shown in these examples is on GitHub. I’ve also written an additional script that uses Pod::TreeWalker to implement the outlining I demonstrate above.