NAME

App::Test::Generator::PodExampleExtractor - Extract runnable code examples from a Perl module's POD

SYNOPSIS

use App::Test::Generator::PodExampleExtractor;

my $ex = App::Test::Generator::PodExampleExtractor->new(
    file => 'lib/My/Module.pm',
);
my $examples = $ex->extract();

for my $e (@$examples) {
    printf "%-30s  %s\n", $e->{label}, $e->{code};
}

DESCRIPTION

Parses the POD of a Perl module and returns a structured list of runnable code examples. Three sources are collected:

  • Verbatim blocks inside =head1 SYNOPSIS and =head2 SYNOPSIS

  • =for example begin ... =for example end blocks

  • Annotated single-line call examples inside per-method docstrings (lines matching $obj->method(...) # returns value or method(...) # = value>)

Return-value annotations of the form # returns value or # => value are parsed and exposed as expected in the result hashref, enabling downstream test generators to emit is() assertions.

new

Construct a new extractor for the given source file.

my $ex = App::Test::Generator::PodExampleExtractor->new(
    file => 'lib/My/Module.pm',
);

Arguments

  • file

    Path to the Perl module to extract examples from. Required. The file must exist on disk.

Returns

A blessed App::Test::Generator::PodExampleExtractor object. Croaks if file is missing or does not exist.

EXAMPLE

use App::Test::Generator::PodExampleExtractor;

my $ex = App::Test::Generator::PodExampleExtractor->new(
    file => 'lib/Acme/Widget.pm',
);
printf "Extracting examples from %s\n", 'lib/Acme/Widget.pm';

MESSAGES

file is required

file was not supplied.

File not found: $path

file was supplied but the path does not exist on disk.

API specification

input

{ file => { type => SCALAR } }

output

{ type => OBJECT, isa => 'App::Test::Generator::PodExampleExtractor' }

FORMAL SPECIFICATION

Pre: defined file ∧ -f file

Post: ref(result) eq 'App::Test::Generator::PodExampleExtractor'result->{file} eq file

extract

Extract all runnable examples from the module's POD.

my $examples = $ex->extract();

Arguments

None beyond $self.

Returns

An arrayref of example hashrefs, deduplicated by code text. Each hashref has the following keys:

  • label — human-readable name for use as a test label (e.g. "SYNOPSIS example 1")

  • section — the POD section heading (=head1/=head2 text) where the example was found

  • code — the raw code text, dedented. May be multi-line for verbatim blocks.

  • expected — the expected return value string from a # returns value or # => value annotation; undef if not annotated.

  • annotated_line — for annotated single-line examples, the bare expression with the annotation stripped; undef for verbatim blocks.

EXAMPLE

my $examples = $ex->extract();
for my $e (@{$examples}) {
    printf "[%s] %s\n", $e->{label}, $e->{code};
    if(defined $e->{expected}) {
        printf "  expects: %s\n", $e->{expected};
    }
}

# Count examples without a declared return value
my @unannotated = grep { !defined $_->{expected} } @{$examples};
printf "%d unannotated examples\n", scalar @unannotated;

MESSAGES

File not found: $path (raised by File::Slurp::read_file)

The source file disappeared between construction and the extract call.

API specification

input

{ self => { type => OBJECT, isa => 'App::Test::Generator::PodExampleExtractor' } }

output

{ type => ARRAYREF }

FORMAL SPECIFICATION

Post: result is an arrayref where: ∀ e ∈ result: defined e->{label}defined e->{section}defined e->{code} ∧ no two entries share the same code value (deduplication)

COMMON PITFALLS

Shell-command blocks are silently dropped

Verbatim paragraphs that contain only shell commands (e.g. prove -l t/ or extract-schemas lib/My/Module.pm) are filtered out by _looks_like_perl. They would cause compile errors under use strict in a generated test file. If you want a shell command to appear as an example, put it in a POD comment block, not a verbatim paragraph.

Annotations must be on the same line as the call expression

The # returns / # => annotation is parsed as a line-level suffix. Multi-line calls spread across multiple lines will not pick up an annotation on the last line — only the last line is examined, and the call expression on earlier lines is lost.

expected is always a raw string

The expected field is the literal text captured after # returns or # =>. It is not evaluated or type-converted. Downstream code that emits is($result, $expected) must handle quoting appropriately.

SYNOPSIS blocks from =head3 and deeper are not collected

Only =head1 SYNOPSIS and =head2 SYNOPSIS sections are scanned for verbatim blocks. Deeper heading levels are ignored.

LIMITATIONS

No evaluation of verbatim blocks

Verbatim blocks are returned as raw code strings. The module does not evaluate them or check that they are syntactically valid Perl. Blocks that are syntactically broken will cause the downstream test file to fail to compile.

Deduplication is by exact code text

Two examples with identical code strings are deduplicated even if they come from different sections. If the same one-liner appears in both SYNOPSIS and a method docstring, only the first occurrence is kept.

SEE ALSO

bin/pod-example-tester
App::Test::Generator
Pod::Simple

AUTHOR

Nigel Horne, <njh at nigelhorne.com>

LICENCE AND COPYRIGHT

Copyright 2026 Nigel Horne.

Usage is subject to the terms of GPL2. If you use it, please let me know.

1 POD Error

The following errors were encountered while parsing the POD:

Around line 110:

Non-ASCII character seen before =encoding in '∧'. Assuming UTF-8