4

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?

tjwrona1992
  • 8,614
  • 8
  • 35
  • 98
  • If your AUTOLOAD sub creates the symbol being autoloaded (so that AUTOLOAD won't be called for that symbol again), it's simply a question of calling `after` (which is just a normal subroutine) after creatng the symbol. – ikegami Sep 11 '15 at 21:11
  • How do you do this after the symbol is created? If I just try and use `after` it says that the method 'diff' was not found in the inheritance hierarchy. – tjwrona1992 Sep 14 '15 at 13:20
  • Then you didn't create the symbol (`diff`), or you called `after` from the wrong package – ikegami Sep 14 '15 at 13:42
  • Ohh, so you mean define something like `*diff`? I haven't messed with symbols much. – tjwrona1992 Sep 14 '15 at 14:06
  • Yet you're writing an AUTOLOAD, whose purpose is to "load" symbols... – ikegami Sep 14 '15 at 14:38
  • Okay so I guess i'll go with the approach @mob suggested. It seems to work well and I can add special processing for any CVS command that I want easily. – tjwrona1992 Sep 14 '15 at 14:45
  • @mob's answer makes no sense. No need to wrap an AUTOLOAD you are writing yourself. You can place the code in AUTOLOAD itself. – ikegami Sep 14 '15 at 14:48
  • basically I want to use `AUTOLOAD` to generate all of the CVS commands, however with the `diff` command I want to add extra processing. I want `AUTOLOAD` to automatically send the 'diff' command to CVS, then I want to use `after` to parse the diff output and return it in a different format. I don't want any other CVS command to be treated this way though, which is why I need a way to single out `diff` and not all of the code can be a part of the `AUTOLOAD` routine itself. – tjwrona1992 Sep 14 '15 at 15:05
  • Actually, I guess you're right. Technically I could add the special processing directly inside the `AUTOLOAD` routine and have it return a different result for `diff`. That might be cleaner too. Thanks @ikegami. – tjwrona1992 Sep 14 '15 at 15:06

3 Answers3

3

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

tjwrona1992
  • 8,614
  • 8
  • 35
  • 98
mob
  • 117,087
  • 18
  • 149
  • 283
  • I really like this approach because it keeps any special processing for `AUTOLOAD`ed commands grouped to one place. Thanks @mob! – tjwrona1992 Sep 14 '15 at 13:07
0

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 { ... };
LeoNerd
  • 8,344
  • 1
  • 29
  • 36
0

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.

Mark
  • 1
  • 1
  • I will look into this, but the main reason I'm using `AUTOLOAD` is because there are a lot of CVS commands and I don't want to have to manually implement them all. If an attempt is made to call an invalid CVS command, it will pass the command to CVS and CVS will return an error saying it is an invalid command. – tjwrona1992 Sep 14 '15 at 12:40