Class-Container
view release on metacpan or search on metacpan
lib/Class/Container.pm view on Meta::CPAN
use strict;
package Class::Container;
{
$Class::Container::VERSION = '0.13';
}
my $HAVE_WEAKEN;
BEGIN {
eval {
require Scalar::Util;
Scalar::Util->import('weaken');
$HAVE_WEAKEN = 1;
};
*weaken = sub {} unless defined &weaken;
}
use Carp;
# The create_contained_objects() method lets one object
# (e.g. Compiler) transparently create another (e.g. Lexer) by passing
# creator parameters through to the created object.
#
# Any auto-created objects should be declared in a class's
# %CONTAINED_OBJECTS hash. The keys of this hash are objects which
# can be created and the values are the default classes to use.
# For instance, the key 'lexer' indicates that a 'lexer' parameter
# should be silently passed through, and a 'lexer_class' parameter
# will trigger the creation of an object whose class is specified by
# the value. If no value is present there, the value of 'lexer' in
# the %CONTAINED_OBJECTS hash is used. If no value is present there,
# no contained object is created.
#
# We return the list of parameters for the creator. If contained
# objects were auto-created, their creation parameters aren't included
# in the return value. This lets the creator be totally ignorant of
# the creation parameters of any objects it creates.
use Params::Validate qw(:all);
Params::Validate::validation_options( on_fail => sub { die @_ } );
my %VALID_PARAMS = ();
my %CONTAINED_OBJECTS = ();
my %VALID_CACHE = ();
my %CONTAINED_CACHE = ();
my %DECORATEES = ();
sub new
{
my $proto = shift;
my $class = ref($proto) || $proto;
my $self = bless scalar validate_with
(
params => $class->create_contained_objects(@_),
spec => $class->validation_spec,
called => "$class->new()",
), $class;
if ($HAVE_WEAKEN) {
my $c = $self->get_contained_object_spec;
foreach my $name (keys %$c) {
next if $c->{$name}{delayed};
$self->{$name}{container}{container} = $self;
weaken $self->{$name}{container}{container};
}
}
return $self;
}
sub all_specs
{
require B::Deparse;
my %out;
foreach my $class (sort keys %VALID_PARAMS)
{
my $params = $VALID_PARAMS{$class};
foreach my $name (sort keys %$params)
{
my $spec = $params->{$name};
my ($type, $default);
if ($spec->{isa}) {
my $obj_class;
$type = 'object';
if (exists $CONTAINED_OBJECTS{$class}{$name}) {
$default = "$CONTAINED_OBJECTS{$class}{$name}{class}->new";
}
} else {
($type, $default) = ($spec->{parse}, $spec->{default});
}
if (ref($default) eq 'CODE') {
$default = 'sub ' . B::Deparse->new()->coderef2text($default);
$default =~ s/\s+/ /g;
} elsif (ref($default) eq 'ARRAY') {
$default = '[' . join(', ', map "'$_'", @$default) . ']';
} elsif (ref($default) eq 'Regexp') {
$type = 'regex';
$default =~ s,^\(\?(\w*)-\w*:(.*)\),/$2/$1,;
$default = "qr$default";
}
unless ($type) {
# Guess from the validation spec
$type = ($spec->{type} & ARRAYREF ? 'list' :
$spec->{type} & SCALAR ? 'string' :
$spec->{type} & CODEREF ? 'code' :
$spec->{type} & HASHREF ? 'hash' :
undef); # Oh well
}
my $descr = $spec->{descr} || '(No description available)';
$out{$class}{valid_params}{$name} = { type => $type,
pv_type => $spec->{type},
default => $default,
descr => $descr,
required => defined $default || $spec->{optional} ? 0 : 1,
public => exists $spec->{public} ? $spec->{public} : 1,
};
}
$out{$class}{contained_objects} = {};
lib/Class/Container.pm view on Meta::CPAN
if ($DECORATEES{$class}) {
# Fix format
$args{decorate_class} = [$args{decorate_class}]
if $args{decorate_class} and !ref($args{decorate_class});
# Figure out which class to decorate
my $decorate;
if (my $c = $args{decorate_class}) {
$decorate = @$c ? shift @$c : undef;
delete $args{decorate_class} unless @$c;
}
$c->{_decorates} = { class => $decorate } if $decorate;
}
# This one is special, don't pass to descendants
my $container_stuff = delete($args{container}) || {};
keys %$c; # Reset the iterator - why can't I do this in get_contained_object_spec??
my %contained_args;
my %to_create;
while (my ($name, $spec) = each %$c) {
# Figure out exactly which class to make an object of
my ($contained_class, $c_args) = $class->_get_contained_args($name, \%args);
@contained_args{ keys %$c_args } = (); # Populate with keys
$to_create{$name} = { class => $contained_class,
args => $c_args };
}
while (my ($name, $spec) = each %$c) {
# This delete() needs to be outside the previous loop, because
# multiple contained objects might need to see it
delete $args{"${name}_class"};
if ($spec->{delayed}) {
$container_stuff->{contained}{$name} = $to_create{$name};
$container_stuff->{contained}{$name}{delayed} = 1;
} else {
$args{$name} ||= $to_create{$name}{class}->new(%{$to_create{$name}{args}});
$container_stuff->{contained}{$name}{class} = ref $args{$name};
}
}
# Delete things that we're not going to use - things that are in
# our contained object specs but not in ours.
my $my_spec = $class->validation_spec;
delete @args{ grep {!exists $my_spec->{$_}} keys %contained_args };
delete $c->{_decorates} if $DECORATEES{$class};
$args{container} = $container_stuff;
return \%args;
}
sub create_delayed_object
{
my ($self, $name) = (shift, shift);
croak "Unknown delayed item '$name'" unless $self->{container}{contained}{$name}{delayed};
if ($HAVE_WEAKEN) {
push @_, container => {container => $self};
weaken $_[-1]->{container};
}
return $self->call_method($name, 'new', @_);
}
sub delayed_object_class
{
my $self = shift;
my $name = shift;
croak "Unknown delayed item '$name'"
unless $self->{container}{contained}{$name}{delayed};
return $self->{container}{contained}{$name}{class};
}
sub contained_class
{
my ($self, $name) = @_;
croak "Unknown contained item '$name'"
unless my $spec = $self->{container}{contained}{$name};
return $spec->{class};
}
sub delayed_object_params
{
my ($self, $name) = (shift, shift);
croak "Unknown delayed object '$name'"
unless $self->{container}{contained}{$name}{delayed};
if (@_ == 1) {
return $self->{container}{contained}{$name}{args}{$_[0]};
}
my %args = @_;
if (keys %args)
{
@{ $self->{container}{contained}{$name}{args} }{ keys %args } = values %args;
}
return %{ $self->{container}{contained}{$name}{args} };
}
# Everything the specified contained object will accept, including
# parameters it will pass on to its own contained objects.
sub _get_contained_args
{
my ($class, $name, $args) = @_;
my $spec = $class->get_contained_object_spec->{$name}
or croak "Unknown contained object '$name'";
my $contained_class = $args->{"${name}_class"} || $spec->{class};
croak "Invalid class name '$contained_class'"
unless $contained_class =~ /^[\w:]+$/;
$class->_load_module($contained_class);
return ($contained_class, {}) unless $contained_class->isa(__PACKAGE__);
my $allowed = $contained_class->allowed_params($args);
( run in 2.216 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )