File Coverage

blib/lib/Acme/SubstituteSubs.pm
Criterion Covered Total %
statement 24 86 27.9
branch 0 52 0.0
condition 0 15 0.0
subroutine 8 14 57.1
pod 5 6 83.3
total 37 173 21.3


line stmt bran cond sub pod time code
1             package Acme::SubstituteSubs;
2              
3 2     2   59137 use 5.008000;
  2         7  
  2         76  
4 2     2   11 use strict;
  2         3  
  2         58  
5 2     2   10 use warnings;
  2         7  
  2         133  
6              
7             our $VERSION = '0.02';
8              
9             =head1 NAME
10              
11             Acme::SubstituteSubs - Replace subroutines at run-time
12              
13             =head1 SYNOPSIS
14              
15             use Acme::SubstituteSubs;
16              
17             sub say_hi { print "hihihihi!\n"; }
18              
19             my $say_hi = Acme::SubstituteSubs->get('main::say_hi') or die;
20             $say_hi =~ s/"hi/"hihi/;
21             Acme::SubstituteSubs->set('main::say_hi', $say_hi) or die;
22             say_hi();
23              
24             exec 'perl', $0;
25              
26              
27             =head1 DESCRIPTION
28              
29             Replaces subroutine definitions in the source code, probably for code that edits
30             itself or lets its user edit it.
31              
32             =head2 C<< Acme::SubstituteSubs->get($qualified_function_name) >>
33              
34             Returns the text of the named function straight from the source file.
35             For the purposes of this module, all code comes from and goes to the top-level C<.pl> file
36             as indicated by F's C<$RealScript> value.
37             Returns nothing if the sub is not found.
38              
39             =head2 C<< Acme::SubstituteSubs->set($qualified_function_name, $replacement_code) >>
40              
41             Replaces the copy of the function or method specified by C<$qualified_function_name>
42             with the code specified in C<$replacement_code> in the source code of the script (see above).
43             C uses L if passed a coderef.
44              
45             If the function name doesn't already exist, it'll be added to the end of the appropriate package.
46             If the package doesn't already exist in the source file of the script, it'll be added to the end and
47             the new function placed after it.
48              
49             If attempting to replace a function defined elsewhere than the top level C<.pl> file, such as in some module,
50             the module won't be changed, but the code will instead be replicated into the main script.
51             The result is undefined when run from C.
52              
53             Cs if it fails to write and replace the original source file.
54              
55             =head2 C<< Acme::SubstituteSubs->list() >>
56              
57             Lists C combinations available for edit.
58              
59             =head2 C<< Acme::SubstituteSubs->list_packages() >>
60              
61             Lists packages defined in the source script.
62              
63             =head1 TODO/BUGS
64              
65             =item Needs a REPL plugin, so REPLs can call this when the user redefines a subroutine.
66              
67             =item Parses the document again each time a method is called rather than caching it. Bug.
68              
69             =item There's gotta be a better way to use the PPI API but I just could not get the C method to work.
70              
71             =item Should have been called L?
72              
73             =item Somehow tie or watch the stash and automatically decompile and write out new subroutines on change?
74              
75             =item Hardly tested this at all. I'd wait for 0.02 if I were you.
76              
77             =head2
78              
79             =head1 HISTORY
80              
81             =over 8
82              
83             =item 0.02
84              
85             Fixed the example.
86              
87             =item 0.01
88              
89             Original version; created by h2xs 1.23 with options
90              
91             -A -C -X -b 5.8.0 -c -n Acme::SubstituteSubs --extra-anchovies
92              
93             =back
94              
95              
96             =head1 SEE ALSO
97              
98             =item L
99              
100             =item L
101              
102             =item L
103              
104             =item L
105              
106             If you're using Acme modules, a therapist.
107              
108             =head1 AUTHOR
109              
110             Scott Walters, Escott@slowass.netE
111              
112             =head1 COPYRIGHT AND LICENSE
113              
114             Copyright (C) 2009 by Scott Walters
115              
116             This library is free software; you can redistribute it and/or modify
117             it under the same terms as Perl itself, either Perl version 5.8.9 or,
118             at your option, any later version of Perl 5 you may have available.
119              
120             =cut
121              
122 2     2   2089 use IO::Handle;
  2         17199  
  2         107  
123 2     2   2010 use PPI;
  2         385205  
  2         76  
124 2     2   1619 use FindBin '$RealScript';
  2         2198  
  2         262  
125 2     2   1632 use Devel::Caller;
  2         7555  
  2         97  
126 2     2   13 use B::Deparse;
  2         5  
  2         1967  
