File Coverage

blib/lib/Acme/PPIx/MetaSyntactic.pm
Criterion Covered Total %
statement 87 100 87.0
branch 25 42 59.5
condition 14 33 42.4
subroutine 20 23 86.9
pod n/a
total 146 198 73.7


line stmt bran cond sub pod time code
1             package Acme::PPIx::MetaSyntactic;
2              
3 2     2   26160 use 5.010001;
  2         8  
  2         85  
4 2     2   3159 use Moo;
  2         49406  
  2         13  
5 2     2   6798 use namespace::sweep;
  2         81737  
  2         16  
6 2     2   164 no warnings qw( void once uninitialized numeric );
  2         4  
  2         152  
7              
8             BEGIN {
9 2     2   6 $Acme::PPIx::MetaSyntactic::AUTHORITY = 'cpan:TOBYINK';
10 2         38 $Acme::PPIx::MetaSyntactic::VERSION = '0.002';
11             }
12              
13 2     2   2890 use Acme::MetaSyntactic;
  2         23775  
  2         14  
14 2     2   2795 use Perl::Critic::Utils qw( is_perl_builtin is_function_call );
  2         391042  
  2         66  
15 2     2   4968 use PPI;
  2         152019  
  2         120  
16              
17 2     2   1919 use Types::Standard -types;
  2         180396  
  2         59  
18 2     2   11943 use Type::Utils;
  2         9973  
  2         19  
