File Coverage

blib/lib/Class/Refresh.pm
Criterion Covered Total %
statement 98 125 78.4
branch 22 40 55.0
condition 4 17 23.5
subroutine 23 23 100.0
pod 5 5 100.0
total 152 210 72.3


line stmt bran cond sub pod time code
1             package Class::Refresh;
2             BEGIN {
3 3     3   209074 $Class::Refresh::AUTHORITY = 'cpan:DOY';
4             }
5             {
6             $Class::Refresh::VERSION = '0.07';
7             }
8 3     3   26 use strict;
  3         7  
  3         85  
9 3     3   16 use warnings;
  3         4  
  3         109  
10             # ABSTRACT: refresh your classes during runtime
11              
12 3     3   15 use Carp 'carp';
  3         5  
  3         160  
13 3     3   2110 use Class::Unload;
  3         14878  
  3         91  
14 3     3   3044 use Class::Load;
  3         113903  
  3         140  
15 3     3   25 use Try::Tiny;
  3         4  
  3         6172  
16              
17              
18             our %CACHE;
19              
20             sub import {
21 3     3   31 my $package = shift;
22 3         9 my %opts = @_;
23              
24 3 100       3408 if ($opts{track_require}) {
25 2         1676 require Devel::OverrideGlobalRequire;
26 2         3181 require B;
27             Devel::OverrideGlobalRequire::override_global_require(sub {
28 102     102   43843 my $next = shift;
29 102         153 my ($file) = @_;
30              
31 102         235 my $ret = $next->();
32              
33 99 100 66     31797 $package->_update_cache_for($file)
34             # require v5.8.1;
35             unless ref(\$file) eq 'VSTRING'
36             # require 5.008001;
37             || !(B::svref_2object(\$file)->FLAGS & B::SVf_POK());
38              
39 99         490 return $ret;
40 2         1318 });
41             }
42             }
43              
44              
45             sub refresh {
46 8     8 1 2011973 my $class = shift;
47              
48 8         41 $class->refresh_module($_) for $class->modified_modules;
49             }
50              
51              
52             sub modified_modules {
53 8     8 1 18 my $class = shift;
54              
55 8         16 my @ret;
56 8         181 for my $file (keys %CACHE) {
57             # refresh files that are in our
58             # %CACHE but not in %INC
59 529 100       1092 push @ret, $class->_file_to_mod($file)
60             if (!$INC{$file});
61             }
62              
63 8         238 for my $file (keys %INC) {
64 820 100       7130 if (exists $CACHE{$file}) {
65 529 100       1061 push @ret, $class->_file_to_mod($file)
66             if $class->_mtime($file) ne $CACHE{$file};
67             }
68             else {
69 291         603 $class->_update_cache_for($file);
70             }
71             }
72              
73 8         208 return @ret;
74             }
75              
76              
77             sub refresh_module {
78 4     4 1 9 my $class = shift;
79 4         8 my ($mod) = @_;
80 4         13 $mod = $class->_file_to_mod($mod);
81              
82 4         22 my @to_refresh = grep { exists $INC{ $class->_mod_to_file($_) } }
  4         317  
83             $class->_dependent_modules($mod);
84              
85             # immutable metaclasses will be automatically recreated when the metaclass
86             # itself is loaded, so we don't want to try to do it here (it won't work,
87             # since it's an autogenerated class)
88 4         7 my %metas_for_immutable;
89 4 50       14 if (Class::Load::is_class_loaded('Class::MOP')) {
90 0 0 0     0 my @immutable_metas = grep {
91 0         0 defined $_ && $_->isa('Class::MOP::Class') && $_->is_immutable
92 0         0 } map { Class::MOP::class_of($_) } @to_refresh;
93 0         0 for my $meta (@immutable_metas) {
94 0         0 $metas_for_immutable{ref $meta} = 1;
95             }
96             }
97              
98             # XXX don't know what else to do here
99 4 50       139 if (Class::Load::is_class_loaded('Class::MOP')) {
100 0         0 my @new_to_refresh;
101 0         0 for my $to_refresh (@to_refresh) {
102 0   0     0 my $inc = $INC{ $class->_mod_to_file($to_refresh) } || '';
103 0 0 0     0 if (!$metas_for_immutable{$to_refresh} && $inc eq '(set by Moose)' && Class::MOP::class_of($to_refresh)) {
      0        
104 0         0 carp("Not reloading $to_refresh since it was created dynamically");
105 0         0 next;
106             }
107 0         0 push @new_to_refresh, $to_refresh;
108             }
109 0         0 @to_refresh = @new_to_refresh;
110             }
111              
112 4         142 $class->unload_module($_) for @to_refresh;
113              
114 4 50       12 if (Class::Load::is_class_loaded('Class::MOP')) {
115 0         0 @to_refresh = grep { !$metas_for_immutable{$_} } @to_refresh;
  0         0  
116             }
117              
118 4         141 $class->load_module($_) for @to_refresh;
119             }
120              
121              
122             sub unload_module {
123 4     4 1 9 my $class = shift;
124 4         10 my ($mod) = @_;
125 4         12 $mod = $class->_file_to_mod($mod);
126              
127 4         34 Class::Unload->unload($mod);
128              
129 4 50       445 if (Class::Load::is_class_loaded('Class::MOP')) {
130 0         0 Class::MOP::remove_metaclass_by_name($mod);
131             }
132              
133 4         145 $class->_clear_cache_for($mod);
134             }
135              
136              
137             sub load_module {
138 4     4 1 7 my $class = shift;
139 4         36 my ($mod) = @_;
140 4         14 $mod = $class->_file_to_mod($mod);
141              
142 4         13 my $file = $class->_mod_to_file($mod);
143 4   66     25 my $last_require_failed = exists $INC{$file} && !defined $INC{$file};
144              
145             try {
146 4     4   237 Class::Load::load_class($mod);
147             }
148             catch {
149 1 50   1   339 if ($last_require_failed) {
150             # This file failed to load previously.
151             # Presumably that error has already been caught, so that's fine
152             }
153             else {
154 0         0 die $_;
155             }
156             }
157             finally {
158 4     4   184 $class->_update_cache_for($mod);
159 4         60 };
160             }
161              
162             sub _dependent_modules {
163 4     4   6 my $class = shift;
164 4         5 my ($mod) = @_;
165 4         11 $mod = $class->_file_to_mod($mod);
166              
167 4 50       25 return ($mod) unless Class::Load::is_class_loaded('Class::MOP');
168              
169 0         0 my $meta = Class::MOP::class_of($mod);
170              
171 0 0       0 return ($mod) unless $meta;
172              
173 0 0       0 if ($meta->isa('Class::MOP::Class')) {
    0          
174             # attribute cloning (has '+foo') means that we can't skip refreshing
175             # mutable classes
176             return (
177             # NOTE: this order is important!
178 0         0 $mod,
179 0         0 map { $class->_dependent_modules($_) }
180             ($meta->subclasses,
181             # XXX: metacircularity? what if $class is Class::MOP::Class?
182             ($mod->isa('Class::MOP::Class')
183 0         0 ? (map { $_->name }
184 0 0       0 grep { $_->isa($mod) }
185             Class::MOP::get_all_metaclass_instances())
186             : ())),
187             );
188             }
189             elsif ($meta->isa('Moose::Meta::Role')) {
190             return (
191 0         0 $mod,
192 0         0 map { $class->_dependent_modules($_) } $meta->consumers,
193             );
194             }
195             else {
196 0         0 die "Unknown metaclass: $meta";
197             }
198             }
199              
200             sub _update_cache_for {
201 391     391   479 my $class = shift;
202 391         441 my ($file) = @_;
203 391         724 $file = $class->_mod_to_file($file);
204 391         788 $CACHE{$file} = $class->_mtime($file);
205             }
206              
207             sub _clear_cache_for {
208 4     4   8 my $class = shift;
209 4         10 my ($file) = @_;
210 4         13 $file = $class->_mod_to_file($file);
211              
212 4         18 delete $CACHE{$file};
213             }
214              
215             sub _mtime {
216 920     920   998 my $class = shift;
217 920         1017 my ($file) = @_;
218 920         1820 $file = $class->_mod_to_file($file);
219 920 100       2234 return 1 if !$INC{$file};
220 917         36794 return join ' ', (stat($INC{$file}))[1, 7, 9];
221             }
222              
223             sub _file_to_mod {
224 20     20   28 my $class = shift;
225 20         29 my ($file) = @_;
226              
227 20 100       78 return $file unless $file =~ /\.pm$/;
228              
229 4         8 my $mod = $file;
230 4         23 $mod =~ s{\.pm$}{};
231 4         10 $mod =~ s{/}{::}g;
232              
233 4         36 return $mod;
234             }
235              
236             sub _mod_to_file {
237 1323     1323   1369 my $class = shift;
238 1323         1322 my ($mod) = @_;
239              
240 1323 100       6316 return $mod unless $mod =~ /^\w+(?:::\w+)*$/;
241              
242 16         21 my $file = $mod;
243 16         29 $file =~ s{::}{/}g;
244 16         23 $file .= '.pm';
245              
246 16         45 return $file;
247             }
248              
249              
250             1;
251              
252             __END__