File Coverage

blib/lib/Devel/FIXME.pm
Criterion Covered Total %
statement 97 109 88.9
branch 32 48 66.6
condition 22 33 66.6
subroutine 20 20 100.0
pod 1 8 12.5
total 172 218 78.9


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package Devel::FIXME;
4 3     3   31265 use fields qw/text line file package script time/;
  3         5068  
  3         19  
5              
6 3     3   527 use 5.008_000; # needs open to work on scalar ref
  3         11  
  3         151  
7              
8 3     3   28 use strict;
  3         5  
  3         118  
9 3     3   16 use warnings;
  3         6  
  3         93  
10              
11 3     3   53 use Exporter;
  3         4  
  3         209  
12 3     3   21 use Scalar::Util qw/reftype/;
  3         4  
  3         299  
13 3     3   17 use List::Util qw/first/;
  3         6  
  3         362  
14 3     3   18 use Carp qw/carp croak/;
  3         5  
  3         5471  
15              
16             our @EXPORT_OK = qw/FIXME SHOUT DROP CONT/;
17             our %EXPORT_TAGS = ( "constants" => \@EXPORT_OK );
18              
19             our $VERSION = 0.01;
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 3     3   23 { my $anon = ''; open my $fh, "<", \$anon or die $!; close $fh; } # otherwise perlio require stuff breaks
  3         5  
  3         28  
35              
36             sub init {
37 6     6 0 13 my $pkg = shift;
38 6 100       27 unless($inited){
39 3         58 $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)
  226         637  
40 3         417 $pkg->install_inc;
41             }
42              
43 6         19 $inited = 1;
44             }
45              
46             our $carprec = 0;
47              
48             sub install_inc {
49 3     3 0 10 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 12     12   1754 my $self = shift;
53 12         20 my $file = shift;
54            
55 12 100       7056 return undef if $lock{$file}; # if we're already processing the file, then we're in the eval several lines down. return.
56 6         20 local $lock{$file} = 1; # set this lock that prevents recursion
57              
58 6 50 33     53 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 6         18 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 6         13 local $cur = $file;
79 6         459 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 6         5999 ($err = "$@\n") =~ s/\nCompilation failed in require at \(eval \d+\)(?:\[.*?\])? line 1\.\n//s; # trim off the eval's appendix to the error
82 6 50       23 $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 6         21 $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 6 50       59 $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 6         82 open my $fh, "<", \$buffer;
92 6         209 $fh;
93 3         45 };
94             }
95              
96             sub regex {
97 81999     81999 1 423608 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 152     152 0 362 my $pkg = shift;
102 152         314 my $file = shift;
103              
104 152 50       7176 return unless -f $file;
105              
106 152 50       7759 open my $src, "<", $file or die "couldn't open $file: $!";
107 152         280 local $_;
108              
109 152         2268 while(<$src>){
110 81999 100       187894 $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 81999 100       397317 } continue { last if eof $src }; # is this a platform bug on OSX?
116 152         3214 close $src;
117             }
118              
119             sub eval { # evaluates all the rules on a fixme object
120 25     25 0 49 my __PACKAGE__ $self = shift;
121              
122 25 100       220 foreach my $rule ($self->can("rules") ? $self->rules : ()){
123              
124 17         144 my $action = &$rule($self); # run the rule as a class method, and get back a return value
125              
126 17 100       149 if ($action == SHOUT){ # if the rule said to shout, we shout and stop
    100          
127 2         9 return $self->shout;
128             } elsif ($action == DROP){ # if the rule says to drop, we stop
129 13         51 return undef;
130             } # otherwise we keep looping through the rules
131             }
132              
133 10         52 $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 2 my __PACKAGE__ $self = shift;
138 1         19 warn("# FIXME: $self->{text} at $self->{file} line $self->{line}.\n");
139             }
140              
141             sub new { # an object per FIXME statement
142 36     36 0 112 my $pkg = shift;
143              
144 36         57 my %args;
145            
146 36 100       4309 if (@_ == 1){ # if we only have one arg
    50          
147 20 100 66     286 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         13 %args = %{ $_[0] };
  10         68  
149             } else { # if it's one arg and not a hashref, then it's our text
150 10         41 %args = ( text => $_[0] );
151             }
152             } elsif (@_ % 2 == 0){ # if there's an even number of arguments, they are key value pairs
153 16         121 %args = @_;
154             } else { # if the argument list is anything else we complain
155 0         0 croak "Invalid arguments";
156             }
157            
158            
159 36         464 my __PACKAGE__ $self = $pkg->fields::new();
160 36         13732 %$self = %args;
161              
162             # fill in some defaults
163 36   66     337 $self->{package} ||= (caller(1))[0];
164 36   66     1102 $self->{file} ||= (caller(1))[1];
165 36   66     333 $self->{line} ||= (caller(1))[2];
166              
167             # these are mainly for rules
168 36   66     255 $self->{script} ||= $0;
169 36   66     1526 $self->{time} ||= localtime;
170              
171 36         245 $self;
172             }
173              
174             sub import { # export \&FIXME to our caller, /and/ generate a message if there is one to generate
175 9     9   11242 my $pkg = $_[0];
176 9 100       53 $pkg->init unless @_ > 1;
177 9 100 100 17   138 if (@_ == 1 or @_ > 2 or (@_ == 2 and first { $_[1] eq $_ or $_[1] eq "&$_" } @EXPORT_OK, map { ":$_" } keys %EXPORT_TAGS)){
  17 100 66     107  
  4   66     35  
178 7         9 shift;
179 7         21 local $Exporter::ExportLevel = $Exporter::ExportLevel + 1;
180 7         423 $pkg->Exporter::import(@_);
181             } else {
182 2         9 $pkg->init;
183 2         11 goto \&FIXME;
184             }
185             }
186              
187             sub FIXME { # generate a method
188 25     25 0 828 my $pkg = __PACKAGE__;
189 25 100 66     971 $pkg = shift if UNIVERSAL::can($_[0],"isa") and $_[0]->isa(__PACKAGE__); # it's a method or function, we don't care
190 25         115 $pkg->new(@_)->eval;
191             }
192             *msg = \&FIXME; # booya.
193              
194             __PACKAGE__
195              
196             __END__