File Coverage

lib/Devel/PerlySense/Editor.pm
Criterion Covered Total %
statement 197 222 88.7
branch 8 14 57.1
condition 11 16 68.7
subroutine 35 40 87.5
pod 17 20 85.0
total 268 312 85.9


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Devel::PerlySense::Editor - Integration with editors
4              
5             =head1 DESCRIPTION
6              
7              
8             =cut
9              
10              
11              
12              
13              
14 7     7   1678 use strict;
  7         10  
  7         151  
15 7     7   19 use warnings;
  7         8  
  7         117  
16 7     7   21 use utf8;
  7         7  
  7         23  
17              
18             package Devel::PerlySense::Editor;
19             $Devel::PerlySense::Editor::VERSION = '0.0217';
20              
21              
22              
23 7     7   616 use Spiffy -Base;
  7         3715  
  7         30  
24 7     7   13208 use Data::Dumper;
  7     7   10  
  7     7   106  
  7         19  
  7         6  
  7         134  
  7         23  
  7         7  
  7         262  
25 7     7   26 use File::Basename;
  7         8  
  7         287  
26 7     7   4530 use Graph::Easy;
  7         521314  
  7         241  
27 7     7   3254 use Text::Table;
  7         72517  
  7         193  
28 7     7   41 use List::Util qw/ max first /;
  7         9  
  7         401  
29 7     7   413 use POSIX qw/ ceil /;
  7         3984  
  7         49  
30 7     7   1472 use Path::Class;
  7         25475  
  7         295  
31              
32 7     7   1572 use Devel::PerlySense;
  7         16  
  7         77  
33 7     7   1888 use Devel::PerlySense::Class;
  7         8  
  7         33  
34 7     7   1131 use Devel::PerlySense::Util;
  7         8  
  7         330  
35 7     7   25 use Devel::PerlySense::Util::Log;
  7         10  
  7         221  
36 7     7   2177 use Devel::PerlySense::Document::Api::Method;
  7         10  
  7         34  
