File Coverage

blib/lib/Devel/FIXME.pm
Criterion Covered Total %
statement 97 108 89.8
branch 36 48 75.0
condition 23 33 69.7
subroutine 20 20 100.0
pod 1 8 12.5
total 177 217 81.5


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package Devel::FIXME;
4 4     4   131265 use fields qw/text line file package script time/;
  4         6557  
  4         21  
5              
6 4     4   502 use 5.008_000; # needs open to work on scalar ref
  4         14  
7              
8 4     4   19 use strict;
  4         8  
  4         68  
9 4     4   17 use warnings;
  4         6  
  4         93  
10              
11 4     4   19 use Exporter;
  4         7  
  4         162  
12 4     4   24 use Scalar::Util qw/reftype/;
  4         7  
  4         238  
13 4     4   26 use List::Util qw/first/;
  4         8  
  4         376  
14 4     4   27 use Carp qw/carp croak/;
  4         14  
  4         5927  
15              
16             our @EXPORT_OK = qw/FIXME SHOUT DROP CONT/;
17             our %EXPORT_TAGS = ( "constants" => \@EXPORT_OK );
18              
19             our $VERSION = 0.02;
20              
21             # some constants for rules
22             sub CONT () { 0 };
23             sub SHOUT () { 1 };
24             sub DROP () { 2 };
25              
26             our $REPAIR_INC = undef; # do not "repair" @INC by default
27              
28             my %lock; # to prevent recursion
29             our %rets; # return value cache
30             our $cur; # the current file, used in an eval
31             our $err; # the current error, for rethrowal
32             our $inited; # whether the code ref was installed in @INC, and all
33              
34 4     4   25 { my $anon = ''; open my $fh, "<", \$anon or die $!; close $fh; } # otherwise perlio require stuff breaks
  4         33  
  4         34  
