Exporter-Extensible

 view release on metacpan or  search on metacpan

lib/Exporter/Extensible.pm  view on Meta::CPAN

package Exporter::Extensible;
use v5;
use strict; no strict 'refs';
use warnings; no warnings 'redefine';
require Exporter::Extensible::Compat if "$]" < "5.012";
require mro;

# ABSTRACT: Create easy-to-extend modules which export symbols
our $VERSION = '0.11'; # VERSION

our %EXPORT_FAST_SUB_CACHE;
our %EXPORT_PKG_CACHE;
our %EXPORT_TAGS_PKG_CACHE;

our %EXPORT= (
	-exporter_setup => [ 'exporter_setup', 1 ],
);

our %sigil_to_reftype= (
	'$' => 'SCALAR',
	'@' => 'ARRAY',
	'%' => 'HASH',
	'*' => 'GLOB',
	'&' => 'CODE',
	''  => 'CODE',
	'-' => 'CODE',
);
our %reftype_to_sigil= (
	'SCALAR' => '$',
	'ARRAY'  => '@',
	'HASH'   => '%',
	'GLOB'   => '*',
	'CODE'   => '',
);
our %sigil_to_generator_prefix= (
	'$' => [ '_generateSCALAR_', '_generateScalar_' ],
	'@' => [ '_generateARRAY_', '_generateArray_' ],
	'%' => [ '_generateHASH_', '_generateHash_' ],
	'*' => [ '_generateGLOB_', '_generateGlob_' ],
	'&' => [ '_generate_', '_generateCODE_', '_generateCode' ],
);
$sigil_to_generator_prefix{''}= $sigil_to_generator_prefix{'&'};
our %ord_is_sigil= ( ord '$', 1, ord '@', 1, ord '%', 1, ord '*', 1, ord '&', 1, ord '-', 1, ord ':', 1 );
our %ord_is_directive= ( ord '-', 1, ord ':', 1 );

my ($carp, $croak, $weaken, $colon, $hyphen);
$carp=   sub { require Carp; $carp= \&Carp::carp; goto $carp; };
$croak=  sub { require Carp; $croak= \&Carp::croak; goto $croak; };
$weaken= sub { require Scalar::Util; $weaken= \&Scalar::Util::weaken; goto $weaken; };
$colon= ord ':';
$hyphen= ord '-';

sub import {
	my $self= shift;
	# Can be called as class method or instance method
	$self= bless { into => scalar caller }, $self
		unless ref $self;
	# Optional config hash might be given as first argument
	$self->exporter_apply_global_config(shift)
		if ref $_[0] eq 'HASH';
	my $class= ref $self;
	my @todo= @_? @_ : @{ $self->exporter_get_tag('default') || [] };
	return 1 unless @todo;
	# If only installing subs without generators or unusual options, use a more direct code path.
	# This only takes effect the second time a symbol is requested, since the cache is not pre-populated.
	# (abuse a while loop as a if/goto construct)
	fast: while (!$self->{_complex} && !grep ref, @todo) {
		my $fastsub= $EXPORT_FAST_SUB_CACHE{$class} || last; # can't optimize if no cache is built
		my $prefix= $self->{into}.'::'; # {into} can be a hashref, but not when {_complex} is false
		my $replace= $self->{replace} || 'carp';
		if ($replace eq 'carp') {
			# Use perl's own warning system to detect attempts to overwrite the GLOB.  Only warn if the
			# new reference isn't the same as existing.
			use warnings 'redefine';
			local $SIG{__WARN__}= sub { *{$prefix.$_}{CODE} == $fastsub->{$_} or $carp->($_[0]) };
			ord == $colon || (*{$prefix.$_}= ($fastsub->{$_} || last fast))
				for @todo;
		}
		elsif ($replace eq 1) {
			ord == $colon || (*{$prefix.$_}= ($fastsub->{$_} || last fast))
				for @todo;
		}
		else { last } # replace==croak and replace==skip require more logic
		# Now apply any tags that were requested.  Each will get its own determination of whether it
		# can use the 'fast' method.
		ord == $colon && $self->import(@{$self->exporter_get_tag(substr $_, 1)})
			for @todo;
		return 1;
	}
	my $install= $self->_exporter_build_install_set(\@todo);

	# Install might actually be uninstall.  It also might be overridden by the user.
	# The exporter_combine_config sets this up so we don't need to think about details.
	my $method= $self->{installer} || ($self->{no}? 'exporter_uninstall' : 'exporter_install');
	# Convert
	#    { foo => { SCALAR => \$foo, HASH => \%foo } }
	# into
	#    [ foo => \$foo, foo => \%foo ]
	my @flat_install= %$install;
	for my $i (reverse 1..$#flat_install) {
		if (ref $flat_install[$i] eq 'HASH') {
			splice @flat_install, $i-1, 2, map +($flat_install[$i-1] => $_), values %{$flat_install[$i]};
		}
	}
	# Then pass that list to the installer (or uninstaller)
	$self->$method(\@flat_install);
	# If scope requested, create the scope-guard object
	if (my $scope= $self->{scope}) {
		$$scope= bless [ $self, \@flat_install ], 'Exporter::Extensible::UnimportScopeGuard';
		$weaken->($self->{scope});
	}
	# It's entirely likely that a generator might curry $self inside the sub it generated.
	# So, we end up with a circular reference if we're holding onto the set of all things we
	# exported.  Clear the set.
	%$install= ();
	1;
}

