Symbol::Util - Additional utils for Perl symbols manipulation


Symbol-Util documentation Contained in the Symbol-Util distribution.

Index


Code Index:

NAME

Top

Symbol::Util - Additional utils for Perl symbols manipulation

SYNOPSIS

Top

  use Symbol::Util ':all';

  my $caller = caller;
  *{ fetch_glob("${caller}::foo") } = sub { "this is foo" };
  my $coderef = fetch_glob("${caller}::bar", "CODE");
  sub baz { 42; }
  export_glob($caller, "baz");

  print join "\n", keys %{ stash("main") };

  delete_glob("${caller}::foo", "CODE");

  use constant PI => 3.14159265;
  delete_sub "PI";   # remove constant from public API

  require YAML;
  export_package(__PACKAGE__, "YAML", "Dump");   # import YAML::Dump
  unexport_package(__PACKAGE, "YAML");   # remove imported symbols

  no Symbol::Util;   # clean all symbols imported from Symbol::Util

DESCRIPTION

Top

This module provides a set of additional functions useful for Perl symbols manipulation.

stash and fetch_glob functions gets stash or glob without need to use no strict 'refs'.

delete_glob function allows to delete specific slot of symbol name without deleting others.

delete_sub removes the symbol from class API. This symbol won't be available as an object method.

export_package works like Exporter module and allows to export symbols from one package to other.

unexport_package allows to delete previously exported symbols.

IMPORTS

Top

By default, the class does not export its symbols.

use Symbol::Util ':all';

Imports all available symbols.

no Symbol::Util;

Deletes all imported symbols from caller name space.

FUNCTIONS

Top

stash( name : Str ) : HashRef

Returns a refernce to the stash for the specified name. If the stash does not already exists it will be created. The name of the stash does not include the :: at the end. It is safe to use this function with use strict 'refs'.

This function is taken from Kurila, a dialect of Perl.

  print join "\n", keys %{ Symbol::stash("main") };

fetch_glob( name : Str ) : GlobRef
fetch_glob( name : Str, slot : Str ) : Ref

Returns a reference to the glob for the specified symbol name. If the symbol does not already exists it will be created. If the symbol name is unqualified it will be looked up in the calling package. It is safe to use this function with use strict 'refs'.

If slot argument is defined and this slot has defined value, reference to its value is returned. The slot argument can be one of the following strings: SCALAR, ARRAY, HASH, CODE, IO, FORMAT).

This function is taken from Kurila, a dialect of Perl.

  my $caller = caller;
  *{ fetch_glob("${caller}::foo") } = sub { "this is foo" };
  my $coderef = fetch_glob("${caller}::foo", "CODE");

list_glob_slots( name ) : Maybe[Array]

Returns a list of slot names for glob with specified name which have defined value. If the glob is undefined, the undef value is returned. If the glob is defined and has no defined slots, the empty list is returned.

The SCALAR slot is used only if it contains defined value.

  print join ",", list_glob_slots("foo");

export_glob( target, name : Str ) : GlobRef
export_glob( target, name : Str, slots : Array ) : Ref

Exports a glob name to the target package. Optionally exports only specified slots of the glob.

  sub my_function { ... };
  sub import {
      my $caller = caller;
      export_glob($caller, "my_function");
  }

delete_glob( name : Str, slots : Array[Str] ) : Maybe[GlobRef]

Deletes the specified symbol name if slots are not specified, or deletes the specified slots in the symbol name (could be one or more of the following strings: SCALAR, ARRAY, HASH, CODE, IO, FORMAT).

Function returns the glob reference if there are any slots defined.

  our $FOO = 1;
  sub FOO { "bar" };

  delete_glob("FOO", "CODE");

  print $FOO;  # prints "1"
  FOO();       # error: sub not found

delete_sub( name : Str ) : Maybe[GlobRef]

Deletes (or hides) the specified subroutine name from class API. It means that this subroutine will be no longer available as the class method. The purpose of this function is the same as namespace::clean pragma has.

Function returns the glob reference if there are any other slots still defined than <CODE> slot.

  package My::Class;

  use constant PI => 3.14159265;

  use Symbol::Util 'delete_sub';
  delete_sub "PI";   # remove constant from public API
  no Symbol::Util;   # remove also Symbol::Util::* from public API

  sub area {
      my ($self, $r) = @_;
      return PI * $r ** 2
  }

  print My::Class->area(2);   # prints 12.5663706
  print My::Class->PI;        # can't locate object method

export_package( target : Str, package : Str, names : Array[Str] ) : Bool
export_package( target : Str, package : Str, spec : HashRef, names : Array[Str] ) : Bool

Exports symbols from package to target. If spec is defined as hash reference, it contains the specification for exporter. Otherwise the standard global variables of package are used (@EXPORT, @EXPORT_OK and %EXPORT_TAGS) to build the specification for exporter. The optional list of names defines an import list.

The spec is a reference to hash with following keys:

EXPORT

Contains the list of default imports. It is the same as @EXPORT variable.

OK

Contains the list of allowed imports. It is the same as @EXPORT_OK variable.