127              
128 0     0 1   sub new { $_[0] }
129              
130             sub get {
131 0 0   0 1   shift if Devel::Caller::called_as_method;
132 0           my $fqfunc = shift;
133              
134 0           my ($packagename, $methodname) = ($fqfunc =~ m/(.*)::(.*)/, 'main', $fqfunc);
135 0 0         my $doc = PPI::Document->new($RealScript) or die PPI::Document->errstr;
136              
137 0           my $code;
138              
139 0           my $currentmodule = "main";
140 0           for my $child ($doc->children) {
141 0 0         if($child->isa('PPI::Statement::Sub')) {
    0          
142 0 0 0       $code = $child->content if $child->name eq $methodname and $currentmodule eq $packagename;
143             } elsif($child->isa('PPI::Statement::Package')) {
144 0           $currentmodule = $child->namespace;
145             }
146             }
147              
148 0 0         return unless $code;
149 0           return $code;
150              
151             }
152              
153             sub set {
154 0 0   0 1   shift if Devel::Caller::called_as_method;
155 0           my $fqfunc = shift;
156 0           my $code = shift;
157              
158 0           my ($packagename, $methodname) = ($fqfunc =~ m/(.*)::(.*)/, 'main', $fqfunc);
159              
160 0 0         defined $code or die 'set($qualified_function_name, $replacement_code)';
161              
162             # if code is a CODE ref, deparse it
163             # XXX extra points for keeping values for lexicals
164 0 0 0       $code = B::Deparse->new->coderef2text($code) if ref($code) and ref($code) eq 'CODE';
165              
166 0 0         if($code =~ m/^{/) {
    0          
    0          
167 0           $code = qq; # happens when B::Deparse kicks in
168             } elsif($code =~ m/^\s*sub\s+{/) {
169 0           $code =~ s/sub/sub $methodname /; # untested codepath alert
170             } elsif($code !~ m/^\s*sub/) {
171 0           $code = qq;
172             }
173              
174             # $code .= "\n" unless $code =~ m/\n$/s;
175              
176             # STDERR->print("saving updates to $RealScript\n");
177 0 0         open my $fh, '>', $RealScript.'.new' or die $!;
178              
179 0           my $currentpackage = 'main';
180 0           my $foundit = 0;
181              
182 0 0         my $doc = PPI::Document->new($RealScript) or die PPI::Document->errstr;
183              
184 0           for my $child ($doc->children) {
185 0 0         if($child->isa('PPI::Statement::Sub')) {
    0          
186 0 0 0       if(! $foundit and $child->name eq $methodname and $currentpackage eq $packagename) {
      0        
187 0           $fh->print($code); # instead of $child->content
188 0           $foundit = 1;
189             } else {
190 0           $fh->print($child->content);
191             }
192             } elsif($child->isa('PPI::Statement::Package')) {
193 0 0 0       if(! $foundit and $currentpackage eq $packagename) {
194 0           $fh->print($code);
195 0           $foundit = 1;
196             }
197 0           $currentpackage = $child->namespace;
198 0           $fh->print($child->content);
199             } else {
200 0 0         $fh->print($child->content) or die;
201             }
202             }
203 0 0         if(! $foundit ) {
204             # at the end of the file and still haven't found the package/sub? just append it.
205 0 0         if($currentpackage ne $packagename) {
206 0           $fh->print(qq{\npackage $packagename;\n});
207             }
208 0           $fh->print($code);
209             }
210 0           $fh->close;
211              
212 0           rename $RealScript, $RealScript.'.last';
213 0 0         rename $RealScript.'.new', $RealScript or do {
214 0           warn "renaming new pl file into place as ``$RealScript'' failed: $!";
215             };
216             }
217              
218             sub list_both {
219 0 0   0 0   shift if Devel::Caller::called_as_method;
220              
221 0           my @packages;
222             my @subs;
223              
224 0 0         my $doc = PPI::Document->new($RealScript) or die PPI::Document->errstr;
225              
226 0           my $currentpackage = 'main::';
227 0           push @packages, $currentpackage;
228 0           for my $child ($doc->children) {
229 0 0         if($child->isa('PPI::Statement::Sub')) {
    0          
230 0           push @subs, $currentpackage . $child->name;
231             } elsif($child->isa('PPI::Statement::Package')) {
232 0           $currentpackage = $child->namespace . '::';
233 0           push @packages, $currentpackage;
234             }
235             }
236              
237 0           return \@subs, \@packages;
238              
239             }
240              
241             sub list {
242 0     0 1   return @{ list_both()->[0] };
  0            
243             }
244              
245             sub list_packages {
246 0     0 1   return @{ list_both()->[1] };
  0            
247             }
248              
249             1;