package Pcore::Core::Exporter; use common::header; use Scalar::Util qw[looks_like_number]; our $CACHE; sub import { my $self = shift; # parse tags and pragmas my $import = parse_import( $self, @_ ); # find caller my $caller = $import->{pragma}->{caller} // 0; $caller = caller $caller if looks_like_number $caller; # export import method *{"$caller\::import"} = \&_import; return; } sub parse_import { my $caller = shift; my $res; my $export_pragma = do { no warnings qw[once]; ${"$caller\::EXPORT_PRAGMA"}; }; while ( my $arg = shift ) { if ( ref $arg ) { die qq[Invalid value in the import specification in the package "$caller". References are not supported.]; } elsif ( substr( $arg, 0, 1 ) eq q[-] ) { substr $arg, 0, 1, q[]; if ( $arg eq 'caller' ) { $res->{pragma}->{caller} = shift; } elsif ( $export_pragma && exists $export_pragma->{$arg} ) { $res->{pragma}->{$arg} = 1; } else { die qq[Unknown exporter pragma found "-$arg" while importing package "$caller"]; } } else { $res->{import}->{$arg} = undef; } } return $res; } sub _import { my $from = shift; # parse tags and pragmas my $import = parse_import( $from, @_ ); # find caller my $to = $import->{pragma}->{caller} // 0; $to = caller $to if looks_like_number $to; # protection from re-export to myself return if $to eq $from; if ( !exists $CACHE->{$to} ) { my $export = ${"$from\::EXPORT"}; $CACHE->{$from} = defined $export ? _parse_export( $from, $export ) : undef; } _export_tags( $from, $to, $import->{import} ) if defined $CACHE->{$from}; return; } sub _parse_export ( $from, $export ) { my $res; $export = { ALL => $export } if ref $export eq 'ARRAY'; my $tags; # 0 - processing, 1 - done my $process_tag = sub ($tag) { # tag is already processed return if $tags->{$tag}; die qq[Cyclic reference found whils processing export tag "$tag"] if exists $tags->{$tag} && !$tags->{$tag}; $tags->{$tag} = 0; for ( $export->{$tag}->@* ) { my $sym = $_; my $type = $sym =~ s/\A([:&\$@%*])//sm ? $1 : q[]; if ( $type ne q[:] ) { $type = q[] if $type eq q[&]; $res->{$tag}->{ $type . $sym } = 1; $res->{ALL}->{ $type . $sym } = [ $sym, $type ]; } else { die qq["ALL" export tag can not contain references to the other tags in package "$from"] if $tag eq 'ALL'; __SUB__->($sym); $res->{$tag}->@{ keys $res->{$sym}->%* } = values $res->{$sym}->%*; } } # mark tag as processed $tags->{$tag} = 1; return; }; for my $tag ( keys $export->%* ) { $process_tag->($tag); } return $res; } sub _export_tags ( $from, $to, $import ) { my $export = $CACHE->{$from}; if ( !$import ) { if ( !exists $export->{DEFAULT} ) { return; } else { $import->{':DEFAULT'} = undef; } } else { die qq[Package "$from" doesn't export anything] if !$export; } # gather symbols to export my $symbols; for my $sym ( keys $import->%* ) { my $no_export; my $is_tag; if ( $sym =~ s/\A([!:])//sm ) { if ( $1 eq q[!] ) { $no_export = 1; $is_tag = 1 if $sym =~ s/\A://sm; } else { $is_tag = 1; } } if ($is_tag) { die qq[Unknown tag ":$sym" to import from "$from"] if !exists $export->{$sym}; if ($no_export) { delete $symbols->@{ keys $export->{$sym}->%* }; } else { $symbols->@{ keys $export->{$sym}->%* } = (); } } else { # remove "&" sigil $sym =~ s/\A&//sm; my $alias; ( $sym, $alias ) = $sym =~ /(.+)=(.+)/sm if index( $sym, q[=] ) > 0; die qq[Unknown symbol "$sym" to import from package "$from"] if !exists $export->{ALL}->{$sym}; if ($no_export) { delete $symbols->{$sym}; } else { $symbols->{$sym} = $alias; } } } # export if ( $symbols->%* ) { my $export_all = $export->{ALL}; my $on_export = do { no warnings qw[once]; *{"$from\::ON_EXPORT"}{CODE}; }; for my $sym ( keys $symbols->%* ) { $on_export->($sym) if $on_export; # skip symbol if it is not exists in symbol table next if !defined *{"$from\::$export_all->{$sym}->[0]"}; my $type = $export_all->{$sym}->[1]; my $alias = $symbols->{$sym} // $export_all->{$sym}->[0]; *{"$to\::$alias"} = $type eq q[] ? \&{"$from\::$export_all->{$sym}->[0]"} : $type eq '$' ? \${"$from\::$export_all->{$sym}->[0]"} : $type eq '@' ? \@{"$from\::$export_all->{$sym}->[0]"} : $type eq '%' ? \%{"$from\::$export_all->{$sym}->[0]"} : $type eq '*' ? *{"$from\::$export_all->{$sym}->[0]"} : die; } } return; } 1; ## -----SOURCE FILTER LOG BEGIN----- ## ## PerlCritic profile "common" policy violations: ## +------+----------------------+----------------------------------------------------------------------------------------------------------------+ ## | Sev. | Lines | Policy | ## |======+======================+================================================================================================================| ## | 3 | 37, 49, 96, 113, | ErrorHandling::RequireCarping - "die" used instead of "croak" | ## | | 146, 169, 187, 224 | | ## |------+----------------------+----------------------------------------------------------------------------------------------------------------| ## | 3 | 134 | Subroutines::ProhibitExcessComplexity - Subroutine "_export_tags" with high complexity score (29) | ## |------+----------------------+----------------------------------------------------------------------------------------------------------------| ## | 2 | 1 | Modules::RequireVersionVar - No package-scoped "$VERSION" variable found | ## +------+----------------------+----------------------------------------------------------------------------------------------------------------+ ## ## -----SOURCE FILTER LOG END----- __END__ =pod =encoding utf8 =head1 NAME Pcore::Core::Exporter =head1 SYNOPSIS use Pcore::Core::Exporter; our $EXPORT = [ ...SYMBOLS TO EXPORT... ]; or our $EXPORT = { TAG1 => [qw[sub1 $var1 ... ]], TAG2 => [qw[:TAG1 sub2 $var2 ... ]], DEFAULT => [qw[:TAG1 :TAG2 sym3 ...]], }; our $EXPORT_PRAGMA = { trigger => 0, option => 1, }; ... use Package qw[-trigger -option OPTION_VALUE :TAG1 !:TAG2 sub1 !sub2 $var1 !$var2 @arr1 !@arr2 %hash1 !%hash2 *sym1 !*sym2], {}; # export aliases use Package qw[$SYM=alias1 @SYM=alias2 sub=alias3] =head1 DESCRIPTION Tag ":ALL" is reserver and is created automatically. If no symbols / tags are specified for import - ":DEFAULT" tag will be exported, if defined. =head1 SEE ALSO =cut