Badger
view release on metacpan or search on metacpan
lib/Badger/Class.pm view on Meta::CPAN
${ $package.PKG.LOADED } ||= 1; # add $BADGER_LOADED to mark our scent
$class->SUPER::export($package, @args);
}
sub _export_hook {
my ($class, $target, $key, $symbols) = @_;
croak sprintf(NO_VALUE, $key)
unless @$symbols;
class($target, $class)->$key(shift @$symbols);
}
sub _export_fail {
my ($class, $target, $key, $symbols, $import) = @_;
# look for any additional export hooks defined in $HOOKS, e.g.
# by a subclass or poked in via the hooks() method
my $hook = class($class)->hash_value( HOOKS => $key ) || return;
croak sprintf(NO_VALUE, $key)
unless @$symbols;
# We use the two-argument call to class() which tells it that we want
# a $class metaclass object rather than the default of Badger::Class.
# This is because subclasses may be calling this method so $class isn't
# always going to be Badger::Class
class($target, $class)->$hook(shift @$symbols);
}
sub _debug_hook {
my ($class, $target, $key, $debug) = @_;
$debug = { default => $debug }
unless ref $debug eq HASH;
_autoload($class->DEBUGGER)->export($target, %$debug);
}
sub _dumps_hook {
my ($class, $target, $key, $dumps) = @_;
_autoload($class->DEBUGGER)->export($target, dumps => $dumps);
}
#-----------------------------------------------------------------------
# Define a lexical scope to enclose class lookup tables
#-----------------------------------------------------------------------
# Badger::Class and each of its subclasses have their own metaclass
# table mapping class names to objects.
my $METACLASSES = { };
{
# class/package name - define this up-front so we can use it below
sub CLASS {
# first argument is object or class name, otherwise return caller
@_ ? (ref $_[0] || $_[0])
: (caller())[0];
}
# Sorry if this messes with your head. We want class() and classes()
# methods that create Badger::Class objects. However, we also want
# Badger::Class to be subclassable (e.g. Badger::Factory::Class), where
# class() and classes() return the subclass objects instead of the usual
# Badger::Class. So we have an UBER() class method whose job it is to
# create the class() and classes() methods for the relevant metaclass
sub UBER {
# $pkg is the metaclass name, e.g. Badger::Class, but can also be
# subclasses, e.g. Badger::Factory::Class
my $pkg = shift || __PACKAGE__;
# $CLASSES is a lookup table mapping package names to Badger::Class
# objects. We need a new lookup table for each subclass of
# Badger::Class, so we reuse/create such a table in $METACLASSES,
# indexed by the metaclass name, e.g. Badger::Class, etc.
my $CLASSES = $METACLASSES->{ $pkg } ||= { };
# We want to keep the class() subroutine as fast as possible as it
# gets called often. It's a tiny bit faster to declare a variable
# outside the closure and reuse it, rather than defining a new
# variable each time the closure is called. Ho hum.
my $class;
# The class() subroutine is used to fetch/create a Badger::Class
# object for a package name. The first argument is the class name,
# or the caller's package if undefined and we look it up in $CLASSES.
# If we get a second argument then we're being asked to lookup an
# entry for a subclass of Badger::Class, e.g. Badger::Factory::Class,
# so we first lookup the correct $METACLASS table.
my $class_sub = sub {
$class = @_ ? shift : (caller())[0];
$class = ref $class || $class;
return @_
? $METACLASSES->{ $_[0] }->{ $class } ||= $_[0]->new($class)
: $CLASSES->{ $class } ||= $pkg->new($class);
};
# The classes() method returns a list of Badger::Class objects for
# each class in the inheritance chain, starting with the object
# itself, followed by each base class, their base classes, and so on.
# As with class(), we use a generator to create a closure for the
# subroutine to allow the the class object name to be parameterised.
my $classes_sub = sub {
$class = shift || (caller())[0];
$class_sub->($class)->heritage;
};
no strict REFS;
no warnings 'redefine';
*{ $pkg.PKG.'CLASS' } = \&CLASS;
*{ $pkg.PKG.'class' } = $class_sub;
*{ $pkg.PKG.'bclass' } = $class_sub; # plan B
*{ $pkg.PKG.'classes' } = $classes_sub;
*{ $pkg.PKG.'_autoload' } = \&_autoload;
$pkg->export_any('CLASS', 'class', 'bclass', 'classes');
}
# call the UBER method to generate class() and classes() for this module
__PACKAGE__->UBER;
}
#-----------------------------------------------------------------------
# generate additional delegate methods listed in $DELEGATES
#-----------------------------------------------------------------------
class(CLASS)->methods(
map {
my $info = $DELEGATES->{ $_ };
my ($module, $method) = @$info;
$_ => sub {
my $self = shift;
_autoload($self->$module)->$method($self->{ name }, @_);
return $self;
};
}
keys %$DELEGATES
);
#-----------------------------------------------------------------------
# constructor method
#-----------------------------------------------------------------------
sub new {
my ($class, $package) = @_;
$package = ref $package || $package;
no strict 'refs';
bless {
name => $package,
symbols => \%{"${package}::"},
}, $class;
}
sub id {
my $self = shift;
return @_
? $self->{ id } = shift
: $self->{ id } ||= do {
my $pkg = $self->{ name };
my $base = $self->base_id; # base to remove, e.g. Badger
if ($base eq $pkg) {
lib/Badger/Class.pm view on Meta::CPAN
: ${ $self->{name}.PKG.$name };
}
sub var_default {
my ($self, $name, $default) = @_;
no strict REFS;
no warnings ONCE;
return ${ $self->{name}.PKG.$name }
||= $default;
}
sub any_var {
my $self = shift;
my $name = shift;
no strict REFS;
# remove any leading '$'
$name =~ s/^\$//;
foreach my $pkg ($self->heritage) {
_debug("looking for $name in $pkg\n") if DEBUG;
return ${ $pkg.PKG.$name } if defined ${ $pkg.PKG.$name };
}
return undef;
}
sub any_var_in {
my $self = shift;
my $names = @_ == 1 ? shift : [@_];
my ($pkg, $name);
no strict REFS;
$names = [ split DELIMITER, $names ]
unless ref $names eq ARRAY;
# remove any leading '$'
$names = [ map { s/^\$//; $_ } @$names ];
foreach $pkg ($self->heritage) {
foreach $name (@$names) {
_debug("looking for $name in $pkg\n") if DEBUG;
return ${ $pkg.PKG.$name } if defined ${ $pkg.PKG.$name };
}
}
return undef;
}
sub all_vars {
my ($self, $name) = @_;
my $pkg = $self->{ name };
my ($value, @values);
no strict REFS;
no warnings ONCE;
# remove any leading '$'
$name =~ s/^\$//;
# _debug("all_vars() caller: ", join(', ', caller()), "\n");
foreach my $pkg ($self->heritage) {
_debug("looking for $name in ", $pkg || "UNDEF", "\n") if DEBUG;
push(@values, $value)
if defined ($value = ${ $pkg.PKG.$name });
_debug("got: $value\n") if DEBUG && $value;
}
return wantarray ? @values : \@values;
}
sub list_vars {
my $self = shift; # must remove these from @_ here
my $name = shift;
my $vars = $self->all_vars($name);
my (@merged, $list);
# remove any leading '$'
$name =~ s/^\$//;
foreach $list (@_, @$vars) { # use whatever is left in @_ here
next unless defined $list;
if (ref $list eq ARRAY) {
next unless @$list;
push(@merged, @$list);
}
else {
push(@merged, $list);
}
}
# return \@merged;
# NOTE TO SELF: this causes problems when doing something like
# foo( something_that_calls_list_vars() ) because list_vars assumed
# list context when we actually want a scalar ref. Must find where
# this is and fix it.
return wantarray ? @merged : \@merged;
}
sub hash_vars {
my $self = shift; # must remove these from @_ here
my $name = shift;
my $vars = $self->all_vars($name);
my (%merged, $hash);
# remove any leading '$'
$name =~ s/^\$//;
# reverse the package vars so we get base classes first, followed by subclass,
# then we add any additional arguments on as well in the order specified
foreach $hash ( reverse(@$vars), @_ ) {
next unless defined $hash;
unless (ref $hash eq HASH) {
warn "Ignoring $name configuration option (not a hash ref): $hash\n";
next;
}
@merged{ keys %$hash } = values %$hash;
}
return \%merged;
}
sub hash_value {
my ($self, $name, $item, $default) = @_;
# remove any leading '$'
$name =~ s/^\$//;
# _debug("hash_value() caller: ", join(', ', caller()), "\n");
foreach my $hash ($self->all_vars($name)) {
next unless ref $hash eq HASH;
return $hash->{ $item }
if defined $hash->{ $item };
}
return $default;
}
#-----------------------------------------------------------------------
# Methods to return immediate parent classes and all ancestor classes.
#-----------------------------------------------------------------------
sub parents {
my $self = shift;
my $class = ref $self || $self;
my $pkg = $self->{ name };
my $parents = $self->{ parents } ||= do {
no strict REFS;
# make sure the module is loaded before we go looking at its @ISA
_autoload($pkg);
[
map { class($_) } # parents are immediate
@{ $pkg.PKG.ISA } # superclasses defined in @ISA
];
};
return wantarray
? @$parents
: $parents;
}
sub heritage {
my $self = shift;
my $heritage = $self->{ heritage } ||= do {
my @pending = ($self);
my (%seen, $item, @order);
while (@pending) {
next unless defined ($item = pop @pending);
unshift(@order, $item);
push(@pending, reverse @{ $item->parents });
}
[ reverse grep { ! $seen{$_}++ } @order ];
};
return wantarray
? @$heritage
: $heritage;
}
#-----------------------------------------------------------------------
# class configuration methods - also available as import hooks
#-----------------------------------------------------------------------
sub base {
my $self = shift;
my $bases = @_ == 1 ? shift : [ @_ ];
( run in 1.732 second using v1.01-cache-2.11-cpan-75ffa21a3d4 )