Alan W. Smith
Alan W. Smith

Reputation: 25445

How can I use Perl's File::Find inside a Moose Object?

I'm building a Perl application that relies on Moose. One task the Moose object needs to accomplish is to use File::Find to populate an attribute with a list of files. I'm having some trouble figuring out how to use find's \&wanted code reference in a way that will let me maintain access to a $self version of the Moose object.

So far, I have this:

#!/usr/bin/perl

package MyMoose;

use Modern::Perl;
use Moose;
use File::Find;
use FindBin qw($Bin);

### Attribute to hold the file list
has "file_list" => (
    is => 'rw',
    isa => 'ArrayRef',
    default => sub {[]}
);


### Method to populate the file list
sub update_file_list {

    my $self = shift;

    find(\&wanted, $Bin);

}


### File::Find wanted sub
sub wanted {

    ### This won't work, but shows what I'd like to do
    #   my $self = shift;
    #   ### Do some filtering
    #   push @{$self->file_list}, $File::Find::name;

}


1;


######################################################################
### Main package to test the object.

package main;

use Data::Dumper;

run_main() unless caller();

sub run_main {

    my $m = MyMoose->new();

    $m->update_file_list();

    print Dumper $m->file_list;

}

It runs, but obviously doesn't assemble a file list. That's the part I'm trying to figure out.

What's the proper way to use File::Find so that it will let you have access to the Moose object during processing?

Upvotes: 3

Views: 444

Answers (3)

Alan W. Smith
Alan W. Smith

Reputation: 25445

After some trial an error, I also got the script to work by replacing the original version of 'update_file_list' with this:

sub update_file_list {

    my $self = shift;

    find( sub { wanted($self); }, $Bin );

}

That seems to work as well.

Upvotes: 0

Ilmari Karonen
Ilmari Karonen

Reputation: 50338

As bvr notes, the subroutine reference passed to find doesn't need to be a named package method — a lexical closure will work just fine. Thus, you can do this:

sub update_file_list {

    my $self = shift;

    my $wanted = sub {

        ### Do some filtering
        push @{$self->file_list}, $File::Find::name;

    };

    find($wanted, $Bin);    

}

The lexical variable $self declared in the outer function scope will be visible in the inner function.

In particular, every time the update_file_list method is called, a new $self and a new $wanted will be created (and bound together by the inner reference to $self), so that it's perfectly safe to call the method several times on different objects, even recursively if you want.

Upvotes: 0

bvr
bvr

Reputation: 9697

The problem is that you don't have access to $self within wanted sub. You can use inline closure and default or builder to build the list.

Edit: updated code per updated question

has "file_list" => (
    is => 'rw',
    isa => 'ArrayRef',
    default => sub {
        my $self = shift;
        return $self->_get_file_list();
    },
);

sub update_file_list {
    my $self = shift;
    $self->file_list($self->_get_file_list());
}

sub _get_file_list {
    my @files;
    find(sub { push @files, $File::Find::name }, $Bin);
    return \@files;
}

_get_file_list method returns arrayref of files found. It is used both in default and update_file_list method to populate the attribute.

Upvotes: 2

Related Questions