package Request;

use strict;
use vars qw($VERSION @ISA @EXPORT);

require Exporter;
require AutoLoader;

@ISA = qw(Exporter AutoLoader);
# Items to export into callers namespace by default. Note: do not export
# names by default without a very good reason. Use EXPORT_OK instead.
# Do not simply export all your public functions/methods/constants.
@EXPORT = qw(
);
$VERSION = '0.02';


# Preloaded methods go here.

sub new {
  die "Usage: new Request \$recipes" unless @_ == 2;
  shift;
  my $recipes = shift;
  $recipes = bless [$recipes, {}];
  # $recipes->set(@_);
  $recipes;
}

sub set {
  my $self = shift;
  die "Odd number of data given to Request::set" if @_ % 2;
  my %data = @_;
  @{$self->[1]}{keys %data} = values %data;
}

sub get {
  my $self = shift;
  my $request = shift;
  $self->request($request);
  $self->[1]->{$request};
}

sub request {
  my $self = shift;
  my ($recipes, $data) = @$self;
  my ($recipe, $request);
  for $request (@_) {
    # Bail out if present
    next if exists $data->{$request};
    $recipe = $recipes->{$request};
    # Get prerequisites
    $self->request(@{$recipe->{prerequisites}})
      if exists $recipe->{prerequisites};
    # Check for default value
    if (exists $recipe->{default}) {
      $data->{$request} = $recipe->{default};
      next;
    } elsif (exists $recipe->{process}) { # Let it do the work itself.
      &{$recipe->{process}}($data, $request);
      die "The recipe for processing the request `$request' did not acquire it" 
	unless exists $data->{$request};
    } elsif (exists $recipe->{output}) { # Keep return value.
      $data->{$request} = &{$recipe->{output}}($data, $request);
    } elsif (exists $recipe->{filter}) { # Input comes from $data
      my @arr = @{ $recipe->{filter} };
      my $sub = shift @arr;
      @arr = map $data->{$_}, @arr;
      $data->{$request} = &$sub( @arr );
    } else {
      die "Do not know how to satisfy the request `$request'";
    }
  }
}

# Autoload methods go after =cut, and are processed by the autosplit program.

1;
__END__
# Below is the stub of documentation for your module. You better edit it!

=head1 NAME

Request - Perl extension for simple-minded recipe-controlled build of data.

=head1 SYNOPSIS

  use Request;
  $recipes = {
	      path  => { default => './MANIFEST'},
	      contents => { prerequisites => ['path', 'x'] ,
			    process => 
			    sub {
			      my $data = shift; 
			      $data->{ shift() } = `cat $data->get('path')`
				x $data->get('x');
			    }
			  },
	     };

  $request = new Request $recipes;
  $request->set( x => 1);
  print $request->get('contents');


=head1 DESCRIPTION

The module Request provides its services via objects. The objects may be obtained by the usual 

  $request = new Request $recipes;

paradigm. The argument $recipes is a hash reference, which provides
the rules for request processing. The objects support two methods,
set() and get(). The first one is used to provide input data for
processing, the second one to obtain the output.

The unit of requested information is a I<field>. The
method set() takes an arbitrary number of pairs C<field =E<gt> value>,
the method get() takes one argument: the C<field>.

Every object is created without any fields filled, but it knows how to
I<construct> fields basing on other fields or some global into. This
knowledge is provided in the argument $recipe of the new()
function. This is a reference to a hash, keyed by I<fields>. The
values of this hash are hash references themselves, which describe how
to acquire the I<field> which is the corresponding key of the initial
hash.

The internal hashes may have the following keys:

=over 8

=item C<default>

describes the default value for the key, if none is provided by
set(). The value becomes the value of the field of the object. No
additional processing is performed. Example:

  default => $Config{installdir}

=item C<prerequisites>

gives the fields which are needed for the build of the given field. It
is a hash reference indexed by fields, with values being array
references. The corresponding arrays contain the I<required> fields. 

If C<defaults> did not satisfy the request for a field, but
C<$recipe-E<gt>{prerequisites}> has the field as a key, the I<required>
fields are build before any further processing is done. Example:

  prerequisites => [ qw(prefix arch) ]

=item C<process>

contains the rule to build the field. The value is a reference to a
subroutine taking 2 arguments: the object $request, and the name of
the required field. It is up to the subroutine to actually fill the
corresponding field of $data, an error condition is raised if it did
not. Example:

  process => sub { my $data = shift; $data->set( time => localtime(time) ) }

=item C<output>

the corresponing value has the same meaning as for C<process>, but the
return value of the subroutine is used as the value of the
I<field>. Example:

  output => sub { localtime(time) }


=item C<filter>

contains the rule to build the field basing on other fields. The value
is a reference to an array. The first element of the array is a
reference to a subroutine, the rest contains names of the fields. When
the subroutine is called, the arguments are the values of I<fields> of
the object $request which appear in the array (in the same order). The
return value of the subroutine is used as the value of the
I<field>. Example:

  filter => [ sub { shift + shift }, 'first_half', 'second_half' ]

=back

=head1 AUTHOR

Ilya Zakharevich, ilya@math.ohio-state.edu

=head1 SEE ALSO

perl(1), make(1).

=cut
