Write Pod That Parsers Can Read Without Guesswork
You will finish with a small Perl documentation file that podchecker accepts, plus a repeatable way to check the parts that a formatter cannot infer safely. Pod is usually forgiving enough to render while still being structurally ambiguous. The habits here make the source predictable for HTML, terminal and other formatters.
The route
Jump straight to the step you need, or tick off Done means at the end.
Prerequisites: Perl and the perl-doc package. This guide was checked with Perl v5.38.2 and perl-doc 5.38.2-3.2ubuntu0.6 on Ubuntu. Allow about 15 minutes. You do not need root for the examples.
Checkpoint: create a Pod block
Make a scratch file in
/tmp, and put the documentation at the start of a Pod block.cat > /tmp/widget.pod <<'POD' =pod =head1 NAME widget - inspect a widget file =head1 SYNOPSIS widget [OPTIONS] FILE =head1 DESCRIPTION The widget command reads FILE and reports its basic properties. =cut PODCheck the file before adding more content.
podchecker /tmp/widget.podA successful check reports that the file has no Pod errors or warnings. A Pod block begins with a line whose first character is
=followed by a letter. It ends at=cut, or at end of file. A file containing only Pod is valid; Pod can also be embedded in a Perl source file.
Checkpoint: keep paragraph types distinct
Pod decides what a paragraph is from its first line. A line beginning with a recognised command creates a command paragraph. A line beginning with a space or tab creates a verbatim paragraph, where whitespace matters. Anything else is an ordinary paragraph. Blank lines separate paragraphs; spaces and tabs on an otherwise empty line count as blank too.
That first-character rule is an easy place to lose time. Do not indent =head1, =item or =cut. Do indent literal command examples, because indentation is what makes them verbatim:
=head1 EXAMPLE
printf "hello\n";
This is ordinary paragraph text.
The indented line is documentation text, not a command to be executed. Keep a blank line around it so later edits do not accidentally merge it with the surrounding prose.
Checkpoint: use commands with their intended shape
Use =head1 through =head6 for headings, =over and =back for a list or indented region, and =item for its entries. A bare =over has the same practical default indent as =over 4; a supplied value must be a positive number such as 3 or 3.5.
=head1 OPTIONS
=over 4
=item B<--verbose>
Print additional details.
=item B<--output> I<FILE>
Write the report to FILE.
=back
Choose one list shape and keep it consistent inside a region: bullet items such as =item *, numbered items starting at 1 and continuing without gaps, or descriptive item text. A region without any =item is an indented block. Do not put headings inside an =over ... =back region.
Commands are not interchangeable. =back takes no trailing text. =end must name the currently open =begin region, in the same nesting order. An unknown command should be treated as an error, so a typo such as =headd1 is not harmless prose.
Checkpoint: make formatting codes unambiguous
Formatting codes start with a capital letter. Use I<text> for emphasis, B<text> for bold text, C<code> for code, F<filename> for filenames, and L<...> for links. Escape a literal angle bracket with an E<...> character escape, or use the multiple-angle form when code contains comparison operators.
=head1 EXAMPLE
Call C<open> with a three-argument form:
open my $fh, '<', $path or die $!;
The expression C<$a E<lt>=E<gt> $b> means that $a is compared with $b.
For longer code, multiple angle brackets keep the closing markers visible:
C<<
open my $fh, '<', $path or die $!;
>>
Do not nest L<...> links. For a page and section, write a form such as L<perlfunc/"open">; for an external address, use L<https://example.invalid/>. A section-only link uses L</"OPTIONS">. The link text can be supplied before a vertical bar, as in L<the open documentation|perlfunc/"open">.
Checkpoint: separate target-specific data
Use =begin FORMAT ... =end FORMAT when content is intended for a particular processor, such as HTML. Inside a region whose name does not start with a colon, non-command paragraphs are data and are not parsed for ordinary Pod formatting codes. A colon changes that: =begin :FORMAT keeps the contents subject to normal paragraph processing.
=begin html
<em>Only an HTML formatter should use this.</em>
=end html
Regions must nest properly. If you open =begin outer, then =begin inner, close =end inner before =end outer. Do not use a target-specific region as a way to hide a malformed command.
Verify the finished document
Add the list and link examples to
/tmp/widget.pod, then run the checker again.podchecker /tmp/widget.podAsk the installed formatter to produce HTML in a separate temporary directory.
pod2html --outfile=/tmp/widget.html --htmlroot=/tmp --htmldir=/tmp /tmp/widget.pod test -s /tmp/widget.html && echo "HTML created" rm -f /tmp/widget.html /tmp/widget.podThese commands change only temporary files. The final
rmis safe here because the paths are explicit scratch outputs. If you need to inspect the HTML, omit that final command and remove the two files later.
Common traps
A command is ignored or rejected: check its first character, spelling and whether it is one of the recognised commands. A command inside a non-colon
=beginregion is still a command, even though ordinary paragraphs there are data.Code layout changes: check whether the paragraph starts with a space or tab. Verbatim whitespace is significant, although a processor may expand tabs.
A list renders oddly: check that every
=overhas a matching=back, that item styles do not change halfway through, and that numbered items are sequential.A link points somewhere surprising: make the page, section, text and URL explicit. Older section-only forms such as
L<"OPTIONS">are ambiguous and deprecated; preferL</"OPTIONS">.
Done means
- The document has a clearly bounded Pod block and a blank line between paragraphs.
- Commands are unindented, code examples are intentionally verbatim, and lists close correctly.
- Formatting codes and links use escaped or multiple-angle syntax where needed.
- Target-specific data is isolated in correctly nested
=beginand=endregions. podchecker /tmp/widget.podpasses, andpod2htmlcreates non-empty output.