Method Extraction in Vim
I'm hacking on code with some methods which are fairly long (inlined code for performance), but sometimes I have to extract some code out into its own method. Padre uses Devel::Refactor for this, but I didn't want to go down that road as it doesn't use PPI. Thus, I hacked my own using PPIx::EditorTools. It's not great, but long-term, I think it's a more robust solution.
In vim, I have the following binding for Perl:
vnoremap <leader>sub :!~/bin/extract_sub <cr>
Thus, when I select code in vim with shift-v, I can type ",sub" and that passes the select code via STDIN to this filter:
#!/usr/bin/env perl
use strict;
use warnings;
use PPI;
use PPIx::EditorTools 0.11;
my $code = do { local $/; <STDIN> };
chomp($code);
my $ppi = PPI::Document->new( \$code );
my %is_found =
map { $_ => 1 }
map { keys %$_ }
values %{ PPIx::EditorTools::get_all_variable_declarations($ppi) };
my $vars = $ppi->find(
sub {
my ( $self, $thingy ) = @_;
no warnings 'uninitialized';
return $thingy->isa('PPI::Token::Symbol')
&& not $is_found{ $thingy->content }++;
}
);
my $subname = $ENV{SUBNAME} || 'XXX';
my $unroll_at_underscore = '';
if ($vars) {
$unroll_at_underscore =
" my ( " . ( join ', ' => map { $_->content } @$vars ) . " ) = \@_;\n";
}
print <<"END";
sub $subname {
$unroll_at_underscore
$code
}
END
And the "print" at the end writes the code back to the selected area, expanding it as needed. So let's say we have the following method:
sub process_doc {
my ( $self, %args ) = @_;
$self->ppi( $args{ppi} ) if defined $args{ppi};
return 1 if $self->ppi && $self->ppi->isa('PPI::Document');
# TODO: inefficient to pass around full code/ppi
$self->code( $args{code} ) if $args{code};
my $code = $self->code;
$self->ppi( PPI::Document->new( \$code ) );
return 1 if $self->ppi && $self->ppi->isa('PPI::Document');
croak "arguments ppi or code required";
return;
}
If you want the "# TODO" line and the following four lines extracted into a method, you select them and run the filter. That results in the following:
sub process_doc {
my ( $self, %args ) = @_;
$self->ppi( $args{ppi} ) if defined $args{ppi};
return 1 if $self->ppi && $self->ppi->isa('PPI::Document');
sub XXX {
my ( $self, $args ) = @_;
# TODO: inefficient to pass around full code/ppi
$self->code( $args{code} ) if $args{code};
my $code = $self->code;
$self->ppi( PPI::Document->new( \$code ) );
return 1 if $self->ppi && $self->ppi->isa('PPI::Document');
}
croak "arguments ppi or code required";
return;
}
That's not legal Perl and it seems trivial, but when you've selected a hundred lines of code, it's very handy. It clearly needs a lot more work, but it's already saving me a lot of time and frustration.
Cute.
Did you ever take a look at App::EditorTools and/or PPIx::EditorTools?
@confuseAcat: you've confusedAovid. I based that on PPIx::EditorTools :)
Oh. Indeed. As always, reading the source helps quite a bit.
I saw a script somewhere once that did this without PPI, and more correctly. It just piped the code to
perl -c -Mstrict=vars
and parsed the stricture failure error messages fromperl
to figure out which variables need to be declared.I've been using a script by Jesse Vincent, ever since Piers Cawley mentioned it.
@Aristotle, I see you commented on that post. Is this the script you mean?
Yes! Good grief, I scoured heaven and hell to find it and failed. Thank you!