File Coverage

blib/lib/Devel/IntelliPerl.pm
Criterion Covered Total %
statement 136 158 86.0
branch 33 56 58.9
condition 10 21 47.6
subroutine 25 28 89.2
pod 12 13 92.3
total 216 276 78.2


line stmt bran cond sub pod time code
1             package Devel::IntelliPerl;
2             our $VERSION = '0.04';
3              
4 5     5   128849 use Moose;
  5         2202745  
  5         42  
5 5     5   42792 use Moose::Util::TypeConstraints;
  5         13  
  5         48  
6 5     5   15282 use PPI;
  5         791132  
  5         206  
7 5     5   60 use Class::MOP;
  5         12  
  5         120  
8 5     5   4234 use Path::Class;
  5         257904  
  5         427  
9 5     5   48 use List::Util qw(first);
  5         10  
  5         10430  
10              
11             my $KEYWORD = '[a-zA-Z_][_a-zA-Z0-9]*';
12             my $CLASS = '(' . $KEYWORD . ')(::' . $KEYWORD . ')*';
13             my $VAR = '\$' . $CLASS;
14              
15              
16             has line_number => ( isa => 'Int', is => 'rw', required => 1 );
17             has column => ( isa => 'Int', is => 'rw', required => 1 );
18             has filename => ( isa => 'Str', is => 'rw', trigger => \&update_inc );
19             has source => ( isa => 'Str', is => 'rw', required => 1 );
20             has inc => ( isa => 'ArrayRef[Str]', is => 'rw' );
21             has ppi => (
22             isa => 'PPI::Document',
23             is => 'rw',
24             lazy => 1,
25             builder => '_build_ppi',
26             clearer => 'clear_ppi'
27             );
28             has error => ( isa => 'Str', is => 'rw' );
29              
30             after source => sub {
31             my $self = shift;
32             $self->clear_ppi if (@_);
33             };
34              
35             after inc => sub {
36             my $self = shift;
37             unshift( @INC, @{ $_[0] } ) if ( $_[0] );
38             };
39              
40             sub update_inc {
41 0     0 1 0 my $self = shift;
42 0 0       0 return unless ( $self->filename );
43 0         0 my $parent = Path::Class::File->new( $self->filename );
44 0         0 my @libs;
45 0         0 while ( $parent = $parent->parent ) {
46 0 0       0 last if ( $parent eq $parent->parent );
47 0 0       0 last unless -d $parent;
48 0 0       0 push( @libs, $parent->subdir('lib')->stringify )
49             if ( -e $parent->subdir('lib') );
50             }
51 0         0 return $self->inc( \@libs );
52             }
53              
54             sub line {
55 51     51 1 704 my ( $self, $line ) = @_;
56 51         206 my @source = split( "\n", $self->source );
57 51 100       853 if ( defined $line ) {
58 9         329 $source[ $self->line_number - 1 ] = $line;
59 9         75 $self->source( join( "\n", @source ) );
60             }
61 51         12883 return $source[ $self->line_number - 1 ];
62             }
63              
64             sub inject_statement {
65 5     5 1 1049 my ( $self, $statement ) = @_;
66 5         25 my $line = $self->line;
67 5         200 my $prefix = substr( $line, 0, $self->column - 1 );
68 5         10 my $postfix;
69 5 100       19 $postfix = substr( $self->line, $self->column - 1 )
70             if ( length $self->line >= $self->column );
71 5   100     43 $self->line( $prefix . $statement . ( $postfix || '' ) );
72 5         13 return $self->line;
73             }
74              
75             sub _build_ppi {
76 6     6   28 return PPI::Document->new( \( shift->source ) );
77             }
78              
79             sub keyword {
80 10     10 1 23 my ($self) = @_;
81 10         37 my $line = substr( $self->line, 0, $self->column - 1 );
82 10 50       321 if ( $line =~ /.*?(\$?$CLASS(->($KEYWORD))*)->($KEYWORD)?$/ ) {
83 10   50     77 return $1 || '';
84             }
85             }
86              
87             sub prefix {
88 11     11 1 24 my ($self) = @_;
89 11         37 my $line = substr( $self->line, 0, $self->column - 1 );
90 11 50       253 if ( $line =~ /.*?(\$?$CLASS)->($KEYWORD)?$/ ) {
91 11   100     77 return $4 || '';
92             }
93             }
94              
95             sub handle_self {
96 4     4 1 9 my ( $self, $keyword ) = @_;
97 4         17 $self->inject_statement('; my $FINDME;');
98 4         155 my $doc = $self->ppi;
99 4         38 my $package = $doc->find_first('Statement::Package');
100              
101 4 50       1031 return unless ($package);
102              
103 4         28 my $class = $package->namespace;
104              
105 4         132 my $var = $doc->find('Statement::Variable');
106             my $statement = first {
107 9     9   68 first { $_ eq '$FINDME' } $_->variables;
  12         745  
108             }
109 4         16596 @{$var};
  4         29  
110              
111 4         46 $statement->sprevious_sibling->remove;
112 4         389 $statement->remove;
113 2     2 0 1051 eval "$doc";
  2     1   5  
  2     1   26  
  1     1   8058  
  1     1   3  
  1     1   145  
  1     0   584  
  1     0   2  
  1         12  
  1         2219  
  1         2  
  1         265  
  1         622  
  1         2  
  1         32  
  1         2165  
  1         2  
  1         240  
  4         378  
  0         0  
  0         0  
  0         0  
114 4 50       42 if ($@) {
115 0         0 $self->error($@);
116 0         0 return;
117             }
118 4         82 return $class;
119             }
120              
121             sub handle_variable {
122 9     9 1 16 my ( $self, $keyword ) = @_;
123 9         41 my @source = split( "\n", $self->source );
124 9         426 my @previous = reverse splice( @source, 0, $self->line_number - 1 );
125 9         15 my $class = undef;
126 9         16 foreach my $line (@previous) {
127 16 50       301 if ( $line =~ /\Q$keyword\E.*?($CLASS)->new/ ) {
    100          
    50          
128 0         0 $class = $1;
129 0         0 last;
130             }
131             elsif ( $line =~ /\Q$keyword\E.*?new ($CLASS)/ ) {
132 9         20 $class = $1;
133 9         17 last;
134             }
135             elsif ( $line =~ /#.*\Q$keyword\E isa ($CLASS)/ ) {
136 0         0 $class = $1;
137 0         0 last;
138             }
139             }
140 9         38 return $class;
141             }
142              
143             sub handle_class {
144 17     17 1 26 my ( $self, $keyword ) = @_;
145 17         21 eval { Class::MOP::load_class($keyword); };
  17         54  
146 17 100       1849659 if ($@) {
147 1         7 $self->handle_self;
148             }
149 17         129 return $keyword;
150             }
151              
152             sub handle_method {
153 11     11 1 17 my ( $self, $keyword ) = @_;
154 11         118 $keyword =~ /^(\$?$CLASS)((->($KEYWORD))+)$/;
155 11         49 my ( undef, $method, @rest ) = split( /->/, $4 );
156 11         16 my $class;
157 11         29 my $pclass = $self->guess_class($1);
158 11         26 $self->handle_class($pclass);
159 11         39 my $meta = Class::MOP::Class->initialize($pclass);
160 11 100 33     137 if ( $meta->has_attribute($method) ) {
    50 33        
161 10         166 my $type_constraint = $meta->get_attribute($method)->type_constraint;
162 10         374 my $class_tc;
163 10   33     11 do {
164 10 50       65 $class_tc = $type_constraint
165             if ( $type_constraint->isa('Moose::Meta::TypeConstraint::Class') );
166             } while ( !$class_tc
167             && ( $type_constraint = $type_constraint->parent ) );
168              
169 10 50       86 $class = $class_tc->class if ($class_tc);
170             }
171             elsif ($meta->has_method($method)
172             && ( my $method_meta = $meta->get_method($method) )
173             && $meta->get_method($method)
174             ->isa('MooseX::Method::Signatures::Meta::Method') )
175             {
176              
177 1         122 my $constraints =
178             $method_meta->_return_type_constraint->type_constraints;
179 1         677 my $class_tc;
180 1         5 foreach my $type_constraint (@$constraints) {
181 1   33     2 do {
182 1 50       7 $class_tc = $type_constraint
183             if (
184             $type_constraint->isa('Moose::Meta::TypeConstraint::Class')
185             );
186 1 50       15 push( @$constraints, @{ $type_constraint->type_constraints } )
  0         0  
187             if ( $type_constraint->can('type_constraints') );
188             } while ( !$class_tc
189             && ( $type_constraint = $type_constraint->parent ) );
190 1 50       9 last if $class_tc;
191             }
192 1 50       8 $class = $class_tc->class if ($class_tc);
193              
194             }
195             return @rest
196 11 100       563 ? $self->guess_class( $class . '->' . join( '->', @rest ) )
197             : $class;
198             }
199              
200             sub trimmed_methods {
201 1     1 1 4 my ($self) = @_;
202 1         3 my $prefix = $self->prefix;
203 1         4 return map { substr( $_, length $prefix ) } $self->methods;
  1         11  
204             }
205              
206             sub guess_class {
207 29     29 1 57 my ( $self, $keyword ) = @_;
208 29 100       405 if ( $keyword =~ /^\$self$/ ) {
    100          
    100          
209 3         16 return $self->handle_self($keyword);
210             }
211             elsif ( $keyword =~ /^$VAR$/ ) {
212 9         36 return $self->handle_variable($keyword);
213             }
214             elsif ( $keyword =~ /^(\$?$CLASS)(->($KEYWORD))+$/ ) {
215 11         41 return $self->handle_method($keyword);
216             }
217             else {
218 6         30 return $self->handle_class($keyword);
219             }
220 0         0 return;
221             }
222              
223             sub methods {
224 8     8 1 723 my ($self) = @_;
225 8         36 my $keyword = $self->keyword;
226              
227 8         40 my $class = $self->guess_class($keyword);
228              
229 8 50 33     427 return unless ( $class && $class =~ /^$CLASS$/ );
230              
231 8         20 eval { Class::MOP::load_class($class); };
  8         35  
232 8 50       24055 if ($@) {
233 0         0 $self->error($@);
234 0         0 return;
235             }
236              
237 8         37 my $prefix = $self->prefix;
238              
239 8         46 my $meta = Class::MOP::Class->initialize($class);
240              
241 259         388 my @methods =
242 595         2315 sort { $a =~ /^_/ cmp $b =~ /^_/ }
243 677         13689 sort { $a =~ /^[A-Z][A-Z]$KEYWORD/ cmp $b =~ /^[A-Z][A-Z]$KEYWORD/ }
244 8         637 sort { lc($a) cmp lc($b) } $meta->get_all_method_names;
245              
246 8         42 return grep { $_ =~ /^$prefix/ } @methods;
  193         592  
247             }
248              
249             __PACKAGE__->meta->make_immutable;
250              
251             1;
252              
253             __END__
254              
255             =head1 NAME
256              
257             Devel::IntelliPerl - Auto-completion for Perl
258              
259              
260             =head1 VERSION
261              
262             version 0.04
263              
264             =head1 SYNOPSIS
265              
266             use Devel::IntelliPerl;
267              
268             my $source = <<'SOURCE';
269             package Foo;
270              
271             use Moose;
272              
273             has foobar => ( isa => 'Str', is => 'rw' );
274             has foo => ( isa => 'Foo', is => 'rw' );
275              
276             sub bar {
277             my ($self, $c) = @_;
278             # $c isa Catalyst
279             $self->
280             }
281              
282             1;
283             SOURCE
284              
285              
286             my $ip = Devel::IntelliPerl->new(source => $source, line_number => 10, column => 12);
287            
288             my @methods = $ip->methods;
289            
290             C<@methods> contains C<bar>, C<foo>, and C<foobar> amongst others.
291             Method completion for C<$c> works as well. Using the comment C<# $c isa Catalyst> you can specify
292             the variable C<$c> as an object of the C<Catalyst> class. This comment can be located anywhere in
293             the current file.
294              
295             Even though the example uses Moose, this module works also with non Moose classes.
296              
297             See L</SCREENCASTS> for usage examples.
298            
299             =head1 ATTRIBUTES
300              
301             =head2 line_number (Int $line_number)
302              
303             B<Required>
304              
305             Line number of the cursor. Starts at C<1>.
306              
307             =head2 column (Int $column)
308              
309             B<Required>
310              
311             Position of the cursor. Starts at C<1>.
312              
313             =head2 source (Str $source)
314              
315             B<Required>
316              
317             Source code.
318              
319             =head2 filename
320              
321             B<Optional>
322              
323             Store the filename of the current file. If this value is set C<@INC> is extended by all C<lib> directories
324             found in any parent directory. This is useful if you want to have access to modules which are not in C<@INC> but in
325             your local C<lib> folder. This method sets L</inc>.
326              
327             B<This value is NOT used to retrive the source code!> Use L<source|/source (Str $source)> instead.
328              
329             =head2 inc (ArrayRef[Str] $inc)
330              
331             B<Optional>
332              
333             All directories specified will be prepended to C<@INC>.
334              
335             =head1 METHODS
336              
337             =head2 error (Str $error)
338              
339             If an error occurs it is accessible via this method.
340              
341             =head2 line (Str $line)
342              
343             Sets or gets the current line.
344              
345             =head2 keyword
346              
347             This represents the current keyword.
348              
349             Examples (C<_> stands for the cursor position):
350              
351             my $foo = MyClass->_ # keyword is MyClass
352             my $foo->_ # keyword is $foo
353             my $foo->bar->_ # keyword is $foo->bar
354            
355             =head2 prefix
356              
357             Part of a method which has been typed already.
358              
359             Examples (C<_> stands for the cursor position):
360              
361             my $foo = MyClass->foo_ # keyword is MyClass, prefix is foo
362             my $foo->bar_ # keyword is $foo, prefix is bar
363              
364             =head2 methods
365              
366             Returns all methods which were found for L</keyword> and L</prefix>.
367              
368             =head2 trimmed_methods
369              
370             Returns L</methods> truncated from the beginning by the length of L</prefix>.
371              
372             =head1 INTERNAL METHODS
373              
374             =head2 guess_class (Str $keyword)
375              
376             Given a keyword (e.g. C<< $foo->bar->file >>) this method tries to find the class
377             from which to load the methods.
378              
379             =head2 handle_class
380              
381             Loads the selected class.
382              
383             =head2 handle_self
384              
385             Loads the current class.
386              
387             =head2 handle_method
388              
389             This method tries to resove the class of the returned value of a given method.
390             It supports Moose attributes as well as L<MooseX::Method::Signatures>.
391              
392             Example for an instrospectable class:
393              
394             package Signatures;
395              
396             use Moose;
397             use Path::Class::File;
398             use MooseX::Method::Signatures;
399             use Moose::Util::TypeConstraints;
400            
401             has dir => ( isa => 'Path::Class::Dir', is => 'rw' );
402              
403             BEGIN { class_type 'PathClassFile', { class => 'Path::Class::File' }; }
404              
405             method file returns (PathClassFile) {
406             return new Path::Class::File;
407             }
408              
409             1;
410              
411             Given this class, Devel::IntelliPerl provides the following features:
412              
413             my $sig = new Signatures;
414             $sig->_ # will suggest "dir" and "file" amongst others
415             $sig->file->_ # will suggest all methods from Path::Class::File
416              
417             =head2 handle_variable
418              
419             Tries to find the variable's class using regexes. Supported syntaxes:
420              
421             $variable = MyClass->new
422             $variable = MyClass->new(...)
423             $variable = new MyClass
424             # $variable isa MyClass
425              
426             =head2 inject_statement (Str $statement)
427              
428             Injects C<$statement> at the current position.
429              
430             =head2 update_inc
431              
432             Trigger called by L</filename>.
433              
434             =head1 SCREENCASTS
435              
436             L<http://www.screencast.com/t/H5DdRNbQVt>
437              
438             L<http://www.screencast.com/t/djkraaYgpx>
439              
440             =head1 TODO
441              
442             =over
443              
444             =item Support for auto completion in the POD (e.g. C<< L <Devel::IntelliPerl/[auto complete]> >>)
445              
446             =back
447              
448             =head1 AUTHOR
449              
450             Moritz Onken, C<< <onken at netcubed.de> >>
451              
452             =head1 BUGS
453              
454             Please report any bugs or feature requests to C<bug-devel-intelliperl at rt.cpan.org>, or through
455             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Devel-IntelliPerl>. I will be notified, and then you'll
456             automatically be notified of progress on your bug as I make changes.
457              
458             =head1 SUPPORT
459              
460             You can find documentation for this module with the perldoc command.
461              
462             perldoc Devel::IntelliPerl
463              
464              
465             You can also look for information at:
466              
467             =over 4
468              
469             =item * RT: CPAN's request tracker
470              
471             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Devel-IntelliPerl>
472              
473             =item * AnnoCPAN: Annotated CPAN documentation
474              
475             L<http://annocpan.org/dist/Devel-IntelliPerl>
476              
477             =item * CPAN Ratings
478              
479             L<http://cpanratings.perl.org/d/Devel-IntelliPerl>
480              
481             =item * Search CPAN
482              
483             L<http://search.cpan.org/dist/Devel-IntelliPerl/>
484              
485             =back
486              
487              
488             =head1 COPYRIGHT & LICENSE
489              
490             Copyright 2009 Moritz Onken, all rights reserved.
491              
492             This program is free software; you can redistribute it and/or modify it
493             under the same terms as Perl itself.
494              
495              
496             =cut