TAGS

Contains the hash with tags. It is the same as %EXPORT_TAGS variable.

See Exporter documentation for explanation of these global variables and list of names.

The export_package function can export symbols from an external package to an external package. This function can also be used as a helper in import method.

  package My::Package;
  sub myfunc { };
  sub import {
      my ($package, @names) = @_;
      my $caller = caller();
      return export_package($caller, $package, {
          OK => [ qw( myfunc ) ],
      }, @names);
  };

All exported symbols are tracked and later can be removed with unexport_package function.

The function returns true value if there were no errors.

unexport_package( target, package ) : Bool

Deletes symbols previously exported from package to target with export_package function. If the symbol was CODE reference it is deleted with delete_sub function. Otherwise it is deleted with delete_glob function with proper slot as an argument.

Deleting with delete_sub function means that this symbol is not available via class API as an object method.

  require YAML;
  export_package(__PACKAGE__, "YAML", "dump");
  unexport_package(__PACKAGE__, "YAML");

This function can be used as a helper in unimport method.

  package My::Package;
  sub unimport {
      my ($package, @names) = @_;
      my $caller = caller();
      return unexport_package($caller, $package);
  };

  package main;
  use My::Package qw(something);
  no My::Package;
  main->something;   # Can't locate object method

The function returns true value if there were no errors.

SEE ALSO

Top

Symbol, Sub::Delete, namespace::clean, Exporter.

BUGS

Top

fetch_glob returns undef value if SCALAR slot contains undef value.

delete_glob deletes SCALAR slot if it exists and contains undef value.

delete_glob always deletes FORMAT slot.

If you find the bug or want to implement new features, please report it at http://rt.cpan.org/NoAuth/Bugs.html?Dist=Symbol-Util

AUTHOR

Top

Piotr Roszatycki <dexter@cpan.org>

COPYRIGHT

Top


Symbol-Util documentation Contained in the Symbol-Util distribution.
#!/usr/bin/perl -c

package Symbol::Util;


use 5.006;

use strict;
use warnings;

our $VERSION = '0.0202';


# Exported symbols $EXPORTED{$target}{$package}{$name}{$slot} = 1
my %EXPORTED;


## no critic qw(ProhibitSubroutinePrototypes)
## no critic qw(RequireArgUnpacking)

sub import {
    my ($package, @names) = @_;

    my $caller = caller();

    my @EXPORT_OK = grep { /^[a-z]/ && !/^(?:import|unimport)$/ } keys %{ stash(__PACKAGE__) };
    my %EXPORT_TAGS = ( all => [ @EXPORT_OK ] );

    return export_package($caller, $package, {
        OK => [ @EXPORT_OK ],
        TAGS => { %EXPORT_TAGS },
    }, @names);
};


sub unimport {
    my ($package) = @_;

    my $caller = caller();

    return unexport_package($caller, $package);
};


sub stash ($) {
    no strict 'refs';
    return \%{ *{ $_[0] . '::' } };
};


sub fetch_glob ($;$) {
    my ($name, $slot) = @_;

    $name = caller() . "::$name" unless $name =~ /::/;

    no strict 'refs';

    if (defined $slot) {
        return if $slot eq 'SCALAR' and not defined ${ *{ $name }{SCALAR} };
        return *{ $name }{$slot};
    };

    return \*{ $name };
};


sub list_glob_slots ($) {
    my ($name) = @_;

    $name = caller() . "::$name" unless $name =~ /::/;

    no strict 'refs';

    return if not defined *{ $name };

    my @slots;

    push @slots, 'SCALAR'
        if defined *{ $name }{SCALAR} and defined ${ *{ $name }{SCALAR} };

    foreach my $slot (qw( ARRAY HASH CODE IO )) {
        push @slots, $slot if defined *{ $name }{$slot};
    };

    return @slots;
};


sub export_glob ($$;@) {
    my ($target, $name, @slots) = @_;

    $name = caller() . "::$name" unless $name =~ /::/;
    (my $subname = $name) =~ s/^(.*):://;

    @slots = qw( SCALAR ARRAY HASH CODE IO ) unless @slots;

    no strict 'refs';

    return if not defined *{ $name };

    my $targetname = "${target}::$subname";

    my $defined;
    foreach my $slot (@slots) {
        next if $slot eq 'SCALAR' and not defined ${ *{ $name }{$slot} };
        next if not defined *{ $name }{$slot};
        *{ $targetname } = *{ $name }{$slot};
        $defined = 1;
    };

    return $defined ? \*{ $targetname } : undef;
};


sub delete_glob ($;@) {
    my ($name, @slots) = @_;

    $name = caller() . "::$name" unless $name =~ /::/;
    $name =~ /^(.*)::([^:]*)/;
    my ($package, $subname) = ($1, $2);  ## no critic qw(ProhibitCaptureWithoutTest)

    my $stash = stash($package);

    if (@slots) {
        my %delete = map { $_ => 1 } @slots;
        my %backup;

        foreach my $slot (list_glob_slots($name)) {
            $backup{$slot} = fetch_glob($name, $slot)
                if not $delete{$slot};
        };

        undef $stash->{$subname};

        foreach my $slot (keys %backup) {
            *{ fetch_glob($name) } = $backup{$slot};
        };

        return fetch_glob($name);
    }
    else {
        # delete all slots
        undef $stash->{$subname};
    };

    return;
};


