Projekt

Allgemein

Profil

Herunterladen (4,92 KB) Statistiken
| Zweig: | Markierung: | Revision:
package SL::Helper::Object;

use strict;

sub import {
my ($class, @args) = @_;

my $caller = (caller)[0];

while (@args > 1) {
my $method = shift @args;
my $args = shift @args;
die "invalid method '$method' for $class" unless $class->can($method);
$class->$method($caller, $args);
}
}

my %args_string_by_key = (
none => '',
raw => '(@_)',
standard => '(@_[1..$#_])',
);

my %pre_context_by_key = (
void => '',
scalar => 'my $return =',
list => 'my @return =',
);

my %post_context_by_key = (
void => 'return',
scalar => '$return',
list => '@return',
);

my %known_delegate_args = map { $_ => 1 } qw(target_method args force_context class_function);

my $_ident = '^[a-zA-Z0-9_]+$';
my $_cident = '^[a-zA-Z0-9_:]+$';

sub delegate {
my ($class, $caller, $args) = @_;

die 'delegate needs an array ref of parameters' if 'ARRAY' ne ref $args;
die 'delegate needs an even number of args' if @$args % 2;

while (@$args > 1) {
my $target = shift @$args;
my $delegate_args = shift @$args;
my $params = 'HASH' eq ref $delegate_args->[0] ? $delegate_args->[0] : {};

$known_delegate_args{$_} || die "unknown parameter '$_'" for keys %$params;

die "delegate: target '$target' must match /$_cident/" if $target !~ /$_cident/;
die "delegate: target_method '$params->{target_method}' must match /$_ident/" if $params->{target_method} && $params->{target_method} !~ /$_ident/;

my $method_joiner = $params->{class_function} ? '::' : '->';

for my $method (@$delegate_args) {
next if ref $method;

die "delegate: method name '$method' must match /$_ident/" if $method !~ /$_ident/;

my $target_method = $params->{target_method} // $method;

my ($pre_context, $post_context) = ('', '');
if (exists $params->{force_context}) {
$pre_context = $pre_context_by_key { $params->{force_context} };
$post_context = $post_context_by_key{ $params->{force_context} };
die "invalid context '$params->{force_context}' to force" unless defined $pre_context && defined $post_context;
}

my $target_code = ucfirst($target) eq $target ? $target : "\$_[0]->$target";

my $args_string = $args_string_by_key{ $params->{args} // 'standard' };
die "invalid args handling '$params->{args}'" unless defined $target_code;

eval "
sub ${caller}::$method {
$pre_context $target_code$method_joiner$target_method$args_string; $post_context
}
1;
" or die "could not create ${caller}::$method: $@";
}
}
}



1;

__END__

=encoding utf-8

=head1 NAME

SL::Helper::Object - Meta Object Helper Mixin

=head1 SYNOPSIS

use SL::Helper::Object (
delegate => [
$target => [ qw(method1 method2 method3 ...) ],
$target => [ { DELEGATE_OPTIONS }, qw(method1 method2 method3 ...) ],
...
],
);

=head1 DESCRIPTION

Sick of writing getter, setter? No because Rose::Object::MakeMethods has you covered.

Sick of writing all the rest that Rose can't do? Put it here. Functionality in this
mixin is passed as an include parameter, but are still described as functions:

=head1 FUNCTIONS

=over 4

=item C<delegate PARAMS>

Creates a method that delegates to the target. If the target string starts with
a lower case character, the generated code will be called on an object found
within the calling object by calling an accessor. This way, it is possible to
delegate to an object:

delegate => [
backend_obj => [ qw(save) ],
],

will generate:

sub save {
$_[0]->backend_obj->save
}

If it starts with an upper case letter, it is assumed that it is a class name:

delegate => [
'Backend' => [ qw(save) ],
],

will generate:

sub save {
Backend->save
}

Possible delegate args are:

=over 4

=item * C<target_method>

Optional. If not given, the generated method will dispatch to the same method
in the target class. If this is not possible, this can be used to overwrite it.

=item * C<args>

Controls how the arguments are passed.

If set to C<none>, the generated code will not bother passing args. This has the benefit
of not needing to splice the caller class out of @_, or to touch @_ at all for that matter.

If set to C<raw>, the generated code will pass @_ without changes. This will
result in the calling class or object being left in the arg, but is fine if the
delegator is called as a function.

If set to C<standard> (which is also the default), the original caller will be
spliced out and replaced with the new calling context.

=item * C<force_context>

Forces the given context on the delegated method. Valid arguments can be
C<void>, C<scalar>, C<list>. Default behaviour simply puts the call at the end
of the sub so that context is propagated.

=item * C<class_function>

If true, the function will be called as a class function instead of a method call.

=back

=back

=head1 BUGS

None yet :)

=head1 AUTHOR

Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>

=cut
(13-13/20)