Exporter-Extensible

 view release on metacpan or  search on metacpan

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

	*_exporter_get_coderef_name= $impl;
	$impl->(shift);
}

sub _exporter_get_ref_to_package_var {
	my ($class, $sigil, $name)= @_;
	unless (defined $name) {
		($sigil, $name)= ($_[1] =~ /^([\$\@\%\*\&]?)(\w+)$/)
			or $croak->("'$_[1]' is not an allowed variable name");
	}
	my $reftype= $sigil_to_reftype{$sigil};
	return undef unless ${$class.'::'}{$name};
	return $reftype eq 'GLOB'? \*{$class.'::'.$name} : *{$class.'::'.$name}{$reftype};
}

sub _exporter_process_attribute {
	my ($class, $coderef, $attr)= @_;
	if ($attr =~ /^Export(?:\(\s*(.*?)\s*\))?$/) {
		my (%tags, $subname, @export_names);
		# If given a list in parenthesees, split on space and proces each.  Else use the name of the sub itself.
		for my $token ($1? split(/\s+/, $1) : ()) {
			if ($token =~ /^:(.*)$/) {
				$tags{$1}++; # save tags until we have the export_names
			}
			elsif ($token =~ /^\w+$/) {
				push @export_names, $token;
				${$class.'::EXPORT'}{$token}= $coderef;
			}
			elsif ($token =~ /^-(\w*)(?:\(([0-9]+|\*|\?)\))?$/) {
				$subname ||= _exporter_get_coderef_name($coderef);
				push @export_names, length $1? $token : "-$subname";
				$class->exporter_register_option(substr($export_names[-1],1), $subname, $2);
			}
			elsif (my($sym, $name)= ($token =~ /^=([\&\$\@\%\*:]?(\w*))$/)) {
				$subname ||= _exporter_get_coderef_name($coderef);
				my $export_name= length $name? $sym : do {
					(my $x= $subname) =~ s/^_generate[A-Za-z]*_//;
					$sym . $x
				};
				$export_name =~ s/^[&]//;
				$class->exporter_register_generator($export_name, $subname);
				push @export_names, $export_name;
			}
			else {
				$croak->("Invalid export notation '$token'");
			}
		}
		if (!@export_names) { # if list was empty or only tags...
			push @export_names, _exporter_get_coderef_name($coderef);
			${$class.'::EXPORT'}{$export_names[-1]}= $coderef;
		}
		$class->exporter_register_tag_members($_, @export_names) for keys %tags;
		return 1;
	}
	return;
}

sub exporter_setup {
	my ($self, $version)= @_;
	push @{$self->{into}.'::ISA'}, ref($self);
	strict->import;
	warnings->import;
	if ($version == 1) {
		# Declare 'our %EXPORT'
		*{$self->{into}.'::EXPORT'}= \%{$self->{into}.'::EXPORT'};
		# Make @EXPORT and $EXPORT_TAGS{default} be the same arrayref.
		# Allow either one to have been declared already.
		my $tags= \%{$self->{into}.'::EXPORT_TAGS'};
		*{$self->{into}.'::EXPORT'}= $tags->{default}
			if ref $tags->{default} eq 'ARRAY';
		$tags->{default} ||= \@{$self->{into}.'::EXPORT'};
		# Export the 'export' function.
		*{$self->{into}.'::export'}= \&_exporter_export_from_caller;
	}
	elsif ($version) {
		$croak->("Unknown export API version $version");
	}
}