37              
38              
39              
40              
41              
42             =head1 PROPERTIES
43              
44             =head2 oPerlySense
45              
46             Devel::PerlySense object.
47              
48             Default: set during new()
49              
50             =cut
51             field "oPerlySense" => undef;
52              
53              
54              
55              
56              
57             =head2 widthDisplay
58              
59             The width of the display in columns, or undef if N/A.
60              
61             Default: undef
62              
63             =cut
64             field "widthDisplay" => undef;
65              
66              
67              
68              
69              
70             =head2 raClassOverviewShowDefault
71              
72             Names of features to show in the class overview by default.
73              
74             Default: { ... }
75              
76             =cut
77             field "raClassOverviewShowDefault" => [qw/
78             inheritance
79             api
80             bookmarks
81             uses
82             /];
83              
84              
85              
86              
87              
88             =head2 raClassOverviewShow
89              
90             Names of features to allow being show in the class overview.
91              
92             Default: { ... }
93              
94             =cut
95             field "raClassOverviewShow" => [qw/
96             inheritance
97             api
98             bookmarks
99             uses
100             neighbourhood
101             /];
102              
103              
104              
105              
106              
107             =head1 CLASS METHODS
108              
109             =head2 dirExtenal()
110              
111             Return the absolute directory of the external editor files.
112              
113             =cut
114 1     1 1 756 sub dirExtenal {
115 1         4 return dir(
116             file(__FILE__)->dir->absolute,
117             "external",
118             ) . "";
119             }
120              
121              
122              
123              
124              
125             =head2 new(oPerlySense, widthDisplay = undef)
126              
127             Create new Emcacs object.
128              
129             =cut
130 8     8 1 562 sub new {
131 8         31 my ($oPerlySense, $widthDisplay) = Devel::PerlySense::Util::aNamedArg(["oPerlySense", "widthDisplay"], @_);
132              
133 7         17 $self = bless {}, $self; #Create the object. It looks weird because of Spiffy
134 7         139 $self->oPerlySense($oPerlySense);
135 7         134 $self->widthDisplay($widthDisplay);
136              
137 7         44 return($self);
138             }
139              
140              
141              
142              
143              
144             =head1 METHODS
145              
146             =head2 classOverview(oClass)
147              
148             Return string representing the Class Overview of $oClass.
149              
150             =cut
151 7     7 1 83 sub classOverview {
152 7         31 my ($oClass, $raShow) = Devel::PerlySense::Util::aNamedArg(["oClass", "raShow"], @_);
153              
154 7         20 my %hNameHeading = (
155             Api => "API",
156             Neighbourhood => "NeighbourHood",
157             );
158 7         9 my @aTextOutput;
159 7         14 for my $show (@$raShow) {
160 15         32 my $name = ucfirst($show);
161 15         23 my $nameMethod = "textClass$name";
162 15 50       53 $self->can($nameMethod) or die("Internal error ($nameMethod)");
163              
164 15   66     182 my $nameHeading = $hNameHeading{$name} || $name;
165 15         52 my $text = "* $nameHeading *\n" . $self->$nameMethod(oClass => $oClass);
166 15         17088 push(@aTextOutput, $text);
167             }
168              
169 7         99 my $textOverview = $self->stripTrailingWhitespace( join("\n", @aTextOutput) );
170              
171             #Highlight the current class
172 7         19 my $leftBracket = "[[]";
173 7         9 my $space = "[ ]";
174 7         167 my $name = $oClass->name;
175 7         151 $textOverview =~ s| $leftBracket \s+ ( $name \s*? ) $space ] |[<$1>]|xg;
176 7         31 debug($textOverview);
177              
178 7         49 return $textOverview;
179             }
180              
181              
182              
183              
184              
185             =head2 textClassInheritance(oClass)
186              
187             Return string representing the class hierarchy of $oClass.
188              
189             =cut
190 3     3 1 4 sub textClassInheritance {
191 3         9 my ($oClass) = Devel::PerlySense::Util::aNamedArg(["oClass"], @_);
192              
193 3         30 my $oGraph = Graph::Easy->new();
194 3         282 $oGraph->set_attribute('graph', flow => "up");
195 3         354 $oGraph->set_attribute('node', border => "dotted");
196              
197 3         318 $oGraph->add_node($oClass->name);
198 3         198 my $rhSeenEdge = { };
199 3         14 $self->addBaseClassNameToGraph(
200             oGraph => $oGraph,
201             oClass => $oClass,
202             rhSeenEdge => $rhSeenEdge,
203             );
204              
205              
206             # Disable the subclass view until it either becomes faster and/or
207             # is better rendered. The Neighbourhood view may be quite enough.
208              
209             # $self->addSubClassNameToGraph(
210             # oGraph => $oGraph,
211             # oClass => $oClass,
212             # rhSeenEdge => $rhSeenEdge,
213             # );
214              
215 3         15 my $textInheritance = $self->textCompactGraph(text => $oGraph->as_ascii()) . "\n";
216              
217 3         19 return $textInheritance;
218             }
219              
220              
221              
222 7     7 0 9 sub addBaseClassNameToGraph {
223 7         23 my ($oClass, $oGraph, $rhSeenEdge) = Devel::PerlySense::Util::aNamedArg(["oClass", "oGraph", "rhSeenEdge"], @_);
224              
225 7         11 for my $oClassBase (values %{$oClass->rhClassBase}) {
  7         112  
226 4 50       63 $rhSeenEdge->{$oClass->name . "->" .$oClassBase->name}++ and next;
227 4         83 $oGraph->add_edge($oClass->name, $oClassBase->name);
228              
229 4         294 $self->addBaseClassNameToGraph(
230             oGraph => $oGraph,
231             oClass => $oClassBase,
232             rhSeenEdge => $rhSeenEdge,
233             );
234             }
235              
236 7         27 return 1;
237             }
238              
239              
240              
241              
242              
243 0     0 0 0 sub addSubClassNameToGraph {
244 0         0 my ($oClass, $oGraph, $rhSeenEdge) = Devel::PerlySense::Util::aNamedArg(["oClass", "oGraph", "rhSeenEdge"], @_);
245              
246 0         0 for my $oClassSub (values %{$oClass->rhClassSub}) {
  0         0  
247 0 0       0 $rhSeenEdge->{$oClassSub->name . "->" .$oClass->name}++ and next;
248 0         0 $oGraph->add_edge($oClassSub->name, $oClass->name);
249              
250 0         0 $self->addSubClassNameToGraph(
251             oGraph => $oGraph,
252             oClass => $oClassSub,
253             rhSeenEdge => $rhSeenEdge,
254             );
255             }
256              
257 0         0 return 1;
258             }
259              
260              
261              
262              
263              
264             =head2 textClassNeighbourhood(oClass)
265              
266             Return string representing the neighbourhood of $oClass.
267              
268             =cut
269 3     3 1 6 sub textClassNeighbourhood {
270 3         10 my ($oClass) = Devel::PerlySense::Util::aNamedArg(["oClass"], @_);
271              
272 3         15 my $rhDirClass = $oClass->rhDirNameClassInNeighbourhood();
273              
274 3         15 my @aColText;
275 3         7 for my $raNameClass (map { $rhDirClass->{$_} } qw/ up current down /) {
  9         17  
276 9         17 my $lenMax = max( map { length } @$raNameClass );
  31         33  
277              
278             my $text = join(
279             "\n",
280 9   100     24 map { sprintf("[ %-*s ]", $lenMax, $_) } @$raNameClass,
281             ) || "-none-";
282              
283 9         12 push(@aColText, $text);
284             }
285              
286 3         33 my $oTable = Text::Table->new();
287 3         274 $oTable->load([ @aColText ]);
288              
289 3         1443 return "$oTable";
290             }
291              
292              
293              
294              
295              
296             =head2 textClassUses(oClass)
297              
298             Return string representing the modules used by $oClass. Use the least
299             number of columns to display this.
300              
301             =cut
302 3     3 1 7 sub textClassUses {
303 3         11 my ($oClass) = Devel::PerlySense::Util::aNamedArg(["oClass"], @_);
304              
305 3   50     59 my $columnsToFitWithin = $self->widthDisplay || 90; ###TODO: Move to config
306              
307             return(
308             $self->textTable(
309             [ $oClass->aNameModuleUse() ],
310             $columnsToFitWithin,
311             sub {
312 19     19   19 my ($item, $raItem) = @_;
313 19         16 my $lenMax = max( map { length } @$raItem );
  27         37  
314 19         50 sprintf("[ %-*s ]", $lenMax, $item);
315             },
316             )
317 3         29 );
318             }
319              
320              
321              
322              
323              
324             =head2 textTable($raItem, $columnWidthMax, [$rsRenderItem = string-as-is])
325              
326             Return string with the items in $raItem rendered as a table, with as
327             few columns as possible.
328              
329             If the $rsRenderItem sub ref is passed, it is called for each item to
330             be rendered:
331              
332             $rsRenderItem->($stringItem, $rsItemColumn)
333              
334             where $stringItem is each individual item, and $rsItemColumn is the
335             items in the current column. The default is to just pass through the
336             $stringItem text.
337              
338             =cut
339 15     15 1 20 sub textTable {
340 15         20 my ($raItemAll, $columnsToFitWithin, $rsRenderItem) = @_;
341 15   100 3127   76 $rsRenderItem ||= sub { $_[0] };
  3127         3171  
342              
343 15         19 my $text = "";
344 15         38 for my $columns (reverse 1 .. @$raItemAll) {
345 97         140 my @aColText;
346              
347 97         114 for my $raItem ( @{$self->raItemInNGroups($raItemAll, $columns)} ) {
  97         239  
348 1247         1045 my $text = join("\n", map { $rsRenderItem->($_, $raItem) } @$raItem);
  3150         2472  
349 1247         1434 push(@aColText, $text);
350             }
351              
352 97         468 my $oTable = Text::Table->new();
353 97         5394 $oTable->load([ @aColText ]);
354 97         97070 $text = "$oTable";
355              
356 97 100       1125820 length( (split(/\n/, $text))[0] ) <= $columnsToFitWithin and last;
357             }
358              
359              
360 15         89 return $text;
361             }
362              
363              
364              
365              
366              
367             =head2 textClassBookmarks(oClass)
368              
369             Return string representing the Bookmarks of $oClass.
370              
371             =cut
372 3     3 1 6 sub textClassBookmarks {
373 3         14 my ($oClass) = Devel::PerlySense::Util::aNamedArg(["oClass"], @_);
374              
375 3         20 my @aBookmarkMatchResult = $oClass->aBookmarkMatchResult();
376              
377             my $matches = join(
378             "\n",
379             map(
380             {
381 3         11 "- " . $_->oDefinition->moniker . "\n" . join(
382             "\n",
383             map(
384             {
385 4         217 sprintf(
386             "%s:%s: %s",
387             basename($_->oLocation->file),
388             $_->oLocation->row,
389             $_->text, ##TODO: text escaped for { }
390             );
391             }
392 2         93 @{$_->raMatch},
  2         53  
393             ),
394             ),
395             }
396             @aBookmarkMatchResult,
397             ),
398             );
399 3   66     81 $matches &&= "$matches\n";
400              
401 3         27 return $matches;
402             }
403              
404              
405              
406              
407              
408             =head2 textClassStructure(oClass)
409              
410             Return string representing the structure of $oClass.
411              
412             This includes a Signature Survey string.
413              
414             =cut
415 0     0 1 0 sub textClassStructure {
416 0         0 my ($oClass) = Devel::PerlySense::Util::aNamedArg(["oClass"], @_);
417              
418             my $textSignature = $self->textLineWrapped(
419             join(
420             "",
421 0         0 map { $_->stringSignatureSurveyFromFile } @{$oClass->raDocument},
  0         0  
  0         0  
422             ),
423             );
424              
425 0         0 return "$textSignature\n";
426             }
427              
428              
429              
430              
431              
432             =head2 textClassApi(oClass)
433              
434             Return string representing the API of $oClass.
435              
436             =cut
437 3     3 1 6 sub textClassApi {
438 3         11 my ($oClass) = Devel::PerlySense::Util::aNamedArg(["oClass"], @_);
439              
440 3         81 my $oDocument = $oClass->raDocument->[0]; ### or die
441 3         57 $oDocument->determineLikelyApi(nameModule => $oClass->name);
442              
443 3 50       39 my $oApi = $oDocument->rhPackageApiLikely->{$oClass->name} or do {
444 0         0 debug("Could not find API for ("
445             . $oClass->name . ") in ("
446             . $oDocument->file . ")");
447 0         0 return("");
448             };
449              
450             my @aColText = map {
451 3         109 my $nameMethod = $_;
  69         130  
452              
453 69         551 my $oMethod = Devel::PerlySense::Document::Api::Method->new(
454             name => $nameMethod,
455             oDocument => $oDocument,
456             );
457              
458 69         1039 my $oLocationDeclaration = $oApi->rhSub->{$nameMethod};
459 69         564 $oMethod->signatureCall($oLocationDeclaration);
460             } $oApi->aNameSubVisible(
461             oPerlySense => $self->oPerlySense,
462             fileCurrent => $oDocument->file,
463             );
464              
465 3   50     68 my $columnsToFitWithin = $self->widthDisplay || 90; ###TODO: Move to config
466 3         31 return( $self->textTable(\@aColText, $columnsToFitWithin) );
467             }
468              
469              
470              
471              
472              
473             =head2 textLineWrapped($text)
474              
475             Return $text wrapped hard at the available number of columns.
476              
477             =cut
478 7     7 1 12 sub textLineWrapped {
479 7         7 my ($text) = @_;
480              
481 7   50     148 my $columnsToFitWithin = $self->widthDisplay || 90; ###TODO: Move to config
482              
483 7         35 my @aLine;
484 7         10 while (length($text)) {
485 14         36 push(@aLine, substr($text, 0, $columnsToFitWithin, ""));
486             }
487              
488 7         15 my $textWrapped = join("\n", @aLine);
489              
490 7         27 return $textWrapped;
491             }
492              
493              
494              
495              
496              
497             =head2 raItemInNGroups($raItem, $countGroup)
498              
499             Split up the items in $raItem so that they form at most $countGroup
500             array refs.
501              
502             The items are evenly distributed between the group with the same numer
503             of items in each, except for the last one which may contain fewer
504             items.
505              
506             Return array ref with $countGroup items, each of which is an array ref
507             with the elements in $raItem.
508              
509             =cut
510 109     109 1 127 sub raItemInNGroups {
511 109         130 my ($raItem, $countGroup) = @_;
512              
513 109         750 my @aItem = @$raItem;
514 109 100       529 my $countItemPerGroup = ceil(@aItem / $countGroup) or return( [ ] );
515              
516 107         122 my @aGroupItem;
517 107         208 while(scalar @aItem) {
518 1265         2127 push(@aGroupItem, [ splice(@aItem, 0, $countItemPerGroup) ]);
519             }
520 107 50       199 @aItem and push(@aGroupItem, [ @aItem ]);
521              
522 107         359 return [ @aGroupItem ];
523             }
524              
525              
526              
527              
528              
529             =head2 textCompactGraph(text)
530              
531             Return compact version of $text.
532              
533             =cut
534 3     3 1 26155 sub textCompactGraph {
535 3         17 my ($text) = Devel::PerlySense::Util::aNamedArg(["text"], @_);
536              
537             # debug($text);
538              
539 3         17 my @aLine = split(/\n/, $text);
540              
541             #Remove blank lines
542 3         8 @aLine = grep { $_ } @aLine;
  21         22  
543              
544             #Put [ Class::Name ] around module names
545 3         51 s{ : ( \s+ [\w:]+ \s+ ) : }{[$1]}xg for (@aLine);
546              
547             #Make [ Class::Name ] left-aligned in the box
548 3         8 my $leftBracket = "[[]";
549 3         5 my $space = "[ ]";
550 3         83 s{ $leftBracket $space (\s+) ([\w:]+) }{[ $2$1}xg for (@aLine);
551              
552             #Remove border-only lines
553 3         7 @aLine = grep { ! /[.]/ } @aLine;
  21         33  
554              
555             #Remove vertical-lines-only lines
556 3         6 @aLine = grep { /[^ |^]/ } @aLine;
  11         23  
557              
558 3         7 $text = join("\n", @aLine);
559              
560 3         11 return $text;
561             }
562              
563              
564              
565              
566              
567             =head2 formatOutputDataStructure(rhData)
568              
569             Return stringification of $rhData suited for the Editor.
570              
571             =cut
572 0     0 1 0 sub formatOutputDataStructure {
573 0         0 my ($rhData) = Devel::PerlySense::Util::aNamedArg(["rhData"], @_);
574 0         0 croak("Abstract method called (formatOutputDataStructure)");
575             }
576              
577              
578              
579              
580              
581             =head2 formatOutputItem($item)
582              
583             Return stringification of $item suited for the Editor. $item can be a
584             scalar, array ref or hash ref.
585              
586             =cut
587 0     0 1 0 sub formatOutputItem {
588 0         0 my ($value) = @_;
589 0         0 croak("Abstract method called (formatOutputDataStructure)");
590             }
591              
592              
593              
594              
595              
596             =head2 renameIdentifier($identifier)
597              
598             Return $identifier changed to suit the Editor.
599              
600             Default is to do nothing.
601              
602             =cut
603 21     21 1 1435 sub renameIdentifier {
604 21         14 my ($identifier) = (@_);
605 21         34 return $identifier;
606             }
607              
608              
609              
610              
611              
612 0     0 0 0 sub escapeValue {
613 0         0 my ($value) = (@_);
614 0         0 return $value;
615             }
616              
617              
618              
619              
620              
621             =head2 stripTrailingWhitespace($string)
622              
623             Return $string with each line in $string stripped of trailing
624             whitespace.
625              
626             =cut
627 7     7 1 10 sub stripTrailingWhitespace {
628 7         11 my ($string) = @_;
629             return join(
630             "\n",
631 7         41 map { $_ =~ s/\s+$//; $_ } split("\n", $string), ## no critic
  120         178  
  120         127  
632             );
633             }
634              
635              
636              
637              
638              
639             1;
640              
641              
642              
643              
644              
645             __END__
646              
647             =encoding utf8
648              
649             =head1 AUTHOR
650              
651             Johan Lindstrom, C<< <johanl@cpan.org> >>
652              
653             =head1 BUGS
654              
655             Please report any bugs or feature requests to
656             C<bug-devel-perlysense@rt.cpan.org>, or through the web interface at
657             L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Devel-PerlySense>.
658             I will be notified, and then you'll automatically be notified of progress on
659             your bug as I make changes.
660              
661             =head1 ACKNOWLEDGEMENTS
662              
663             =head1 COPYRIGHT & LICENSE
664              
665             Copyright 2005 Johan Lindstrom, All Rights Reserved.
666              
667             This program is free software; you can redistribute it and/or modify it
668             under the same terms as Perl itself.
669              
670             =cut