sub delete_sub ($) {
    my ($name) = @_;

    $name = caller() . "::$name" unless $name =~ /::/;
    $name =~ /^(.*)::([^:]*)/;
    my ($package, $subname) = ($1, $2);  ## no critic qw(ProhibitCaptureWithoutTest)

    return if not defined fetch_glob($name, 'CODE');

    my $stash = stash($package);

    my %backup;

    foreach my $slot (list_glob_slots($name)) {
        $backup{$slot} = fetch_glob($name, $slot);
    };

    *{ fetch_glob($name) } = $backup{CODE};
    delete $backup{CODE};

    delete $stash->{$subname};

    foreach my $slot (keys %backup) {
        *{ fetch_glob($name) } = $backup{$slot};
    };

    return %backup ? fetch_glob($name) : undef;
};


sub export_package ($$@) {
    my $target = shift;
    my $package = shift;
    my $spec = ref $_[0] eq 'HASH' ? shift : {
        EXPORT => fetch_glob("${package}::EXPORT", "ARRAY"),
        OK     => fetch_glob("${package}::EXPORT_OK", "ARRAY"),
        TAGS   => fetch_glob("${package}::EXPORT_TAGS", "HASH"),
    };

    my @names = @_;

    # support: use Package 3.14 qw();
    return 1 if @names == 1 and $names[0] eq '';

    # default exports on empty list or if first element is negation
    unshift @names, ":DEFAULT" if not @names or @names and $names[0] =~ /^!/;

    my @export = ref ($spec->{EXPORT} || '') eq 'ARRAY' ? @{ $spec->{EXPORT} } : ();
    my @export_ok = ref ($spec->{OK} || '') eq 'ARRAY' ? @{ $spec->{OK} } : ();
    my %export_tags = ref ($spec->{TAGS} || '') eq 'HASH' ? %{ $spec->{TAGS} } : ();

    my %export = map { $_ => 1 } @export;
    my %export_ok = map { $_ => 1 } @export_ok;

    my %names;

    while (my $name = shift @names) {
        if ($name =~ m{^/(.*)/$}) {
            my $pattern = $1;
            $names{$_} = 1 foreach grep { /$pattern/ } (@export, @export_ok);
        }
        elsif ($name =~ m{^!/(.*)/$}) {
            my $pattern = $1;
            %names = map { $_ => 1 } grep { ! /$pattern/ } keys %names;
        }
        elsif ($name =~ /^(!?):DEFAULT$/) {
            my $neg = $1;
            unshift @names, map { "${neg}$_" } @export;
        }
        elsif ($name =~ /^(!?):(.*)$/) {
            my ($neg, $tag) = ($1, $2);
            if (defined $export_tags{$tag}) {
                unshift @names, map { "${neg}$_" } @{ $export_tags{$tag} };
            }
            else {
                require Carp;
                Carp::croak("$name is not a tag of the $package module");
            };
        }
        elsif ($name =~ /^!(.*)$/) {
            $name = $1;
            delete $names{$name};
        }
        elsif (defined $export_ok{$name} or defined $export{$name}) {
            $names{$name} = 1;
        }
        else {
            require Carp;
            Carp::croak("$name is not exported by the $package module");
        };
    };

    foreach my $name (keys %names) {
        my $type = '';
        if ($name =~ s/^(\W)//) {
            $type = $1;
        };

        my @slots;
        if ($type eq '&' or $type eq '') {
            push @slots, 'CODE';
        }
        elsif ($type eq '$') {
            push @slots, 'SCALAR';
        }
        elsif ($type eq '@') {
            push @slots, 'ARRAY';
        }
        elsif ($type eq '%') {
            push @slots, 'HASH';
        }
        elsif ($type eq '*') {
            push @slots, 'IO';
        }
        else {
            require Carp;
            Carp::croak("Can't export symbol $type$name");
        };
        foreach my $slot (@slots) {
            if (defined export_glob($target, "${package}::$name", $slot)) {
                $EXPORTED{$target}{$package}{$name}{$slot} = 1;
            };
        };
    };

    return 1;
};


sub unexport_package ($$) {
    my ($target, $package) = @_;

    if (defined $EXPORTED{$target}{$package}) {
        foreach my $name (keys %{ $EXPORTED{$target}{$package} }) {
            # CODE slot have to be the last one
            foreach my $slot ( qw( SCALAR ARRAY HASH IO CODE ) ) {
                next unless exists $EXPORTED{$target}{$package}{$name}{$slot};
                if ($slot eq 'CODE') {
                    delete_sub("${target}::$name");
                }
                else {
                    delete_glob("${target}::$name", $slot);
                };
            };
        };
        delete $EXPORTED{$target}{$package};
    };

    return 1;
};


1;