package Poet::Import;
$Poet::Import::VERSION = '0.14';
use Carp;
use Poet::Moose;
use Try::Tiny;
use strict;
use warnings;

our @CARP_NOT = qw(Poet Poet::Script);

has 'default_tags' => ( init_arg => undef, lazy_build => 1 );
has 'env'          => ( required => 1, weak_ref => 1 );
has 'valid_vars'   => ( init_arg => undef, lazy_build => 1 );

method _build_valid_vars () {
    my @provide_methods = grep { /^provide_var_/ } $self->meta->get_method_list;
    return [ sort( map { substr( $_, 12 ) } @provide_methods ) ];
}

method _build_default_tags () {
    return ['debug'];
}

method export_to_level ($level, @params) {
    my ($caller) = caller($level);
    $self->export_to_class($caller);
    foreach my $param (@params) {
        if ( substr( $param, 0, 1 ) eq '$' ) {
            $self->export_var_to_level( substr( $param, 1 ), $level + 1 );
        }
        elsif ( substr( $param, 0, 1 ) eq ':' ) {
            $self->export_tag_to_level( substr( $param, 1 ), $level + 1 );
        }
    }
    foreach my $tag ( @{ $self->default_tags } ) {
        $self->export_tag_to_level( $tag, $level + 1 );
    }
}

method export_to_class ($class) {
}

method export_var_to_level ($var, $level) {
    my $provide_method = "provide_var_" . $var;
    if ( $self->can($provide_method) ) {
        my ($caller) = caller($level);
        my $value = $self->$provide_method($caller);
        no strict 'refs';
        *{ $caller . "\::$var" } = \$value;
    }
    else {
        croak sprintf( "unknown import var '\$$var': valid import vars are %s",
            join( ", ", map { "'\$$_'" } grep { $_ ne 'env' } @{ $self->valid_vars } ) );
    }
}

method export_tag_to_level ($tag, $level) {
    my $util_class;
    try {
        $util_class = $self->env->app_class( "Util::" . ucfirst($tag) );
    }
    catch {
        croak "problem with import tag ':$tag' ($_)";
    };
    $util_class->export_to_level( $level + 1, $util_class, ':all' );
}

method provide_var_cache ($caller) {
    $self->env->app_class('Cache')->new( namespace => $caller );
}

method provide_var_conf ($caller) {
    $self->env->conf();
}

method provide_var_env ($caller) {
    $self->env;
}

method provide_var_log ($caller) {
    $self->env->app_class('Log')->get_logger( category => $caller );
}

method provide_var_poet ($caller) {
    $self->env;
}

1;

__END__

=pod

=head1 NAME

Poet::Import -- Import Poet quick vars and utilities

=head1 SYNOPSIS

    # In a script...
    use Poet::Script qw($conf $poet $log :file);

    # In a module...
    use Poet qw($conf $poet $log :file);

=head1 DESCRIPTION

Poet makes it easy to import certain variables (known as "quick vars") and
utility sets into any script or module in your environment.

In a script:

    use Poet::Script qw(...);

and in a module:

    use Poet qw(...);

where C<...> contains one or more quick var names (e.g. C<$conf>, C<$poet>)
and/or utility tags (e.g. C<:file>, C<:web>).

(Note that C<use Poet::Script> is also necessary for initializing the
environment, even if you don't care to import anything, whereas C<use Poet> has
no effect other than importing.)

=head1 QUICK VARS

Here is the built-in list of quick vars you can import. Some of the variables
are singletons, and some of them are specific to each package they are imported
into.

=over

=item $poet

The global environment object, provided by
L<Poet::Environment|Poet::Environment>. This provides information such as the
root directory and paths to subdirectories.

For backward compatibility this is also available as C<$env>.

=item $conf

The global configuration object, provided by L<Poet::Conf|Poet::Conf>.

=item $cache

The cache for the current package, provided by L<Poet::Cache|Poet::Cache>.

=item $log

The logger for the current package, provided by L<Poet::Log|Poet::Log>.

=back

=head1 UTILITIES

=head2 Default utilities

The utilities in L<Poet::Util::Debug|Poet::Util::Debug> are always imported,
with no tag necessary.

=head2 :file

This tag imports all the utilities in L<Poet::Util::File|Poet::Util::File>.

=head2 :web

This tag imports all the utilities in L<Poet::Util::Web|Poet::Util::Web>. It is
automatically included in all Mason components.

=head1 MASON COMPONENTS

Every Mason component automatically gets this on top:

    use Poet qw($conf $poet :web);

C<$m-E<gt>cache> and C<$m-E<gt>log> will get you the cache and log objects for
a particular Mason component.

=head1 CUSTOMIZING

=head2 Adding variables

To add your own variable, define a method called provide_var_I<varname> in
C<MyApp::Import>. For example to add a variable C<$dbh>:

    package MyApp::Import;
    use Poet::Moose;
    extends 'Poet::Import';

    method provide_var_dbh ($caller) {
        # Generate and return a dbh.
        # $caller is the package importing the variable.
        # $poet is the current Poet environment.
    }

C<provide_dbh> can return a single global value, or a dynamic value depending
on C<$caller>.

Now your scripts and libraries can do

    use Poet::Script qw($dbh);
    use Poet qw($dbh);

=head2 Adding utility tags

To add your own utility tag, define a class C<MyApp::Util::Mytagname> that
exports a set of functions via the ':all' tag. For example:

    package MyApp::Util::Hash;
    use Hash::Util qw(hash_seed all_keys);
    use Hash::MoreUtils qw(slice slice_def slice_exists);
    
    our @EXPORT_OK = qw(hash_seed all_keys slice slice_def slice_exists);
    our %EXPORT_TAGS = ( 'all' => \@EXPORT_OK );

    1;

Now your scripts and libraries can do

    use Poet::Script qw(:hash);
    use Poet qw(:hash);

=head2 Other exports

To export other general things to the calling class, you can override
C<export_to_class>, which takes the calling class as its argument. e.g.

    package MyApp::Import;
    use Poet::Moose;
    extends 'Poet::Import';

    before 'export_to_class' => sub {
        my ($self, $class) = @_;
        no strict 'refs';
        %{$class . "::some_name"} = ...;
    }

=over

=back

=head1 SEE ALSO

L<Poet|Poet>

=head1 AUTHOR

Jonathan Swartz <swartz@pobox.com>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2012 by Jonathan Swartz.

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

=cut
