Providing sugar *and* object methods in a Moose role - moose

I'm working on a Moose Role that allows the consuming class to emit XML based on an 'xml_path' option specified to one or more attributes, like so:
package MooseX::Role::EmitsXML::Trait::HasXMLPath;
use Moose::Role;
has xml_path => (
'is' => 'ro',
'isa' => 'Str',
'predicate' => 'has_xml_path',
);
has 'namespace' => (
'is' => 'ro',
'isa' => 'Str',
'predicate' => 'has_namespace',
);
has 'cdata' => (
'is' => 'ro',
'isa' => 'Bool',
'predicate' => 'has_cdata',
);
package MooseX::Role::EmitsXML;
our $VERSION = '0.01';
use Moose::Role;
use namespace::autoclean;
use XML::LibXML;
use Moose::Exporter;
sub has_xml {
my ($meta, $attr_name, %opts) = #_;
$opts{'traits'} ||= [];
push #{$opts{'traits'}}, 'MooseX::Role::EmitsXML::Trait::HasXMLPath';
$meta->add_attribute($attr_name, %opts);
}
Moose::Exporter->setup_import_methods(
with_meta => [qw(has_xml)],
also => [qw/Moose/],
);
sub to_xml {
my ( $self, #args ) = #_;
my $doc = XML::LibXML::Document->new();
for my $attr ( map { $self->meta->get_attribute($_) } $self->meta->get_attribute_list ) {
my $reader = $attr->get_read_method;
if ( $attr->does('XMLPath') && $attr->has_xml_path ) {
my $val = $self->$reader();
my $path = $attr->xml_path;
my #elements = split /\//, $path;
if ( $path =~ /^\// ) { # Throw away blank
shift #elements;
}
my $previous;
while ( my $element = shift #elements ) {
my $node;
my $attrs = extract_attrs($element);
( my $node_name = $element ) =~ s/\[.+?\]//g;
if ( !$previous ) {
if ( !$doc->documentElement ) {
$doc->setDocumentElement( XML::LibXML::Element->new($node_name) );
for my $key ( keys %{$attrs} ) {
$doc->documentElement->setAttribute( $key, $attrs->{$key} );
}
}
else {
my $root1 = $doc->documentElement->nodeName;
my $root2 = $element;
if ( $root1 ne $root2 ) {
die qq{MISMATCH! Root elements do not match: "$root1" <> "$root2"};
}
}
$node = $doc->documentElement;
}
else {
($node) = #{ $previous->find(qq{./$element}) };
if ( !$node ) {
$node = XML::LibXML::Element->new($node_name);
for my $key ( keys %{$attrs} ) {
$node->setAttribute( $key, $attrs->{$key} );
}
$previous->addChild($node);
}
}
$previous = $node;
}
# $previous has become the leaf here
$previous->appendText($val);
}
}
}
sub _extract_attrs {
my $element = shift;
my #attr_strings = ($element =~ m/(\[.+?\])/); # Capture everything between [ and ].
if (scalar #attr_strings > 1) {
die q{Invalid attribute specification. Specify multiple attrs as [#attr1=val1,#attr2=val2]};
}
my %attrs;
if (#attr_strings) {
for my $string (split /,/, $attr_strings[0]) {
my ($key, $val) = ($string =~ m/\[#?\s*(\w+)\s*=\s*"(\w+)"\s*\]/);
if (!$key) {
die qq{Malformed attribute specification: "$string". Should be [key="value"] (DOUBLE QUOTES MANDATORY)\n};
}
if (exists $attrs{$key}) {
warn qq{Duplicate key "$key" in attrs};
}
$attrs{$key} = $val;
}
}
return \%attrs;
}
no Moose::Role;
1;
However, when I try to use it:
package Product;
use Moose;
use MooseX::Role::EmitsXML;
# If I comment this out, has_xml works right ($meta is passed as first argument) but I don't have to_xml() available in the
# consuming class.
#
# If I don't, I have to_xml available in the consuming class, but has_xml doesn't work right.
with 'MooseX::Role::EmitsXML';
has_xml 'description' =>
( 'is' => 'ro', 'isa' => 'Str', 'xml_path' => '/Product/DescriptiveInformation/ProductDescription' );
has_xml 'item_number' => ( 'is' => 'ro', 'isa' => 'Str', 'xml_path' => '/Product/Identifiers/ItemNumber' );
has_xml 'catalog_number' => ( 'is' => 'ro', 'isa' => 'Str', 'xml_path' => '/Product/Identifiers/CatalogNumber' );
has_xml 'upc' => ( 'is' => 'ro', 'isa' => 'Int', 'xml_path' => '/Product/Identifiers/UPC' );
has_xml 'color' => ( 'is' => 'ro', 'isa' => 'Str', 'xml_path' => '/Product/DescriptiveInformation/Color' );
has 'that_je_ne_sais_quoi' => ('is' => 'ro', 'isa' => 'Str' );
1;
package main;
use Test::Most;
use XML::LibXML;
my %product_args = (
color => 'periwinkle',
upc => 1234567890123,
item_number => 'THX-1138',
catalog_number => 'KP-1652051819',
description => q{Oh, yes. It's very nice!},
that_je_ne_sais_quoi => q{Something French. Or maybe Swahili.},
);
ok my $p = Product->new(%product_args), 'Created instance of class using role';
ok my $xml = $p->to_xml, 'Output XML';
ok my $doc = XML::LibXML::parse_string($xml), 'XML is valid (or at least parseable)';
for my $key (keys %product_args) {
my $attr = $p->meta->get_attribute($key);
if ($key ne 'that_je_ne_sais_quoi') {
ok $attr->can('has_xml_path'), qq{Predicate 'has_xml_path' present for "$key"};
ok my $path = $attr->xml_path, qq{Got an XML path for "$key"};
1;
}
}
As the comments say, if I comment out with 'MooseX::Role::EmitsXML', then has_xml gets the consuming package's metaclass as the first argument, but the consuming package doesn't have to_xml. If I uncomment it, the consuming package gets to_xml, but has_xml doesn't get the consuming package metaclass. How can I get both to_xml and the has_xml sugar?

Per ether, this is Not How It's Done. Instead, the Role providing with_xml needs to be defined in a separate package, and the "ultimate" Role should apply the aforementioned to the consuming class, like so:
package MooseX::Role::EmitsXML::Trait::HasXMLPath;
use Moose::Role;
has xml_path => (
'is' => 'ro',
'isa' => 'Str',
'predicate' => 'has_xml_path',
);
has 'namespace' => (
'is' => 'ro',
'isa' => 'Str',
'predicate' => 'has_namespace',
);
has 'cdata' => (
'is' => 'ro',
'isa' => 'Bool',
'predicate' => 'has_cdata',
);
package MooseX::Role::EmitsXML::ToXML;
# This package provides the to_xml() method to the consuming class
our $VERSION = '0.01';
use Moose::Role;
use namespace::autoclean;
use XML::LibXML;
sub to_xml {
my ( $self, #args ) = #_;
my $doc = XML::LibXML::Document->new();
for my $attr ( map { $self->meta->get_attribute($_) } $self->meta->get_attribute_list ) {
my $reader = $attr->get_read_method;
if ( $attr->does('MooseX::Role::EmitsXML::Trait::HasXMLPath') && $attr->has_xml_path ) {
my $val = $self->$reader();
my $path = $attr->xml_path;
my #elements = split /\//, $path;
if ( $path =~ /^\// ) { # Throw away blank
shift #elements;
}
my $previous;
while ( my $element = shift #elements ) {
my $node;
my $attrs = extract_attrs($element);
( my $node_name = $element ) =~ s/\[.+?\]//g;
if ( !$previous ) {
if ( !$doc->documentElement ) {
$doc->setDocumentElement( XML::LibXML::Element->new($node_name) );
for my $key ( keys %{$attrs} ) {
$doc->documentElement->setAttribute( $key, $attrs->{$key} );
}
}
else {
my $root1 = $doc->documentElement->nodeName;
my $root2 = $element;
if ( $root1 ne $root2 ) {
die qq{MISMATCH! Root elements do not match: "$root1" <> "$root2"};
}
}
$node = $doc->documentElement;
}
else {
($node) = #{ $previous->find(qq{./$element}) };
if ( !$node ) {
$node = XML::LibXML::Element->new($node_name);
for my $key ( keys %{$attrs} ) {
$node->setAttribute( $key, $attrs->{$key} );
}
$previous->addChild($node);
}
}
$previous = $node;
}
# $previous has become the leaf here
$previous->appendText($val);
}
}
return "$doc";
}
sub _extract_attrs {
my $element = shift;
my #attr_strings = ($element =~ m/(\[.+?\])/); # Capture everything between [ and ].
if (scalar #attr_strings > 1) {
die q{Invalid attribute specification. Specify multiple attrs as [#attr1=val1,#attr2=val2]};
}
my %attrs;
if (#attr_strings) {
for my $string (split /,/, $attr_strings[0]) {
my ($key, $val) = ($string =~ m/\[#?\s*(\w+)\s*=\s*"(\w+)"\s*\]/);
if (!$key) {
die qq{Malformed attribute specification: "$string". Should be [key="value"] (DOUBLE QUOTES MANDATORY)\n};
}
if (exists $attrs{$key}) {
warn qq{Duplicate key "$key" in attrs};
}
$attrs{$key} = $val;
}
}
return \%attrs;
}
no Moose::Role;
1;
package MooseX::Role::EmitsXML;
# This package applies the role providing to_xml to the consuming class,
# and creates the 'has_xml' sugar
use Moose::Exporter;
sub has_xml {
my ($meta, $attr_name, %opts) = #_;
$opts{'traits'} ||= [];
push #{$opts{'traits'}}, 'MooseX::Role::EmitsXML::Trait::HasXMLPath';
$meta->add_attribute($attr_name, %opts);
}
Moose::Exporter->setup_import_methods(
with_meta => [qw(has_xml)],
base_class_roles => [qw(MooseX::Role::EmitsXML::ToXML)],
);

Related

Better way to get all attributes from a Moose class as a hash

I want to get all attributes back from a class as a hash.
Is there any better way to do it than this ?
Ideally(?) I would like to be able to say something like:
my $hash = \%{ Diag->new( {range =>1, code => 'AB'} ) };
But will settle for:
my $d = Diag->new( {range =>1, code => 'AB'} );
my $hash = $d->hash;
package Diag;
use Moose;
my #attrs = qw/range code severity source message/;
has 'range' => ( is => 'rw', isa => 'Int' );
has 'code' => ( is => 'rw', isa => 'String' );
has 'severity' => ( is => 'rw', isa => 'Int' );
has 'source' => ( is => 'rw', isa => 'String' );
has 'message' => ( is => 'rw', isa => 'String' );
sub hash {
my $self = shift;
my $hash = {};
for (#attrs) {
$hash->{$_} = $self->$_;
}
return $hash;
}
no Moose;
1;
EDIT Hash with string output for pack/unpack:
# Combining this attribute and the record_format would be great.
# if $self->record->format worked that would be cool.
has 'record' => (
is => 'ro',
isa => 'HashRef',
default => sub {
{
foo => 'A5',
foo2 => 'A16',
}
);
sub record_format
{
my $self = shift;
my #fields = qw( foo foo2 );
return _build_format_string($self->record, \#fields);
}
sub _build_format_string {
return join '', map { $_[1]->{$_} } #{ $_[2] };
}
EDIT2
I found that if I created an Attribute Trait I could make this a little nicer. This way the hash order is with the attribute and only one format method is needed.
package Order;
use Moose::Role;
has order => (
is => 'ro',
isa => 'ArrayRef',
predicate => 'has_order',
);
Moose::Util::meta_attribute_alias('Order');
1;
package Record;
use Moose;
has 'record' => (
traits => [qw/Order/],
is => 'ro',
isa => 'HashRef',
default => sub {
{
foo => 'A5',
foo2 => 'A16',
},
;
},
order => [qw(foo foo2)]
);
sub format {
my ( $self, $attr ) = #_;
my $fields = $self->meta->get_attribute($attr)->order();
return join '', map { $self->{$attr}{$_} } #$fields;
}
1;
my $r = Record->new();
print $r->format("record");
Outputs: A5A16
I would much rather pack that into a method, but your "ideal" case is almost there
my $data = { %{ Diag->new( {range =>1, code => 'AB'} ) } };
The %{...} returns a (key,value,...) list so you want {} to make a hashref out of it, not \ (which curiously turns it back into an object).
But really, that should be tucked away in a method
my $data = Diag->new(...)->get_data;
package Diag;
...
sub get_data { return { %{$_[0]} } };
...
1;
For purely presentational purposes – to print them out – consider using a module, so you don't have to worry about (or know) which attributes have what reference as a value. I use Data::Dump for conciseness of its output
my $obj = Diag->new(...);
say $obj->stringify(); # whole object serialized
say for $obj->stringify('attr1', 'attr1', ...); # serialized values for each
package Diag;
...
use Data::Dump qw(pp);
...
sub stringify {
my $self = shift;
return map { pp $self->{$_} } #_ if #_;
return { pp %$self } }
}
If native OO is used and not Moo/Moose also overload "" for say $obj; use
use overload q("") => sub { return shift->stringify() }
In Moo and Moose the stringification of object under "" (implied in prints as well) is provided.
By further clarifications the code below doesn't address the actual problem. I'll edit but I am leaving this for now as it was deemed generally useful.
It has come up in comments and question edit that a part of the intent is to be able to retrieve values for attribute(s) as well, and packed. The added code does that, but as there is explicit dereferencing a check with ref should be added so to correctly retrieve all values, from either of arrayref, hashref, or string/number. For example
sub record_format {
my ($self, #attrs) = #_;
#attrs = qw(attr1 attr2 ...) if not #attrs; # default list
my $packed;
foreach my $attr (#attrs) {
my $val = $self->{$attr};
my $rv = ref $val;
if (not $rv) { $packed .= $val }
elsif ($rv eq 'HASH') { $packed .= join '', values %$val }
elsif ($rv eq 'ARRAY') { $packed .= join '', #$val }
}
return $packed;
}
This packs values of the passed attributes or of the listed defaults.
The desired $self->record->format can't work nicely since $self->record doesn't return an object so you can't string another method call. You can write an accessor but if you made it return an object under any circumstances that would likely be a surprising behavior, thus not good design.

Perl: Recursive object instantiation with Moose

In the example code below, I am defining a class Person that can have child objects of the same class.
When I invoke the printTree method, I am expecting the following output
Sam Ram Geeta
What I see instead is
SamRamRamRamRamRamRamRamRamRamRamR.....
Any hints on what I am doing wrong and how to achieve my goal?
package Person;
use Moose;
has name => ( is => 'ro' );
my #kids;
sub addChild {
my ( $self, $name ) = #_;
my $k = Person->new( name => $name );
push #kids, $k;
return $k;
}
sub printTree {
my $self = shift;
print $self->name;
$_->printTree foreach ( #kids );
}
no Moose;
package main;
my $s = Person->new( name => "Sam" );
my $r = $s->addChild( "Ram" );
my $g = $s->addChild( "Geeta" );
$s->printTree;
The issue is that #Person::kids does not belong to any one instance, and you effectively end up with
#Person::kids = ($r, $g);
$s->printTree() loops through #Person::kids, calls
$r->printTree() loops through #Person::kids, calls
$r->printTree() loops through #Person::kids, calls
$r->printTree() loops through #Person::kids, calls
...
You need to make it an attribute, e.g.
has kids => (
isa => 'ArrayRef[Person]',
traits => ['Array'],
handles => {
all_kids => 'elements',
push_kids => 'push',
},
default => sub { [] },
);
sub addChild {
my ($self, $name) = #_;
my $k = Person->new(name => $name);
$self->push_kids($k);
return $k;
}
sub printTree {
my ($self) = #_;
print $self->name;
$_->printTree foreach $self->all_kids;
}
You can check perldoc Moose::Meta::Attribute::Native::Trait::Array for other useful handles from the Array trait.

Why won't this die?

I've created a package that starts a simple HTTP server for testing purposes, but the stop() method doesn't seem to want to stop the fork()'ed process. Killing the process (via SIGHUP) works fine outside of the object, but calling $server->stop just doesn't work. Why?
package MockHub;
use Moose;
use HTTP::Server::Brick;
use JSON;
use Log::Any qw($log);
use English qw(-no_match_vars);
has 'server' => (
'is' => 'ro',
'lazy' => 1,
'isa' => 'HTTP::Server::Brick',
'builder' => '_build_server',
'init_arg' => undef
);
has 'port' => ( 'is' => 'ro', 'isa' => 'Int' );
has 'pid' => ( 'is' => 'rw', 'isa' => 'Int', 'init_arg' => undef );
has 'token' => ( 'is' => 'rw', 'isa' => 'Str', 'init_arg' => undef );
has 'log' => ( 'is' => 'ro', 'isa' => 'Log::Any::Proxy', 'default' => sub { Log::Any->get_logger() } );
sub start {
my $self = shift;
my $pid = fork;
# Spawn the server in a child process.
if (!defined $pid) {
die qq{Can't fork: $!};
}
elsif ($pid == 0) { # child
$self->server->start;
exit; # exit after server exits
}
else { # parent
$self->pid($pid);
return $pid;
}
}
sub _build_server {
my ($self) = #_;
my $port = $self->port;
my $pid = $self->pid || 'NO PID';
my $server = HTTP::Server::Brick->new( port => $port );
$server->mount(
'/foo' => {
'handler' => sub {
my ( $req, $res ) = #_;
my $token = substr( $req->{'path_info'}, 1 ); # remove leading slash
$self->token($token);
$res->header( 'Content-Type' => 'application/json' );
$res->add_content( encode_json( { 'success' => 1, 'message' => 'Process Report Received' } ) );
1;
},
'wildcard' => 1,
},
);
$server->mount(
'/token' => {
'handler' => sub {
my ( $req, $res ) = #_;
my $token = $self->token || '';
$res->header( 'Content-Type' => 'text/plain' );
$res->add_content($token);
1;
},
},
);
return $server;
}
sub stop {
my ($self) = #_;
my $pid = $self->pid || die q{No PID};
if (kill 0, $pid) {
sleep 1;
kill 'HUP', $pid;
if (kill 0, $pid) {
warn q{Server will not die!};
}
}
else {
warn q{Server not running};
}
}
__PACKAGE__->meta->make_immutable;
Although it's not running, the process still exists until its parent reaps it reaped by wait(2). Since the child is never reaped (and since there's no permission issue), kill 0, $pid will always succeed. Fixed:
sub stop {
my ($self) = #_;
my $pid = $self->pid
or die("No child to stop.\n");
kill(TERM => $pid);
or die("Can't kill child.\n");
if (!eval {{
local $SIG{ALRM} = sub { die "timeout\n" };
alarm(15);
waitpid($pid, 0) > 0
or die("Can't reap child.\n");
return 1; # No exception
}}) {
die($#) if $# ne "timeout\n";
warn("Forcing child to end.\n");
kill(KILL => $pid)
or die("Can't kill child.\n");
waitpid($pid, 0) > 0
or die("Can't reap child.\n");
}
$self->pid(0);
}
Your stop subroutine does not appear to stop anything. It sends kill 0 (which merely detects when the process is running), or a HUP. Don't you want to send SIGTERM or similar?
Also, what are you trying to achieve with $self->$pid? When you do the fork(), then the memory space of parent and child are separated, so what you write into $self->pid in the parent will not be visible to the child. You thus need to record the PID of the child in the child, e.g.
$self->pid = $$;
before
$self->server->start;
I'm a little unsure exactly which process you are trying to kill here, and which process is calling stop(). I'm presuming these aren't exactly the same or you'd surely just have quit from there rather than muck around with kill etc.

How to auto generate a bunch of setters / getters tied to a network service in Moose?

By way of teaching myself Moose, I'm working on a Moose object that interfaces to a particular piece of hardware. Said hardware takes a number of different commands that set various properties of the hardware, all of the form PROPERTYNAME=VALUE for a setter, and PROPERTYNAME? for a getter (note that these 'setters' and 'getters' are on the network interface to the hardware). What I want to do is create an object where all of these properties of the hardware are implemented with an attribute-like interface. Since getting and setting the various properties takes the same form for all properties, is there a way to automatically generate the setters and getters from a list of those properties?
I.E.: Rather than this:
Package MyHardware;
use Moose;
has property1 => (
'is' => 'rw',
'reader' => 'set_property1',
'writer' => 'get_property1',
);
has property2 => (
'is' => 'rw',
'reader' => 'set_property2',
'writer' => 'get_property2',
);
# ...
has propertyN => (
'is' => 'rw',
'reader' => 'set_propertyN',
'writer' => 'get_propertyN',
);
Is there something I can do like this:
Package MyHardware;
use Moose;
attributes => (
'is' => 'rw',
'names' => [qw/property1 property2 ... propertyN/],
'reader' => sub {
my $self = shift;
my $property = shift;
return $self->_send_command("$property?");
},
'writer' => sub {
my $self = shift;
my $property = shift;
my $value = shift;
return $self->_send_command("$property=$value");
},
);
EDIT: Here's what I want to happen:
# CALLER:
my $hw = MyHardware->new();
$hw->property1('foo');
print $hw->property2 . "\n";
And "under the hood":
$hw->property1('foo');
# Becomes
sub { return $hw->_send_command('property1=foo'); }
# And
$hw->property2();
# Becomes
sub { return $hw->_send_command('property2?'); }
How about looping over the properties?
use strict;
use warnings;
use Moose;
foreach my $prop ( qw( property1 property2 property3 property4 ) ) {
has $prop => (
is => 'rw',
isa => 'Str',
reader => "get_$prop",
writer => "set_$prop",
);
}
1;
Figured it out. I realize that I shouldn't be using attributes at all to do this. Instead, I'll dynamically generate methods using Class::MOP::Class like so:
my $meta = Class::MOP::Class->initialize(__PACKAGE__);
foreach my $prop (qw/property1 property2 property3/) {
$meta->add_method(qq/set_$prop/, sub {
my $self = shift;
my $value = shift;
return $self->_send_command(qq/$prop=$value/);
}
);
$meta->add_method(qq/get_$prop/, sub {
my $self = shift;
return $self->_send_command(qq/$prop?/);
}
);
}
Doing it with calls to has() would have effectively put the object state in two places - on the hardware and in the instance - and I only want it in one.
You don't store any value, so you don't want attributes.
You don't don't even want two subs since you want a single name for both getting and setting.
for my $prop (qw( property1 property2 property3 )) {
my $accessor = sub {
my $self = shift;
if (#_) {
$self->_send_command("$prop=$value");
} else {
return $self->_send_command("$prop?");
}
};
no strict 'refs';
*$prop = $accessor;
}
I would recommend using a has rather than an individual attribute for each of your properties.
Package MyHardware;
use Moose;
has properties => (
'is' => 'rw',
'isa' => 'HashRef',
'lazy_build' => 1,
);
sub _build_properties {
my $self = shift;
return {
'property1' => '',
'property2' => '',
};
}
print $self->properties->{property1};
Generate getters and setters for instance data
BEGIN
{
my #attr = qw(prop1 prop2 prop3 prop4);
no strict 'refs';
for my $a (#attr)
{
*{__PACKAGE__ . "::get_$a"} = sub { $_[0]->{$a} };
*{__PACKAGE__ . "::set_$a"} = sub { $_[0]->{$a} = $_[1] };
}
}

How can I easily generate a Perl function depending on name of the importing class?

I want to export a function which depends on name of class where is exported into. I thought that it should be easy with Sub::Exporter but unfortunately the into key is not passed to generators. I have ended up with those ugly example code:
use strict;
use warnings;
package MyLog;
use Log::Log4perl qw(:easy get_logger);
use Sub::Exporter -setup => {
exports => [
log => \&gen_log,
audit_log => \&gen_log,
],
groups => [ default => [qw(log audit_log)] ],
collectors => ['category'],
installer => \&installer, # tunnel `into` value into generators
};
if ( not Log::Log4perl->initialized() ) {
#easy init if not initialised
Log::Log4perl->easy_init($ERROR);
}
sub gen_log {
my ( $class, $name, $arg, $global ) = #_;
my $category = $arg->{category};
$category = $global->{category}{$name} unless defined $category;
return sub { # return generator
my $into = shift; # class name passed by `installer`
$category = $name eq 'audit_log' ? "audit_log.$into" : $into
if !defined $category; # set default category
# lazy logger
my $logger;
return sub {
$logger or $logger = get_logger($category);
};
};
}
sub installer {
my ( $args, $todo ) = #_;
# each even value is still generator thus generate final function
my $i;
1 & $i++ and $_ = $_->( $args->{into} ) for #$todo;
Sub::Exporter::default_installer(#_);
}
1;
Is there better way how to do it without sacrifice all this rich Sub::Exporter abilities?
For example I would like to use one of those:
use MyLog category => { log => 'foo', audit_log => 'bar' };
use MyLog -default => { -prefix => 'my_' };
use MyLog
audit_log => { -as => 'audit' },
log => { -as => 'my_log', category => 'my.log' };
Edit: Added Sub::Exporter abilities requirement to question.
Edit2: Added usage examples.
You aren't clear how you want to determine the name. If I understand you correctly, this does what you want.
my %sub_for = (
foo => \&foo,
#...
);
sub install_as {
my ($package, $exported_name, $sub) = #_;
no strict 'refs';
*{"$package\::$exported_name"} = $sub;
return;
}
sub get_name_for {
my ($package, $name) = #_;
#... your code here
}
sub import {
my $class = shift;
my $package = caller;
for my $internal_name (#_) {
install_as($package, get_name_for($package, $internal_name), $get_sub_for{$name});
}
return;
}