package Contentment::Generator;

use strict;
use warnings;

our $VERSION = '0.05';

use IO::NestedCapture qw( capture_out );

=head1 NAME

Contentment::Generator - Provides the contract and factory for generators

=head1 SYNOPSIS

  my $generator = Contentment::Generator->generator('Plain', {
      source     => sub {
          print "Hello World!\n";
      },
      kind       => 'text/plain',
      properties => {
          title       => 'Generic Generator',
          description => 'This is a very simple example.',
      },
  });

  $generator->generate( foo => 1, bar => 2 );

=head1 DESCRIPTION

At the center of each response is the generator. A generator is responsible for sending a response to the user and must implement a few methods. This class provides a generic way for constructing generators.

=head2 GENERATOR INTERFACE

This is the list of methods that a generator class must implement and the expected parameters, output, and return values. 

=over

=item my $generator-E<gt>new(\%args)

Users should not call generator constructors directly. Rather, C<$generator> objects should be constructed via the factory method, C<generator>, provided by L<Contentment::Generator> (this class).

The arguments can be anything appropriate to the generator class. However, here a few recommendations:

=over

=item source

Many generators take some source text and use that as the basis for executing some code, finding properties, etc. The source should be accepted as a scalar. You may also wish to accept other forms like file handles.

=item properties

Many generators will take a list of properties to associate with the generator. If your generator creates properties from the source, it is recommended that the properties associated with this option be used as default values. Any properties found in the file should append to or override the properties stated here.

=back

=item my $value = $generator-E<gt>get_property($key)

This method must exist and returns whatever information is appropriate. For generators based on files, it might return information for keys naming values returned from the C<stat> operator. This is anything you want.

This should, in general, include the following properties for each generator:

=over

=item kind

This is the kind of data the C<generate()> method will output.

=item title

This is the title of the document generated by this generator.

=item description

This is a short one line description of the document generated by this generator.

=back

=item $result = $generator-E<gt>generate(%args)

This method should print to STDOUT the output of the generator. It may return whatever result seems appropriate (it will be ignored if it's run as the top-level response).

=back

=head2 FACTORY

=over

=item $generator = Contentment::Generator->generator($class, \%args)

This factory method will create the requested generator. If the given class name, C<$class>, does not contain "::", it will be considered a short name and will have the string "Contentment::Generator::" prepended to it to find the generator class.

The arguments should be those necessary to initialize the generator.

=cut

sub generator {
    my $class           = shift;
    my $generator_class = shift;

    if ($generator_class !~ /^[\w:]+$/) {
        Contentment::Exception->throw(
            message => qq(Invalid generator class "$generator_class".)
        );
    }

    if ($generator_class !~ /::/) {
        $generator_class = "Contentment::Generator::$generator_class";
    }

    eval "require $generator_class";

    warn qq(Error loading "$generator_class": $@) if $@;

    my $generator = $generator_class->new(@_);
	if (defined $generator) {
		Contentment::Log->debug(
			'Generator class %s returned a generator.', [$generator_class]);
		return $generator;
	}

	else {
		Contentment::Log->error(
			'Generator class %s returned undef.', [$generator_class]);
		Class::Exception->throw(
			message => "Generator class $generator_class returned undef.",
		);
	}
}

=item $content = Contentment::Generator->content_of($generator, \%args)

This is a helper to stringify the generation of a generator in case you need to just capture the string output instead of printing that output immediately. This is very useful for calling generators from the Template Toolkit generator:

  [% USE Generator %]
  [% Generator.content_of(arg.gen, foo = 1 bar = 2) %]

=cut

sub content_of {
    my $class     = shift;
    my $generator = shift;

	if (!defined $generator) {
		Contentment::Exception->throw(
			message => 'content_of() called with an undefined generator.',
		);
	}

    capture_out {
        $generator->generate(@_);
    };

    my $fh   = IO::NestedCapture->get_last_out;
    my $text = join '', <$fh>;

    return $text;
}

=back

=head1 AUTHOR

Andrew Sterling Hanenkamp, E<lt>hanenkamp@cpan.orgE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright 2005 Andrew Sterling Hanenkamp. All Rights Reserved.

Contentment is licensed and distributed under the same terms as Perl itself.

=cut

1
