The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Pod::Snippets - Extract and reformat snippets of POD so as to use them in a unit test (or other Perl code)

VERSION

Version 0.14

SYNOPSIS

    use Pod::Snippets;

    my $snips = load Pod::Snippets($file_or_handle,
            -markup => "test");

    my $code_snippet = $snips->named("synopsis")->as_code;

    # ... Maybe borg $code_snippet with regexes or something...

    my $result = eval $code_snippet; die $@ if $@;

    like($result->what_happen(), qr/bomb/);

The Perl code that we want to extract snippets from might look like this:

    package Zero::Wing;

    =head1 NAME

    Zero::Wing - For great justice!

    =head1 SYNOPSIS

    =for test "synopsis" begin

       use Zero::Wing;

       my $capitain = Zero::Wing->capitain;

    =for test "synopsis" end

    =cut

    # ...

    1;

DESCRIPTION

This class is a very simple extension of Pod::Parser that extracts POD snippets from Perl code, and pretty-prints it so as to make it useable from other Perl code. As demonstrated above, Pod::Snipets is immediately useful to test-driven-development nutcases who want to put every single line of Perl code under test, including code that is in the POD (typically a SYNOPSIS section). There are other uses, such as storing a piece of information that is both human- and machine-readable (eg an XML schema) simultaneously as documentation and code.

Using Pod::Snippets for unit testing

The "SYNOPSIS" demonstrates how to use Pod::Snippets to grab a piece of POD and execute it with "eval" in perlfunc. This can readily be done using your usual unit testing methodology, without too much ajusting if any. This approach has some advantages over other code-in-POD devices such as Pod::Tested and Test::Inline:

  • There is no preprocessing step involved, hence no temp files and no loss of hair in the debugger due to line renumbering.

  • Speaking of which, "as_code" prepends an appropriate #line if possible, so you can single-step through your POD (yow!).

The Pod-Snippets CPAN distribution consists of a single Perl file, and has no dependencies besides what comes with a standard Perl 5.8.x. It is therefore easy to embed into your own module so that your users won't need to install Pod::Snippets by themselves before running your test suite. All that remains to do is to select the right options to pass to "load" as part of an appropriately named wrapper function in your test library.

Snippet Syntax