sub _exporter_build_install_set {
	my ($self, $todo)= @_;
	$self->{todo}= $todo;
	my $install= $self->{install_set} ||= {};
	my $inventory= $EXPORT_PKG_CACHE{ref $self} ||= {};
	while (@$todo) {
		my $symbol= shift @$todo;

		# If it is a tag, then recursively call import on that list
		if (ord $symbol == $colon) {
			my $name= substr $symbol, 1;
			my $tag_cache= $self->exporter_get_tag($name)
				or $croak->("Tag ':$name' is not exported by ".ref($self));
			# If first element of tag is a hashref, they count as nested global options.
			# If tag was followed by hashref, those are user-supplied options.
			if (ref $tag_cache->[0] eq 'HASH' || ref $todo->[0] eq 'HASH') {
				$tag_cache= [ @$tag_cache ]; # don't destroy cache
				my $self2= $self;
				$self2= $self2->exporter_apply_global_config(shift @$tag_cache)
					if ref $tag_cache->[0] eq 'HASH';
				$self2= $self2->exporter_apply_inline_config(shift @$todo)
					if ref $todo->[0] eq 'HASH';
				if ($self != $self2) {
					$self2->_exporter_build_install_set($tag_cache);
					next;
				}
			}
			unshift @$todo, @$tag_cache;
			next;
		}
		# Else, it is an option or plain symbol to be exported
		# Check current package cache first, else do the full lookup.
		my $ref= (exists $inventory->{$symbol}? $inventory->{$symbol} : $self->exporter_get_inherited($symbol))
			or $croak->("'$symbol' is not exported by ".ref($self));

		# If it starts with '-', it is an option, and might consume additional args
		if (ord $symbol == $hyphen) {
			# back-compat for when opt was arrayref
			if (ref $ref eq 'ARRAY') {
				my ($method, $count)= @$ref;
				$ref= $self->_exporter_wrap_option_handler($method, $count);
			}
			$self->$ref;
		}
		else {
			my ($sigil, $name)= $ord_is_sigil{ord $symbol}? ( substr($symbol,0,1), substr($symbol,1) ) : ( '', $symbol );
			my $self2= $self;
			# If followed by a hashref, add those options to the current ones.
			$self2= $self->exporter_apply_inline_config(shift @$todo)
				if ref $todo->[0] eq 'HASH';
			if ($self2->{_name_mangle}) {
				next if defined $self2->{not} and $self2->_exporter_is_excluded($symbol);



( run in 0.583 second using v1.01-cache-2.11-cpan-39bf76dae61 )