#+##############################################################################
#                                                                              #
# File: No/Worries/Export.pm                                                   #
#                                                                              #
# Description: symbol exporting without worries                                #
#                                                                              #
#-##############################################################################

#
# module definition
#

package No::Worries::Export;
use strict;
use warnings;
our $VERSION  = "1.6";
our $REVISION = sprintf("%d.%02d", q$Revision: 1.7 $ =~ /(\d+)\.(\d+)/);

#
# used modules
#

use Params::Validate qw(validate_with :types);

#
# simple yet powerful export control
#

sub export_control ($$$@) {
    my($callpkg, $pkg, $exported, @names) = @_;
    my($name, $regexp, $ref);

    validate_with(
        params => \@_,
        spec => [ { type => SCALAR }, { type => SCALAR }, { type => HASHREF } ],
        allow_extra => 1,
    );
    while (@names) {
        $name = shift(@names);
        # special case for * and /.../ and x.y
        if ($name eq "*") {
            unshift(@names, grep(!ref($exported->{$_}), keys(%{ $exported })));
            next;
        } elsif ($name =~ /^\/(.*)\/$/) {
            $regexp = $1;
            unshift(@names, grep(/$regexp/, grep(!ref($exported->{$_}),
                                                 keys(%{ $exported }))));
            next;
        } elsif ($name =~ /^\d/) {
            # version checking via UNIVERSAL
            $pkg->VERSION($name);
            next;
        }
        die("\"$name\" is not exported by the $pkg module\n")
            unless defined($exported->{$name});
        $ref = ref($exported->{$name});
        if ($ref eq "") {
            # normal symbol
            if ($name =~ /^(\w+)$/) {
                # function
                no strict qw(refs);
                no warnings qw(once prototype);
                *{"${callpkg}::${1}"} = \&{"${pkg}::${1}"};
            } elsif ($name =~ /^\$(\w+)$/) {
                # scalar
                no strict qw(refs);
                *{"${callpkg}::${1}"} = \${"${pkg}::${1}"};
            } elsif ($name =~ /^\@(\w+)$/) {
                # array
                no strict qw(refs);
                *{"${callpkg}::${1}"} = \@{"${pkg}::${1}"};
            } elsif ($name =~ /^\%(\w+)$/) {
                # hash
                no strict qw(refs);
                *{"${callpkg}::${1}"} = \%{"${pkg}::${1}"};
            } else {
                die("unsupported export by the $pkg module: $name\n");
            }
        } elsif ($ref eq "CODE") {
            # special symbol
            $exported->{$name}->($name);
        } else {
            die("unsupported export by the $pkg module: $name=$ref\n");
        }
    }
}

#
# export control
#

sub import : method {
    my($pkg, %exported);

    $pkg = shift(@_);
    grep($exported{$_}++, qw(export_control));
    export_control(scalar(caller()), $pkg, \%exported, @_);
}

1;

__DATA__

=head1 NAME

No::Worries::Export - symbol exporting without worries

=head1 SYNOPSIS

  use No::Worries::Export qw(export_control);

  sub foo () { ... }

  our $bar = 42;

  sub import : method {
      my($pkg, %exported);
      $pkg = shift(@_);
      grep($exported{$_}++, qw(foo $bar));
      export_control(scalar(caller()), $pkg, \%exported, @_);
  }

=head1 DESCRIPTION

This module eases symbol exporting by providing a simple yet powerful
alternative to the L<Exporter> module.

The symbols that can be imported are defined in a hash (the third
argument of export_control()), the key being the symbol name and the
value being:

=over

=item * a scalar: indicating a normal symbol

=item * a code reference: to be called at import time

=back

The normal symbols can be functions (such as C<foo>), scalars
(<$foo>), arrays (<@foo>) or hashes (<%foo>).

All the normal symbols can be imported at once by using an asterisk in
the import code:

  use Foo qw(*);

Alternatively, a regular expression can be given to filter what to
import:

  # import "foo" and all the normal symbols starting with "bar"
  use Foo qw(foo /^bar/);

The special symbols can be used to execute any code. For instance:

  # exporting module
  our $backend = "stdout";
  sub import : method {
      my($pkg, %exported);
      $pkg = shift(@_);
      $exported{syslog} = sub { $backend = "syslog" };
      export_control(scalar(caller()), $pkg, \%exported, @_);
  }

  # importing code
  use Foo qw(syslog);

Finally, anything looking like a number will trigger a version check:

  use Foo qw(1.2);
  # will trigger
  Foo->VERSION(1.2);

See L<UNIVERSAL> for more information on the VERSION() mthod.

=head1 FUNCTIONS

This module provides the following function (not exported by default):

=over

=item export_control(CALLERPKG, PKG, EXPORT, NAMES...)

control the symbols exported by the module; this should be called from
an C<import> method

=back

=head1 SEE ALSO

L<Exporter>,
L<No::Worries>.

=head1 AUTHOR

Lionel Cons L<http://cern.ch/lionel.cons>

Copyright (C) CERN 2012-2019