Pod::Snippets only deals with verbatim portions of the POD (that is, as per perlpod, paragraphs that start with whitespace at the right) and custom markup starting with =for test, =begin test or =end test; it discards the rest (block text, actual Perl code, character markup such as B<>, =head's and so on). The keyword "test" in =for test and =begin test can be replaced with whatever one wants, using the -markup argument to "load". Actually the default value is not even "test"; nonetheless let's assume you are using "test" yourself for the remainder of this discussion. The following metadata markup is recognized:

=for test ignore

Starts ignoring all POD whatsoever. Verbatim portions of the POD are no longer stashed by Pod::Snippets until remanded by a subsequent =for test.

=for test

Cancels the effect of an ongoing =for test ignore directive.

=for test "foo" begin
=for test "foo" end

These signal the start and end of a named POD snippet, that can later be fetched by name using "named". Unless countermanded by appropriate parser options (see "load"), named POD snippets can nest freely (even badly).

=begin test
=end test

The POD between these markers will be seen by Pod::Snippets, but not by other POD formatters. Otherwise has no effect on the naming or ignoring of snippets; in particular, if the contents of the section is not in POD verbatim style, it still gets ignored.

=begin test "foo"
=end test "foo"

These have the exact same effect as =for test "foo" begin and =for test "foo" end, except that other POD formatters will not see the contents of the block.

CONSTRUCTORS

load ($source, -opt1 => $val1, ...)

Parses the POD from $source and returns an object of class Pod::Snippets that holds the snippets found therein. $source may be the name of a file, a file descriptor (glob reference) or any object that has a getline method.

Available named options are:

-filename => $filename

The value to set for "filename", that is, the name of the file to use for #line lines in "as_code". The default behavior is to use the filename passed as the $source argument, or if it was not a filename, use the string "pod snippet" instead.

-line => $line

The line number to start counting lines from, eg in case the $source got a few lines chopped off it before being passed to load. Default is 1.

-markup => $name

The markup (aka "format name" in perlpod) to use as the first token after =for, =begin or =end to indicate that the directive is to be processed by Pod::Snippets (see "Snippet Syntax". Default is "Pod::Snippets".

-report_errors => $sub

Invokes $sub like so to deal with warnings and errors:

  $sub->($severity, $text, $file, $line);

where $severity is either "WARNING" or "ERROR". By default the standard Perl "warn" in perlfunc is used.

Regardless of the number of errors, the constructor tries to load the whole file; see below.

-named_snippets => "warn_impure"

Raises an error upon encountering this kind of construct:

  =for test "foobar" begin

     my $foobar = foobar();

  =head1 And now something completely different...

  =for test "foobar" end

In other words, only verbatim blocks may intervene between the =for test "foobar" begin and =for test "foobar" end markups.

-named_snippets => "warn_multiple"

Raises a warning upon encountering this kind of construct:

  =for test "foobar" begin

     my $foobar = foobar();

  =for test "foobar" end

  =for test "foobar" begin

     $foobar->quux_some_more();

  =for test "foobar" end
-named_snippets => "warn_overlap"

Raises a warning if named snippets overlap in any way.

-named_snippets => "warn_bad_pairing"

Raises a warning if opening and closing markup for named snippets is improperly paired (eg opening or closing twice, or forgetting to close before the end of the file).

-named_snippets => "error_impure"
-named_snippets => "error_multiple"
-named_snippets => "error_overlap"
-named_snippets => "error_bad_pairing"

Same as the warn_ counterparts above, but cause errors instead of warnings.

-named_snippets => "ignore_impure"
-named_snippets => "ignore_multiple"
-named_snippets => "ignore_overlap"
-named_snippets => "ignore_bad_pairing"

Ignores the corresponding dubious constructs described above. The default behavior is -named_snippets => "warn_bad_pairing" and ignore the rest.

-named_snippets => "strict"

Equivalent to (-named_snippets => "error_overlap", -named_snippets => "error_impure", -named_snippets => "error_multiple", -named_snippets => "error_bad_pairing").

Note that the correctness of the POD to be parsed is a prerequisite; in other words, Pod::Snippets won't touch the error management knobs of the underlying Pod::Parser object.

Also, note that the parser strictness options such as -named_snippets have no effect on the semantics; they merely alter its response (ignore, warning or error) to the aforementioned dubious constructs. In any case, the parser will soldier on until the end of the file regardless of the number of errors seen; however, it will disallow further processing of the snippets if there were any errors (see "errors").

parse ($string, -opt1 => $val1, ...)

Same as "load", but works from a Perl string instead of a file descriptor. The named options are the same as in load(), but consider using -filename as parse() is in no position to guess it.

ACCESSORS

filename ()

Returns the name of the file to use for #line lines in "as_code". The default behavior is to use the filename passed as the $source argument, or if it was not a filename, use the string "pod snippet" instead.

warnings ()

Returns the number of warnings that occured during the parsing of the POD.

errors ()

Returns the number of errors that occured during the parsing of the POD. If that number is non-zero, then all accessors described below will throw an exception instead of performing.

as_data ()

Returns the snippets in "data" format: that is, the return value is ragged to the left by suppressing a constant number of space characters at the beginning of each snippet. (If tabs are present in the POD, they are treated as being of infinite length; that is, the ragging algorithm does not eat them or replace them with spaces.)

A snippet is defined as a series of subsequent verbatim POD paragraphs with only Pod::Snippets markup, if anything, intervening in between. That is, as_data(), given the following POD in input:

    my $a = new The::Brain;

  =begin test

      # Just kidding. We can't do that, it's too dangerous.
      $a = new Pinky;

  =end test

  =for test ignore

    system("/sbin/reboot");

  and all of a sudden, we have:

  =for test

        if ($a->has_enough_cookies()) {
          $a->conquer_world();
        }

would return (in list context)

  (<<'FIRST_SNIPPET', <<'SECOND_SNIPPET');
  my $a = new The::Brain;



    # Just kidding. We can't do that, it's too dangerous.
    $a = new Pinky;
  FIRST_SNIPPET
  if ($a->has_enough_cookies()) {
    $a->conquer_world();
  }
  SECOND_SNIPPET

Notice how the indentation is respected snippet-by-snippet; also, notice that the FIRST_SNIPPET has been padded with an appropriate number of carriage returns to replace the Pod::Snippets markup, so that the return value is line-synchronized with the original POD. However, leading and trailing whitespace is trimmed, leaving only strings that starts with a nonblank line and end with a single newline.

In scalar context, returns the blocks joined with a single newline character ("\n"), thus resulting in a single piece of text where the blocks are joined by exactly one empty line (and which as a whole is no longer line-synchronized with the source code, of course).

as_code ()

Returns the snippets formatted as code, that is, like "as_data", except that each block is prepended with an appropriate #line statement that Perl can interpret to renumber lines. For instance, these statements would cause Perl to Do The Right Thing if one compiles the snippets as code with "eval" in perlfunc and then runs it under the Perl debugger.

named ($name)

Returns a clone of this Pod::Snippet object, except that it only knows about the snippet (or snippets) that are named $name. In the most lax settings for the parser, this means: any and all snippets where an =for test "$name" begin (or =begin test "$name") had been open, but not yet closed with =for test "$name" end (or =end test "$name"). Returns undef if no snippet named $name was seen at all.

SEE ALSO

Test::Pod::Snippets

AUTHOR

Dominique QUATRAVAUX, <domq@cpan.org>

BUGS

Please report any bugs or feature requests to bug-pod-snippet@rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Pod-Snippet. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

ACKNOWLEDGEMENTS

Yanick Champoux <yanick@CPAN.org> is the author of Test::Pod::Snippets which grandfathers this module.

COPYRIGHT & LICENSE

Copyright 2007 Dominique QUATRAVAUX, all rights reserved.

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.