File Coverage

blib/lib/File/PackageIndexer/PPI/Util.pm
Criterion Covered Total %
statement 120 136 88.2
branch 70 98 71.4
condition 33 51 64.7
subroutine 12 14 85.7
pod 0 9 0.0
total 235 308 76.3


line stmt bran cond sub pod time code
1             package File::PackageIndexer::PPI::Util;
2              
3 9     9   244 use 5.008001;
  9         30  
  9         367  
4 9     9   57 use strict;
  9         103  
  9         328  
5 9     9   52 use warnings;
  9         20  
  9         34224  
6              
7             our $VERSION = '0.01';
8              
9             # try to regenerate a hash or array struct from
10             # a PPI::Structure::Constructor (anon hash/array constr.)
11             sub constructor_to_structure {
12 51     51 0 66 my $token = shift;
13            
14 51 50       174 return() unless $token->isa("PPI::Structure::Constructor");
15              
16 51         168 my $start = $token->start;
17 51 50       403 return() unless $start->isa("PPI::Token::Structure");
18 51 100       128 if ($start->content eq '{') {
    50          
19 34         179 return _hash_constructor_to_structure($token);
20             }
21             elsif ($start->content eq '[') {
22 17         175 return _array_constructor_to_structure($token);
23             }
24 0         0 return();
25             }
26              
27             sub list_structure_to_hash {
28 0     0 0 0 my $token = shift;
29            
30             #return() unless $token->isa("PPI::Structure::List");
31              
32 0         0 return _hash_constructor_to_structure($token);
33 0         0 return();
34             }
35              
36             sub list_structure_to_array {
37 117     117 0 157 my $token = shift;
38            
39             #return() unless $token->isa("PPI::Structure::List");
40              
41 117         271 return _array_constructor_to_structure($token);
42 0         0 return();
43             }
44              
45             sub _hash_constructor_to_structure {
46 34     34   45 my $hash = shift;
47              
48 34         57 my $struct = {};
49            
50 34         48 my $state = 'key';
51 34         41 my $key;
52              
53             # either use the children or walk the siblings
54 34 50       129 my $use_siblings = $hash->can('schildren') ? 0 : 1;
55 34 50       130 my @children = $use_siblings ? () : $hash->schildren();
56 34 50       371 my $token = $use_siblings ? $hash : undef;
57 34         44 while (1) {
58 242 100       453 if (@children) {
    50          
59 208         262 $token = shift @children;
60             }
61             elsif ($use_siblings) {
62 0         0 $token = $token->snext_sibling();
63 0 0 0     0 last if not defined $token or not ref($token);
64             }
65             else {
66 34         44 last;
67             }
68              
69             # flatten
70 208 100 66     1788 if ($token->isa("PPI::Statement") or $token->isa("PPI::Structure::List")) {
71 34         94 my @ch = $token->schildren();
72              
73             # pop trailing commas
74 34   66     753 while (@ch and $ch[-1]->isa("PPI::Token::Operator") || $ch[-1]->content =~ /^(?:,|=>)$/) {
      33        
75 3         28 pop(@ch);
76             }
77              
78 34         290 unshift @children, @ch;
79 34         68 next;
80             }
81              
82             # semicolon
83 174 50 33     897 return($struct)
84             if $token->isa("PPI::Token::Structure")
85             and $token->content eq ';';
86              
87             # special case: qw()
88 174 100 100     1276 if ( ($state eq 'key' or $state eq 'value')
      100        
89             and $token->isa("PPI::Token::QuoteLike::Words") )
90             {
91 4         13 my @values = qw_to_list($token);
92              
93             # emulate the state flip flop to end up in a consistent state afterwards
94 4         8 foreach my $v (@values) {
95 8 100       16 if ($state eq 'key') {
96 4         6 $key = $v;
97 4         8 $state = 'value';
98             }
99             else {
100 4         12 $struct->{$key} = $v;
101 4         5 $key = undef;
102 4         9 $state = 'key';
103             }
104             }
105 4         10 $state = 'comma';
106 4         8 next;
107             } # end special case 'qw'
108              
109 170 100       434 if ($state eq 'key') {
    100          
    50          
110 50         84 my $keyname = get_keyname($token);
111 50 50       279 return() if not defined $keyname;
112 50         65 $key = $keyname;
113 50         80 $state = 'comma';
114             }
115             elsif ($state eq 'comma') {
116 70 50       212 return() unless $token->isa("PPI::Token::Operator");
117 70 50       167 return() unless $token->content =~ /^(?:,|=>)$/; # are there other valid comma-likes?
118 70 100       509 $state = defined($key) ? 'value' : 'key';
119             }
120             elsif ($state eq 'value') {
121 50         90 my $value = token_to_string($token);
122 50 50       398 return() unless defined $value;
123 50         149 $struct->{$key} = $value;
124 50         58 $key = undef;
125 50         65 $state = 'comma';
126             }
127             else {
128 0         0 die "Sanity check: Unknown state!";
129             }
130             }
131              
132 34         114 return($struct);
133             }
134              
135             sub _array_constructor_to_structure {
136 134     134   168 my $array = shift;
137              
138 134         230 my $struct = [];
139            
140 134         188 my $state = 'elem';
141              
142             # either use the children or walk the siblings
143 134 100       622 my $use_siblings = $array->can('schildren') ? 0 : 1;
144 134 100       381 my @children = $use_siblings ? () : $array->schildren();
145 134 100       443 my $token = $use_siblings ? $array : undef;
146 134         171 while (1) {
147 546 100       1198 if (@children) {
    100          
148 200         302 $token = shift @children;
149             }
150             elsif ($use_siblings) {
151 320         939 $token = $token->snext_sibling();
152 320 100 33     6984 last if not defined $token or not ref($token);
153             }
154             else {
155 26         41 last;
156             }
157              
158             # flatten
159 490 100 100     4266 if ($token->isa("PPI::Statement") or $token->isa("PPI::Structure::List")) {
160 101         977 my @ch = $token->schildren();
161              
162             # pop trailing commas
163 101   66     1629 while (@ch and $ch[-1]->isa("PPI::Token::Operator") || $ch[-1]->content =~ /^(?:,|=>)$/) {
      66        
164 6         34 pop(@ch);
165             }
166              
167 101         1883 unshift @children, @ch;
168 101         183 next;
169             }
170              
171             # semicolon
172             last
173 389 100 66     2082 if $token->isa("PPI::Token::Structure")
174             and $token->content eq ';';
175              
176 311 100       686 if ($state eq 'elem') {
    50          
177 219 100       1180 if ($token->isa("PPI::Token::QuoteLike::Words")) {
178 32         88 my @values = qw_to_list($token);
179 32         45 push @{$struct}, @values;
  32         101  
180             }
181             else {
182 187         439 my $value = token_to_string($token);
183 187 50       1951 last unless defined $value;
184 187         215 push @{$struct}, $value;
  187         424  
185             }
186 219         346 $state = 'comma';
187             }
188             elsif ($state eq 'comma') {
189 92 50       297 last unless $token->isa("PPI::Token::Operator");
190 92 50       282 last unless $token->content =~ /^(?:,|=>)$/; # are there other valid comma-likes?
191 92         681 $state = 'elem';
192             }
193             else {
194 0         0 die "Sanity check: Unknown state!";
195             }
196             }
197              
198 134         895 return($struct);
199             }
200              
201             # best guess at turning a qw() into a real list
202             sub qw_to_list {
203 36     36 0 59 my $token = shift;
204 36 50       148 return() if not $token->isa("PPI::Token::QuoteLike::Words");
205              
206             # FIXME This breaks PPI encapsulation, but there seems to be no API!
207 36         98 my $string = substr($token->content, $token->{sections}[0]{position}, $token->{sections}[0]{size});
208 36         391 $string =~ s/^\s+//;
209 36         105 $string =~ s/\s+$//;
210 36         165 return split /\s+/, $string;
211             }
212              
213             # best guess at turning a token into the string it represents
214             sub token_to_string {
215 237     237 0 314 my $token = shift;
216 237 100       1183 if ($token->isa("PPI::Token::Quote")) {
    50          
217 164 100       1081 return($token->can('literal') ? $token->literal : $token->string);
218             }
219             elsif ($token->isa("PPI::Token::HereDoc")) {
220 0         0 return join '', $token->heredoc;
221             }
222             else {
223 73         206 return $token->content;
224             }
225             }
226              
227             # Given a PPI token, try to interpret it as a quoted "key" or word (re fat comma)
228             sub get_keyname {
229 129     129 0 169 my $token = shift;
230 129 50       423 return() unless $token->isa("PPI::Token");
231 129 100       579 return $token->content if $token->isa("PPI::Token::Word"); # likely followed by a =>
232 2 50       11 return $token->string if $token->isa("PPI::Token::Quote");
233 0         0 return(); # TODO: what else makes sense here?
234             }
235              
236             sub is_class_method_call {
237 7448     7448 0 8250 my $token = shift;
238 7448 100       30240 if ($token->isa("PPI::Token::Word")) {
239 1155         1797 return is_method_call($token);
240             }
241 6293         12115 return();
242             }
243              
244             sub is_instance_method_call {
245 0     0 0 0 my $token = shift;
246 0 0       0 if ($token->isa("PPI::Token::Symbol")) {
247 0         0 return is_method_call($token);
248             }
249 0         0 return();
250             }
251              
252             sub is_method_call {
253 1155     1155 0 1280 my $token = shift;
254 1155 50 33     3607 return() unless $token->isa("PPI::Token::Word") or $token->isa("PPI::Token::Symbol");
255              
256 1155         2618 my $next = $token->snext_sibling();
257             return()
258 1155 100 100     25176 unless $next
      100        
      100        
259             and $token->content =~ /^[\w:]+$/
260             and $next->isa("PPI::Token::Operator")
261             and $next->content eq '->';
262              
263 12         237 my $third = $next->snext_sibling();
264             return()
265 12 50       216 unless defined $third;
266              
267 12 50 33     50 if ( $third->isa("PPI::Token::Word") or $third->isa("PPI::Token::Symbol") ) {
268 12         32 return($token->content(), $third->content());
269             }
270 0           return();
271             }
272              
273              
274             1;
275              
276             __END__