35              
36             sub init {
37 7     7 0 13 my $pkg = shift;
38 7 100       28 unless($inited){
39 4         86 $pkg->readfile($_) for ($0, sort grep { $_ ne __FILE__ } (values %INC)); # readfile on everything loaded, but not us (we don't want to match our own docs)
  501         1135  
40 4         265 $pkg->install_inc;
41             }
42              
43 7         19 $inited = 1;
44             }
45              
46             our $carprec = 0;
47              
48             sub install_inc {
49 4     4 0 18 my $pkg = shift;
50            
51             unshift @INC, sub { # YUCK! but tying %INC didn't work, and source filters are applied per caller. XS for source filter purposes is yucki/er/
52 6     6   1282 my $self = shift;
53 6         14 my $file = shift;
54            
55 6 100       1535 return undef if $lock{$file}; # if we're already processing the file, then we're in the eval several lines down. return.
56 3         9 local $lock{$file} = 1; # set this lock that prevents recursion
57              
58 3 50 33     25 unless (ref $INC[0] and $INC[0] == $self){ # if this happens, some stuff won't be filtered. It shouldn't happen often though.
59 0 0       0 local @INC = grep { !ref or $_ != $self } @INC; # make sure we don't recurse when carp loads it's various innards, it causes a mess
  0         0  
60 0 0       0 carp "FIXME's magic sub is no longer first in \@INC" . ($REPAIR_INC ? ", repairing" : "");
61 0 0       0 if ($REPAIR_INC){
62 0         0 my $i = 0;
63 0         0 while ($i < @INC) {
64 0 0       0 ref $INC[$i] or next;
65 0 0       0 if ($INC[$i] == $self) {
66 0         0 unshift @INC, splice(@INC, $i, 1);
67 0         0 last;
68             }
69             } continue {
70 0         0 $i++;
71             }
72             }
73             }
74              
75             # create some perl code that gives back the return value of the original package, and thus looks like you're really requiring the same thing
76 3         15 my $buffer = "\${ delete \$Devel::FIXME::rets{q{$file}} };"; # return what the last module returned. I don't know why it doesn't work without refs
77             # really load the file
78 3         7 local $cur = $file;
79 3         202 my $ret = eval 'require $Devel::FIXME::cur'; # require always evaluates the return from an evalfile in scalar context, so we don't need to worry about list
80              
81 3         3109 ($err = "$@\n") =~ s/\nCompilation failed in require at \(eval \d+\)(?:\[.*?\])? line 1\.\n//s; # trim off the eval's appendix to the error
82 3 100       15 $buffer = 'die $Devel::FIXME::err' if $@; # rethrow this way, so that base.pm shuts up
83            
84             # save the return value so that the original require can have it
85 3         11 $rets{$file} = \$ret; # see above for why it's a ref
86              
87             # look for FIXME comments in the file that was really required
88 3 100       32 $pkg->readfile($INC{$file}) if ($INC{$file});
89              
90             # return a filehandle containing source code that simply returns the value the real file did
91 3         53 open my $fh, "<", \$buffer;
92 3         349 $fh;
93 4         49 };
94             }
95              
96             sub regex {
97 186887     186887 1 622319 qr/#\s*(?:FIXME|XXX)\s+(.*)$/; # match a FIXME or an XXX, in a comment, with some lax whitespace rules, and suck in anything afterwords as the text
98             }
99              
100             sub readfile { # FIXME refactor to something classier
101 372     372 0 1813 my $pkg = shift;
102 372         875 my $file = shift;
103              
104 372 100       7014 return unless -f $file;
105              
106 371 50       12182 open my $src, "<", $file or die "couldn't open $file: $!";
107 371         1043 local $_;
108              
109 371         5784 while(<$src>){
110 186887 100       335375 $pkg->FIXME( # if the line matches the fixme, generate a fixme
111             text => "$1",
112             line => $., # the current line number for <$src>
113             file => $file,
114             ) if $_ =~ $pkg->regex;
115 186887 100       624488 } continue { last if eof $src }; # is this a platform bug on OSX?
116 371         5566 close $src;
117             }
118              
119             sub eval { # evaluates all the rules on a fixme object
120 41     41 0 100 my __PACKAGE__ $self = shift;
121              
122 41 100       278 foreach my $rule ($self->can("rules") ? $self->rules : ()){
123              
124 33         292 my $action = &$rule($self); # run the rule as a class method, and get back a return value
125              
126 33 100       214 if ($action == SHOUT){ # if the rule said to shout, we shout and stop
    100          
127 2         8 return $self->shout;
128             } elsif ($action == DROP){ # if the rule says to drop, we stop
129 29         85 return undef;
130             } # otherwise we keep looping through the rules
131             }
132              
133 10         59 $self->shout; # and shout if there are no more rules left.
134             }
135              
136             sub shout { # generate a pretty string and send it to STDERR
137 1     1 0 1 my __PACKAGE__ $self = shift;
138 1         14 warn("# FIXME: $self->{text} at $self->{file} line $self->{line}.\n");
139             }
140              
141             sub new { # an object per FIXME statement
142 53     53 0 586 my $pkg = shift;
143              
144 53         84 my %args;
145            
146 53 100       192 if (@_ == 1){ # if we only have one arg
    100          
147 20 100 66     87 if (ref $_[0] and reftype($_[0]) eq 'HASH'){ # and it's a hash ref, then we take the hashref to be our args
148 10         20 %args = %{ $_[0] };
  10         54  
149             } else { # if it's one arg and not a hashref, then it's our text
150 10         31 %args = ( text => $_[0] );
151             }
152             } elsif (@_ % 2 == 0){ # if there's an even number of arguments, they are key value pairs
153 32         178 %args = @_;
154             } else { # if the argument list is anything else we complain
155 1         22 croak "Invalid arguments";
156             }
157            
158            
159 52         242 my __PACKAGE__ $self = $pkg->fields::new();
160 52         15351 %$self = %args;
161              
162             # fill in some defaults
163 52   66     342 $self->{package} ||= (caller(1))[0];
164 52   66     1103 $self->{file} ||= (caller(1))[1];
165 52   66     320 $self->{line} ||= (caller(1))[2];
166              
167             # these are mainly for rules
168 52   66     447 $self->{script} ||= $0;
169 52   66     1477 $self->{time} ||= localtime;
170              
171 52         308 $self;
172             }
173              
174             sub import { # export \&FIXME to our caller, /and/ generate a message if there is one to generate
175 10     10   8220 my $pkg = $_[0];
176 10 100       44 $pkg->init unless @_ > 1;
177 10 100 100 17   89 if (@_ == 1 or @_ > 2 or (@_ == 2 and first { $_[1] eq $_ or $_[1] eq "&$_" } @EXPORT_OK, map { ":$_" } keys %EXPORT_TAGS)){
  17 100 66     64  
  4   100     23  
178 8         18 shift;
179 8         17 local $Exporter::ExportLevel = $Exporter::ExportLevel + 1;
180 8         399 $pkg->Exporter::import(@_);
181             } else {
182 2         6 $pkg->init;
183 2         8 goto \&FIXME;
184             }
185             }
186              
187             sub FIXME { # generate a method
188 41     41 0 639 my $pkg = __PACKAGE__;
189 41 100 66     446 $pkg = shift if UNIVERSAL::can($_[0],"isa") and $_[0]->isa(__PACKAGE__); # it's a method or function, we don't care
190 41         134 $pkg->new(@_)->eval;
191             }
192             *msg = \&FIXME; # booya.
193              
194             __PACKAGE__
195              
196             __END__