19              
20             my $Document = class_type Document => { class => "PPI::Document" };
21             my $MetaSyntactic = class_type MetaSyntactic => { class => "Acme::MetaSyntactic" };
22             my $TruthTable = declare TruthTable => as Map[Str, Bool];
23              
24             coerce $Document,
25             from ScalarRef[Str], q { "PPI::Document"->new($_) },
26             from Str, q { "PPI::Document"->new($_) },
27             from FileHandle, q { do { local $/; my $c = <$_>; "PPI::Document"->new(\$c) } },
28             from ArrayRef[Str], q { do { my $c = join "\n", map { chomp(my $l = $_); $l } @$_; "PPI::Document"->new(\$c) } },
29             ;
30              
31             coerce $MetaSyntactic,
32             from Str, q { "Acme::MetaSyntactic"->new($_) },
33             ;
34              
35             coerce $TruthTable,
36             from ArrayRef[Str], q { +{ map +($_, 1), @$_ } },
37             ;
38              
39             has document => (
40             is => "ro",
41             isa => $Document,
42             coerce => $Document->coercion,
43             required => 1,
44             trigger => 1,
45             );
46              
47             has theme => (
48             is => "lazy",
49             isa => $MetaSyntactic,
50             coerce => $MetaSyntactic->coercion,
51             );
52              
53             has local_subs => (
54             is => "lazy",
55             isa => $TruthTable,
56             coerce => $TruthTable->coercion,
57             );
58              
59             has names => (
60             is => "lazy",
61             isa => Map[Str, Str],
62             );
63              
64             has already_used => (
65             is => "lazy",
66             isa => $TruthTable,
67             coerce => $TruthTable->coercion,
68             init_arg => undef,
69             );
70              
71             sub _get_name
72             {
73 4     4   172 my $self = shift;
74 4         97 my $name = $self->theme->name;
75 4         17271 my $i = undef;
76 4         99 my $used = $self->already_used;
77 4         132 $i++ while $used->{"$name$i"};
78 4         14 $used->{"$name$i"} = 1;
79 4         20 return "$name$i";
80             }
81              
82             sub _build_theme
83             {
84 1     1   1133 my $self = shift;
85 1         9 return $MetaSyntactic->new("haddock");
86             }
87              
88             sub _build_local_subs
89             {
90 1     1   1189 my $self = shift;
91 1         4 my %r;
92            
93 1 50       3 for my $word (@{ $self->document->find("PPI::Token::Word") || [] })
  1         16  
94             {
95 8 50       3599 $r{$word} = 1 if $word->sprevious_sibling eq "sub";
96 8 100 66     279 $r{$word} = 1 if $word->sprevious_sibling eq "constant" && $word->sprevious_sibling->sprevious_sibling eq "use";
97             }
98            
99 1         28 return \%r;
100             }
101              
102             sub _build_names
103             {
104 1     1   849 my $self = shift;
105 1         24 return +{};
106             }
107              
108             sub _build_already_used
109             {
110 1     1   1037 my $self = shift;
111             return +{
112 1         3 map +($_, 1), values %{ $self->names },
  1         26  
113             };
114             }
115              
116             sub _trigger_document
117             {
118 1     1   19195 my $self = shift;
119 1         8 $self->_relabel_subs;
120 1         6 $self->_relabel_variables;
121 1         41 return;
122             }
123              
124             sub _relabel_subs
125             {
126 1     1   2 my $self = shift;
127 1         7 my $ls = $self->local_subs;
128 1         1307 my $n = $self->names;
129            
130 1 50       26 for my $word (@{ $self->document->find("PPI::Token::Word")||[] })
  1         34  
131             {
132 8 100       4242 next if is_perl_builtin($word);
133            
134             # Function to preserve original case of variable.
135             my $case =
136 1     1   24 ($word eq uc $word) ? sub { uc $_[0] } :
137 4 50   0   112 ($word eq lc $word) ? sub { lc $_[0] } : sub { $_[0] };
  0 100       0  
  0         0  
138            
139 4 50 33     162 if ($word->sprevious_sibling eq "sub" and $ls->{$word})
    100 66        
    100 66        
      66        
140             {
141 0   0     0 $word->set_content($n->{$word} ||= $case->($self->_get_name));
142             }
143             elsif ($word->sprevious_sibling eq "constant" && $word->sprevious_sibling->sprevious_sibling eq "use" and $ls->{$word})
144             {
145 1   33     142 $word->set_content($n->{$word} ||= $case->($self->_get_name));
146             }
147             elsif (is_function_call($word) and $ls->{$word})
148             {
149 1   33     309 $word->set_content($n->{$word} ||= $case->($self->_get_name));
150             }
151             }
152            
153 1         16 return;
154             }
155              
156             sub _relabel_variables
157             {
158 1     1   3 my $self = shift;
159 1         33 my $ls = $self->local_subs;
160 1         34 my $n = $self->names;
161            
162             my $VariableFinder = sub {
163 90 100   90   2143 $_[1]->isa("PPI::Token::Symbol") or $_[1]->isa("PPI::Token::ArrayIndex");
164 1         12 };
165            
166 1 50       3 for my $word (@{ $self->document->find($VariableFinder) || [] })
  1         11  
167             {
168 7 50       428 next if $word->isa("PPI::Token::Magic");
169            
170             # Function to preserve original case of variable.
171             my $case =
172 0     0   0 ($word eq uc $word) ? sub { uc $_[0] } :
173 7 50   0   26 ($word eq lc $word) ? sub { lc $_[0] } : sub { $_[0] };
  3 50       12  
  0         0  
174            
175             # Separate sigil from the rest of the variable name.
176 7         306 (my $sigil = "$word") =~ s/(\w.*)$//g;
177 7         70 my $rest = $1;
178            
179 7 50       27 if ($word->isa("PPI::Token::Symbol"))
    0          
180             {
181 7   66     25 $n->{$word->symbol} ||= $case->($self->_get_name);
182 7         368 $word->set_content($sigil . $n->{$word->symbol});
183             }
184             elsif ($word->isa("PPI::Token::ArrayIndex")) # like $#foo
185             {
186 0   0     0 $n->{"\@$rest"} ||= $case->($self->_get_name);
187 0         0 $word->set_content($sigil . $n->{"\@$rest"});
188             }
189             }
190            
191 1 50       99 for my $qq (@{ $self->document->find("PPI::Token::Quote") || [] })
  1         9  
192             {
193             # A string that "co-incidentally" happens to have the name as a locally
194             # defined sub. This might be a __PACKAGE__->can("foo"), so change it!
195             #
196 1 50 33     3412 if ($ls->{$qq->string})
    50          
197             {
198 0         0 my $txt = "$qq";
199 0         0 $txt =~ s/${\quotemeta($qq->string)}/$n->{$qq->string}/eg;
  0         0  
  0         0  
200 0         0 $qq->set_content($txt);
201             }
202            
203             # An interpolated string. We'll do our best to find any variables
204             # within it and rename them, but PPI doesn't really look inside
205             # interpolated strings (yet?).
206             #
207             elsif ($qq->isa("PPI::Token::Quote::Double") or $qq->isa("PPI::Token::Quote::Interpolate"))
208             {
209 1         29 my $txt = "$qq";
210 1 0       8 $txt =~ s/([\$\@]\w+)/$n->{$1}?substr($1,0,1).$n->{$1}:$1/eg;
  0         0  
211 1         15 $qq->set_content($txt);
212             }
213             }
214             }
215              
216             1;
217              
218             __END__
219              
220             =pod
221              
222             =encoding utf-8
223              
224             =head1 NAME
225              
226             Acme::PPIx::MetaSyntactic - rename functions and variables in a PPI::Document using Acme::MetaSyntactic
227              
228             =head1 SYNOPSIS
229              
230             my $acme = "Acme::PPIx::MetaSyntactic"->new(document => \<<'END');
231             use v5.010;
232             use constant PLACE => "World";
233            
234             sub join_spaces {
235             return join " ", @_;
236             }
237            
238             my @greetings = qw(Hello);
239            
240             say join_spaces($greetings[0], PLACE);
241             END
242            
243             say $acme->document;
244              
245             Example output:
246              
247             use v5.010;
248             use constant VULTURE => "World";
249            
250             sub fraud {
251             return join " ", @_;
252             }
253            
254             my @gang_of_thieves = qw(Hello);
255            
256             say fraud($gang_of_thieves[0], VULTURE);
257              
258             =head1 DESCRIPTION
259              
260             This module uses L<PPI> to parse some Perl source code, find all the
261             variables and function names defined in it, and reassign them random names
262             using L<Acme::MetaSyntactic>.
263              
264             =head2 Constructor
265              
266             This module is object-oriented, though there's really very little reason
267             for it to be.
268              
269             =over
270              
271             =item C<< new(%attributes) >>
272              
273             Moose-style constructor.
274              
275             =back
276              
277             =head2 Attributes
278              
279             All attributes are read-only.
280              
281             =over
282              
283             =item C<< document >>
284              
285             The L<PPI::Document> that will be munged.
286              
287             Can be coerced from a C<< Str >> (filename), C<< ScalarRef[Str] >> (string
288             of Perl source), C<< ArrayRef[Str] >> (lines of Perl source) or
289             C<< FileHandle >>.
290              
291             Required.
292              
293             Once the C<document> attribute has been set, a trigger automatically runs
294             the relabelling.
295              
296             =item C<< theme >>
297              
298             The L<Acme::MetaSyntactic> object that will be used to obtain new names.
299             If your source code is more than a couple of lines; choose one that provides
300             a large selection of names.
301              
302             Can be coerced from C<< Str >> (theme name).
303              
304             Defaults to the C<< "haddock" >> theme.
305              
306             =item C<< local_subs >>
307              
308             HashRef where the keys are the names of subs which are considered locally
309             defined (i.e. not Perl built-ins, and not imported) and thus available for
310             relabelling. Values are expected to all be C<< "1" >>.
311              
312             Can be coerced from C<< ArrayRef[Str] >>.
313              
314             Defaults to a list built by scanning the C<document> with PPI.
315              
316             =item C<< names >>
317              
318             HashRef mapping old names to new names. This will be populated by the
319             relabelling process, but you may supply some initial values.
320              
321             Defaults to empty hashref.
322              
323             =item C<< already_used >>
324              
325             HashRef keeping track of names already used in remapping, to avoid renaming
326             two variables the same thing.
327              
328             Defaults to a hashref populated from C<names>.
329              
330             This attribute cannot be provided to the constructor.
331              
332             =back
333              
334             =head1 BUGS
335              
336             Please report any bugs to
337             L<http://rt.cpan.org/Dist/Display.html?Queue=Acme-PPIx-MetaSyntactic>.
338              
339             =head1 SEE ALSO
340              
341             L<PPI>, L<Acme::MetaSyntactic>, L<Acme::MetaSyntactic::RefactorCode>.
342              
343             =head1 AUTHOR
344              
345             Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
346              
347             =head1 COPYRIGHT AND LICENCE
348              
349             This software is copyright (c) 2013 by Toby Inkster.
350              
351             This is free software; you can redistribute it and/or modify it under
352             the same terms as the Perl 5 programming language system itself.
353              
354             =head1 DISCLAIMER OF WARRANTIES
355              
356             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
357             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
358             MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.