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

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;
}

Related

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.

Automatically generate moose attribute wrapper methods

Is is possible to supply an accessor wrapper for a moose attribute without having to write it every time?
Example:
* There is an an attribute of type TkRef
* It should provide a wrapper for setting the value
* The name of the wrapper should be defined when defining the attribute
* I don't want to have to write the wrapper
I imagine it like this:
has _some_val => (
is => 'rw',
isa => 'TkRef',
coerce => 1,
init_arg => 'my_accessor_wrapper_name',
default => 'default value'
);
# Later in the class:
sub some_public_method {
my $self = shift;
# will set _some_val behind the scenes:
$self->my_accessor_wrapper_name('this will be the new value');
...
}
I'm assuming here that this follows on from your previous question so the aim is to wrap a ScalarRef attribute's accessors to ensure that when the setter is called with a new ScalarRef (or something that can be coerced into a ScalarRef), rather that the usual set action happening, you copy the string stored in the new scalar into the old scalar.
There are easier ways to do this than below (say, by writing a wrapper for has), but I think this is the "most antlered":
use 5.010;
use strict;
use warnings;
{
package MooseX::Traits::SetScalarByRef;
use Moose::Role;
use Moose::Util::TypeConstraints qw(find_type_constraint);
# Supply a default for "is"
around _process_is_option => sub
{
my $next = shift;
my $self = shift;
my ($name, $options) = #_;
if (not exists $options->{is})
{
$options->{is} = "rw";
}
$self->$next(#_);
};
# Supply a default for "isa"
my $default_type;
around _process_isa_option => sub
{
my $next = shift;
my $self = shift;
my ($name, $options) = #_;
if (not exists $options->{isa})
{
if (not defined $default_type)
{
$default_type = find_type_constraint('ScalarRef')
->create_child_constraint;
$default_type
->coercion('Moose::Meta::TypeCoercion'->new)
->add_type_coercions('Value', sub { my $r = $_; \$r });
}
$options->{isa} = $default_type;
}
$self->$next(#_);
};
# Automatically coerce
around _process_coerce_option => sub
{
my $next = shift;
my $self = shift;
my ($name, $options) = #_;
if (defined $options->{type_constraint}
and $options->{type_constraint}->has_coercion
and not exists $options->{coerce})
{
$options->{coerce} = 1;
}
$self->$next(#_);
};
# This allows handles => 1
around _canonicalize_handles => sub
{
my $next = shift;
my $self = shift;
my $handles = $self->handles;
if (!ref($handles) and $handles eq '1')
{
return ($self->init_arg, 'set_by_ref');
}
$self->$next(#_);
};
# Actually install the wrapper
around install_delegation => sub
{
my $next = shift;
my $self = shift;
my %handles = $self->_canonicalize_handles;
for my $key (sort keys %handles)
{
$handles{$key} eq 'set_by_ref' or next;
delete $handles{$key};
$self->associated_class->add_method($key, $self->_make_set_by_ref($key));
}
# When we call $next, we're going to temporarily
# replace $self->handles, so that $next cannot see
# the set_by_ref bits which were there.
my $orig = $self->handles;
$self->_set_handles(\%handles);
$self->$next(#_);
$self->_set_handles($orig); # and restore!
};
# This generates the coderef for the method that we're
# going to install
sub _make_set_by_ref
{
my $self = shift;
my ($method_name) = #_;
my $reader = $self->get_read_method;
my $type = $self->type_constraint;
my $coerce = $self->should_coerce;
return sub {
my $obj = shift;
if (#_)
{
my $new_ref = $coerce
? $type->assert_coerce(#_)
: do { $type->assert_valid(#_); $_[0] };
${$obj->$reader} = $$new_ref;
}
$obj->$reader;
};
}
}
{
package Local::Example;
use Moose;
use Moose::Util::TypeConstraints;
subtype 'TkRef', as 'ScalarRef';
coerce 'TkRef', from 'Str', via { my $r = $_; return \$r };
has _some_val => (
traits => [ 'MooseX::Traits::SetScalarByRef' ],
isa => 'TkRef',
init_arg => 'some_val',
default => 'default value',
handles => 1,
);
}
use Scalar::Util qw(refaddr);
my $eg = Local::Example->new;
say refaddr($eg->some_val);
$eg->some_val("new string");
say refaddr($eg->some_val), " - should not have changed";
say ${ $eg->some_val };

Moose trigger caller

Is there any way of knowing the trigger caller attribute in Moose ?
For example, taking the example from Moose::Manual::Attributes:
has 'size' => (
is => 'rw',
trigger => \&_size_set,
);
sub _size_set {
my ( $self, $size, $old_size ) = #_;
my $msg = $self->name;
if ( #_ > 2 ) {
$msg .= " - old size was $old_size";
}
$msg .= " - size is now $size";
warn $msg;
}
Is it possible in _set_size to know that the attribute size called it, without needing to specify the name of the caller attribute explicitly?
EDIT: updated per comment.
It might be simpler to create a wrapper that adds one argument:
sub make_trigger {
my ($name, $sub) = #_;
return sub {
my $self = shift;
$self->$sub($name, #_);
};
}
has 'size' => (
is => 'rw',
trigger => make_trigger(size => \&_size_set),
);
sub _size_set {
my ( $self, $name, $size, $old_size ) = #_;
...
}
Here's what #RsrchBoy refers to as the "proper way"...
use v5.14;
use strict;
use warnings;
BEGIN {
package MooseX::WhatTheTrig::Trait::Attribute
{
use Moose::Role;
use Scope::Guard qw(guard);
after _process_trigger_option => sub
{
my $class = shift;
my ($name, $opts) = #_;
return unless exists $opts->{trigger};
my $orig = delete $opts->{trigger};
$opts->{trigger} = sub
{
my $self = shift;
my $guard = guard {
$self->meta->_set_triggered_attribute(undef);
};
$self->meta->_set_triggered_attribute($name);
$self->$orig(#_);
};
}
}
package MooseX::WhatTheTrig::Trait::Class
{
use Moose::Role;
has triggered_attribute => (
is => 'ro',
writer => '_set_triggered_attribute',
);
}
}
package Example
{
use Moose -traits => ['MooseX::WhatTheTrig::Trait::Class'];
has [qw(foo bar)] => (
traits => ['MooseX::WhatTheTrig::Trait::Attribute'],
is => 'rw',
trigger => sub {
my ($self, $new, $old) = #_;
$_ //= 'undef' for $old, $new;
my $attr = $self->meta->triggered_attribute;
say "Changed $attr for $self from $old to $new!";
}
);
}
my $obj = Example->new(foo => 1, bar => 2);
$obj->foo(3);
$obj->bar(4);
You'll notice that the "foo" and "bar" attributes share a trigger, but that the trigger is able to differentiate between the two attributes.
Moose::Exporter has some sugar for making this a little less ugly. I might have a play at turning this into a CPAN module some time.
The proper way to do this would be to employ an attribute trait of some sort; one that passes the name, or (preferably) the metaclass instance of the attribute the trigger belongs to. One could even create a trait that allows the class' metaclass to be asked if we're in an attribute trigger, and if so, which one. (This would be transparent and not break anyone's expectations as to how trigger works.)
The easiest would be to curry your triggers as shown in another example.

How to use Perl Moose with Plugins for a Main Object?

I'm a newbie in Moose. I have to create an object that should load several plugins. The structure is like this:
Main Object -> some general Functions
Plugins -> extensions for the Main Object
The plugins are in a separate Folder on the server. The Main Object has to load the plugins, initialize them and store the object in itself. Each plugin's return value has to go through the Main Object. Because the Main Object should transform every return value in a JSON structure for the caller.
I would call something like this:
my $main_obj = Main->new();
$main_obj->plugin('MainExtention')->get_title();
Here is my example code:
Main Object:
package Main;
use Moose;
has 'plugins' => (
is => 'rw',
);
has 'title' => (
is => 'rw',
isa => 'Str',
reader => '_get_title',
);
# load plugins
sub _load_modules {
my $self = shift;
my $path = "/usr/local/apache/sites/.../Plugins";
push(#INC, $path);
my #modules = _find_modules_to_load($path);
eval {
my $plugins = {};
foreach my $module ( sort #modules) {
(my $file = $module) =~ s|::|/|g;
(my $modname = $module) =~ s/.*?(\w+$)/$1/;
require $file . '.pm';
my $obj = $module->new();
$plugins->{$modname} = $obj;
1;
}
$self->plugins($plugins);
} or do {
my $error = $#;
warn "Error loading modules: $error" if $error;
};
}
# read plugins
sub _find_modules_to_load {
my ($dir) = #_;
my #files = glob("$dir/*.pm");
my $namespace = $dir;
$namespace =~ s/\//::/g;
# Get the leaf name and add the System::Module namespace to it
my #modules = map { s/.*\/(.*).pm//g; "${namespace}::$1"; } #files;
return #modules;
}
sub BUILD {
my $self = shift;
$self->_load_modules();
}
sub get_title {
return 'main title'
}
1;
__PACKAGE__->meta->make_immutable;
Plugin MainExtention in directory "/usr/local/apache/sites/.../Plugins":
package MainExtention;
use Moose;
has 'title' => (
is => 'rw',
isa => 'Str',
default => 'Default',
);
sub get_title {
return 'MainExtention Title';
}
1;
This works like this:
my $main = Main->new();
my $plugins = $main->plugins();
print $plugins->{'MainExtention'}->get_title();
But this is not what I will have :) I will get the return of the plugin not directly from the plugin but from the Main Object. Does anyone have an idea?
Second question: is there a simpler way to get the plugins loaded? How?
To load plugins, I'd recommend using Module::Pluggable, which can load a bunch of packages from a directory and instantiate them (or not) as you need.
If you need to have the main object wrap the plugin, just define a method on the main object to do whatever you need:
package Main;
use Moose;
# Adds a plugins() method to Main that returns a list of all loaded plugin packages
use Module::Pluggable search_dirs => [ "/usr/local/apache/sites/.../Plugins" ];
# Used to store the plugins after ->new is called on each package
has loaded_plugins => (
is => 'rw',
isa => 'HashRef[Object]',
lazy_build => 1,
traits => [ 'Hash' ],
handles => { _plugin => 'get' },
);
# Constructor for loaded_plugins, implied by lazy_build => 1
sub _build_loaded_plugins {
my ($self) = #_;
my %loaded_plugins;
for my $plugin ($self->plugins) {
$loaded_plugins{$plugin} = $plugin->new;
}
return \%loaded_plugins;
}
# Method for getting the processed title from any plugin
sub get_plugin_title {
my ($self, $name) = #_;
my $plugin = $self->_plugin($name);
my $title = $plugin->get_title;
# process the title according to whatever Main needs to do...
...
return $title;
}
Then, your code:
my $main = Main->new();
print $main->get_plugin_title('MainExtension');
If you're using Moose, I might also suggest making all of your plugins implement a role, which can help you discover if the plugin has been implemented properly.

How can I construct a moose object from a hash generated from one of the attributes?

I have a couple of packages:
package FOO;
use Moose;
has 'obj' => (is=>'ro');
sub hash {
my $self = shift;
return $self->make_hash($self->obj};
}
and another package extending FOO:
package FOOBAR;
use Moose;
extends 'FOO';
has [qw/val1 val2/] => (is => 'rw');
sub BUILD {
my ($self) = #_;
$self->val1($self->hash->{val1});
$self->val2($self->hash->{val2});
}
Basically I want to do FOOBAR->new(obj=>$obj); and use a hash generated from $obj to populate the attributes specified in FOOBAR (~20 or so attributes)
Is using 'BUILD' like this a good way of solving it?
Why? Then you end up with two copy of the data. Delegate instead.
has obj => (
is => 'ro',
handles => {
val1 => sub { my $self = shift; my $obj = $self->obj; ... },
val2 => sub { my $self = shift; my $obj = $self->obj; ... },
},
);
If the accessors are practically identical, you can do something like
sub make_obj_accessor {
my ($name) = #_;
return sub {
my $self = shift;
my $obj = $self->obj;
... $name ...
};
}
has obj => (
is => 'ro',
handles => {
(map make_obj_accessor($_), qw(
val1
val2
))
},
);
Of course, if you really only have a hash, all you need is
FOOBAR->new( %hash )