The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package package::compute;

use 5.008;
use strict;
use warnings;

BEGIN {
	$package::compute::AUTHORITY = 'cpan:TOBYINK';
	$package::compute::VERSION   = '0.004';
}

use B::Hooks::Parser 0.08 qw();
use Carp qw(confess);

my $count = 0;

sub import
{
	my ($class, $name) = @_;
	
	my $caller = caller;
	my $pkg;
	
	if (not defined $name)
	{
		$pkg = $caller;
	}
	elsif (!ref $name and $name eq '-anon')
	{
		$pkg = sprintf('%s::__ANON__::%s', $class, ++$count);
	}
	elsif (!ref $name and $name eq '-filename')
	{
		# figure out where we're called from
		my (undef, $filename) = caller;

		# figure out where it got loaded via %INC.
		for my $k (sort keys %INC)
		{
			if ($INC{$k} eq $filename)
			{
				$pkg = $k;
				$pkg =~ s<[/\\]><::>g;
				$pkg =~ s<\.pm$><>i; # can this be uppercase on some platforms?
				last;
			}
		}
	}
	else
	{
		$pkg = __RPACKAGE__($name, $caller);
	}
	
	confess("package::compute could not determine package name, died")
		unless defined $pkg;
	
	B::Hooks::Parser::inject("; package $pkg;")
		unless $pkg eq $caller;
	
	no strict 'refs';
	*{"$pkg\::__RPACKAGE__"} = \&__RPACKAGE__;
}

sub __RPACKAGE__
{
	my ($name, $caller) = @_;
	$caller ||= scalar caller;
	return $caller unless defined $name;
	
	my $count;
	while (ref $name eq 'CODE')
		{ $name = $name->($caller); die "too deep" if ++$count > 7 }
	if ($name =~ /^[.]+(?:::|'|\/)/)
		{ $name = "$caller\::$name" }
	
	my @pkg = grep { length and $_ ne '.' } split /(?:::|'|\/)/, $name;
	my @final;
	foreach (@pkg)
	{
		push @final, $_;
		next unless /^([.]+)/;
		confess "invalid relative package path" if length $1 > @final;
		splice(@final, -(length $1));
	}
	
	return join q/::/, @final;
}

1;

__END__

=head1 NAME

package::compute - stop hard-coding your package names

=head1 SYNOPSIS

   package Foo::Bar;  # this is a hard-coded package name
   use 5.010;
   
   {
      use package::compute "../Quux";
      say __PACKAGE__;              # says "Foo::Quux";
      say __RPACKAGE__("./Xyzzy");  # says "Foo::Quux::Xyzzy";
      
      sub hello { say __PACKAGE__ };
   }
   
   say __PACKAGE__;   # says "Foo::Bar" (lexically scoped!)
   Foo::Quux->hello;  # says "Foo::Quux"

=head1 DESCRIPTION

This module allows you to compute package names on the fly at compile
time, rather than hard-coding them as barewords. It is the solution to
the problem (if indeed you consider it to be a problem at all) that you
cannot write this in Perl:

   package $blah;

This module uses L<B::Hooks::Parser> to accomplish its evil goals.

Using this module at all is probably a very bad idea.

=head2 Package Specification

The general syntax for specifying a package with this module is:

   use package::compute EXPR;

Where EXPR is an arbitrary expression which will (caveat!) be evaluated
at compile time, and interpreted roughly the way Perl interprets package
names, with the following bonus features:

=over

=item *

If the package name expression is a coderef, then that coderef is
called and the return value is used instead.

=item *

Slashes may be used to separate package name components in addition
to the usual Perl C<< "::" >> and deprecated C<< "'" >> package
separators.

=item *

The component C<< "." >> at the start of the package name refers to the
caller. (C<< "." >> elsewhere is a no-op.)

=item *

The component C<< ".." >> climbs "up" the package hierarchy.

=item *

The component C<< "..." >> climbs "up" the package hierarchy by two
levels. Et cetera.

=back

Thus the following are all valid ways of expressing package "Foo::Bar":

   ### 
   use package::compute "Foo::Bar";
   ####
   
   ### Using a coderef
   use package::compute sub { join q(::) qw( Foo Bar ) };
   ####
   
   #### Relative package name
   package Foo;
   use package::compute "./Bar";
   ####
   
   ### Climbing the package hierarchy
   package Foo::XXX;
   use package::compute "../Bar";
   ####
   
   ### Climbing the package hierarchy twice
   package Foo::XXX::YYY;
   use package::compute "../../Bar";
   ####
   
   ### Climbing the package hierarchy twice - shortcut
   package Foo::XXX::YYY;
   use package::compute ".../Bar";
   ####

As a special case, you can also do:

   use package::compute -filename;

Which will attempt to determine the package name based on the filename it
is defined in, much like C<autopackage> does.

Also:

   use package::compute -anon;

Will compute an "anonymous" (i.e. arbitrary) package name.

=head2 Utility Function

This module also exports a utility function:

=over

=item C<< __RPACKAGE__($name) >>

Returns a package name as a string, computed in the same way as
C<< use package::compute >> does. An example of its use for object-oriented
code:

	package MyProject;
	
	{
		use package::compute './Person';
		use Moose;
		has name => (is => 'ro');
	}
	
	my $bob = __RPACKAGE__('./Person')->new(name => "Robert");

As you can see, this makes it possible to avoid hard-coded references to
the MyProject::Person class.

C<< __RPACKAGE__ >> doesn't support the special C<< -filename >> and 
C<< -anon >> options.

It is possible to import the C<< __RPACKAGE__ >> function alone, without
the package declaration magic using:

   use package::compute undef;

=back

=head1 BUGS

Please report any bugs to
L<http://rt.cpan.org/Dist/Display.html?Queue=package-compute>.

=head1 SEE ALSO

L<autopackage>,
L<Package::Relative>.

=head1 AUTHOR

Toby Inkster E<lt>tobyink@cpan.orgE<gt>.

=head1 COPYRIGHT AND LICENCE

This software is copyright (c) 2012 by Toby Inkster.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=head1 DISCLAIMER OF WARRANTIES

THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.