sub _exporter_export_from_caller {
	unshift @_, scalar caller;
	goto $_[0]->can('exporter_export');
}
sub exporter_export {
	my $class= shift;
	my ($export, $is_gen, $sigil, $name, $args, $ref);
	arg_loop: for (my $i= 0; $i < @_;) {
		$export= $_[$i++];
		ref $export and $croak->("Expected non-ref export name at argument $i");
		# If they provided the ref, capture it from arg list.
		$ref= ref $_[$i]? $_[$i++] : undef;
		# Common case first - ordinary functions
		if ($export =~ /^\w+$/) {
			if ($ref) {
				ref $ref eq 'CODE' or $croak->("Expected CODEref following '$export'");
			} else {
				$ref= $class->can($export) or $croak->("Export '$export' not found in $class");
			}
			${$class.'::EXPORT'}{$export}= $ref;
		}
		# Next, check for generators
		elsif (($is_gen, $sigil, $name)= ($export =~ /^(=?)([\$\@\%\*]?)(\w+)$/)) {
			if ($is_gen) {
				if ($ref) {
					# special case, remove ref on method name (since it isn't possible to pass
					# a plain scalar as the second asrgument)
					$ref= $$ref if ref $ref eq 'SCALAR';
					$class->exporter_register_generator($sigil.$name, $ref);
				} else {
					for (@{ $sigil_to_generator_prefix{$sigil} }) {
						my $method= $_ . $name;
						if ($class->can($method)) {
							$class->exporter_register_generator($sigil.$name, $method);
							next arg_loop;
						}
					}
					$croak->("Export '$export' not found in package $class, nor a generator $sigil_to_generator_prefix{$sigil}[0]");
				}
			}
			else {
				$ref ||= $class->_exporter_get_ref_to_package_var($sigil, $name);
				ref $ref eq $sigil_to_reftype{$sigil} or (ref $ref eq 'REF' && $sigil eq '$')
					or $croak->("'$export' should be $sigil_to_reftype{$sigil} but you supplied ".ref($ref));
				${$class.'::EXPORT'}{$sigil.$name}= $ref;
			}
		}
		# Tags ":foo"
		elsif (($is_gen, $name)= ($export =~ /^(=?):(\w+)$/)) {
			if ($is_gen && !$ref) {
				my $gen= $sigil_to_generator_prefix{':'}.$name;
				$class->can($gen)
					or $croak->("Can't find generator for tag $name : '$gen'");
				$ref= $gen;
			}
			ref $ref eq 'ARRAY'? $class->exporter_register_tag_members($name, @$ref)
				: $class->exporter_register_generator($export, $ref);
		}
		# Options "-foo" or "-foo(3)"
		elsif (($name, $args)= ($export =~ /^-(\w+)(?:\(([0-9]+|\*|\?)\))?$/)) {
			if ($ref) {
				ref $ref eq 'CODE' or (ref $ref eq 'SCALAR' and $class->can($ref= $$ref))
					or $croak->("Option '$export' must be followed by coderef or method name as scalar ref");
			} else {
				$class->can($name)
					or $croak->("Option '$export' defaults to a method '$name' which does not exist on $class");
				$ref= $name;
			}
			$class->exporter_register_option($name, $ref, $args);
		}
		else {
			$croak->("'$export' is not a valid export syntax");
		}
	}
}

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

Exporter::Extensible - Create easy-to-extend modules which export symbols

=head1 SYNOPSIS

Define a module with exports

  package My::Utils;
  use Exporter::Extensible -exporter_setup => 1;

  export(qw( foo $x @STUFF -strict_and_warnings ), ':baz' => ['foo'] );

  sub foo { ... }

  sub strict_and_warnings {
    strict->import;
    warnings->import;
  }

Create a new module which exports all that, and more

  package My::MoreUtils;
  use My::Utils -exporter_setup => 1;
  sub util_fn3 : Export(:baz) { ... }

Use the module

  use My::MoreUtils qw( -strict_and_warnings :baz @STUFF );
  # Use the exported things
  push @STUFF, foo(), util_fn3();

=head1 DESCRIPTION

As a module author, you have dozens of exporters to choose from, so I'll try to get straight to
the pros/cons of this module:

=head2 Pros

=over

=item Extend Your Module

This exporter focuses on the ability and ease of letting you "subclass" a module-with-exports to
create a derived module-with-exports.  It supports multiple inheritance, for things like tying
together all your utility modules into a mega-utility module.

=item Extend Behavior of C<import>

This exporter supports lots of ways to add custom processing during 'import' without needing to
dig into the implementation.

=item More than just subs

This module supports exporting C<foo>, C<$foo>, C<@foo>, C<%foo>, or even C<*foo>.  It also
supports tags (C<:foo>) and options (C<-foo>).

=item Be Lazy

This exporter supports on-demand generators for symbols, as well as tags!  So if you have a
complicated or expensive list of exports you can wait until the first time each is requested
before finding out whether it is available or loading the dependent module.

=item Full-featured

This exporter attempts to copy useful features from other popular exporters, like renaming
imports with C<-prefix>/C<-suffix>/C<-as>, excluding symbols with C<-not>, scoped-unimport,
passing options to generators, importing to things other than C<caller>, etc.

=item More-Than-One-Way-To-Declare-Exports

Pick your favorite.  You can use the L<export> do-what-I-mean function, method attributes, the
C<< __PACKAGE__->exporter_ ... >> API, or declare package variables similar to L<Exporter>.

=item No Non-core Dependencies (for Perl E<8805> 5.12)

Because nobody likes big dependency trees.



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