How can I apply a method modifier to a method generated by AUTOLOAD?

152 Views Asked by At

I have a very interesting predicament. I am working on a Perl script interface to the CVS repository and have created Perl Objects to represent Modules,Paths, and Files. Since Modules, Paths, and Files can all have CVS commands issued on them, I set up the AUTOLOAD routine to take any unidentified methods and issue them on the object as if they were CVS commands.

All of these CVS commands are executed exactly the same way, but some of them need special processing done with the output to get the result i desire.


For example, I want to take the output from the diff command and reformat it before I return it.

I am using Moose, so typically this special processing could be done as follows:

after 'diff' => sub {
    # Reformat output here
}

The problem is... I never explicitly created the diff method since it is being generated by AUTOLOAD and Perl won't let me create a method modifier for it since it technically doesn't exist!

Is there a way to get this to work how I want?

3

There are 3 best solutions below

1
On BEST ANSWER

Apply after to your AUTOLOAD method.

after 'AUTOLOAD' => sub {
    my $method = $The::Package::AUTOLOAD;
    $method =~ s/.*:://;
    if ($method eq 'diff') {
        # do  after diff  stuff
    } elsif ($method eq 'foo') {
        # do  after foo  stuff
    } else {
        # never mind, don't want to do anything after this function
    }
};

EDIT:

I found that I may want even more control over the diff command so I have added more detail to your answer. Hopefully someone will find this information useful.

For even more control you can use around!

around 'AUTOLOAD' => sub {
    my $orig = shift;
    my $self = shift;
    (my $command = $AUTOLOAD) =~ s{.+::}{};

    # Special processing
    if ($command eq 'diff') {

        #
        # Add "before" special processing here
        #

        my $output = $self->$orig(@_);

        #
        # Add "after" special processing here
        #

    }
    else {
        return $self->$orig(@_);
    }
};

This allows you to do special processing before the function is called AND after.

For more information see: Moose::Manual::MethodModifiers

1
On

Depending on how well the AUTOLOAD-using class is implemented, you may find that it respects the can method too, and that simply calling can is enough to create the method.

__PACKAGE__->can( "diff" );
after diff => sub { ... };
1
On

I'd suggest that you re-architect your system to use traits, instead of relying on AUTOLOAD behavior. The maintainability and intent will be much more obvious, if you don't have behavior scattered all over the place.

As an example, you can do what you want with something like the following:

package Trait::CVSActions;

use Moose::Role;

sub commit { print 'in commit for ' . shift . "\n" }

sub diff { print 'diffing for ' . shift . "\n" }

package Module;

use Moose;

with 'Trait::CVSActions';

package Path;

use Moose;

with 'Trait::CVSActions';

after commit => sub { print "after commit on Path\n" };

package main;

my $module = new Module;
my $path = new Path;

$module->commit;
$path->commit;

If you're looking to use AUTOLOAD to dispatch to unknown commands, then this is dangerous, since there may be some that you will have to have special handling for that you aren't aware of, so you may be causing yourself future problems.