File Coverage

blib/lib/Module/Case.pm
Criterion Covered Total %
statement 15 34 44.1
branch 1 10 10.0
condition 1 9 11.1
subroutine 5 6 83.3
pod n/a
total 22 59 37.2


line stmt bran cond sub pod time code
1             package Module::Case;
2              
3 1     1   149901 use 5.008000;
  1         4  
4 1     1   12 use strict;
  1         2  
  1         80  
5 1     1   6 use warnings;
  1         2  
  1         1012  
6              
7             our $VERSION = '0.05';
8              
9             our $sensitive_modules;
10              
11             our $inc_sniffer = sub {
12             # Special @INC hook to only load modules with exact case match.
13             # This is particularly useful on case insensitive file systems.
14             my ($self, $filename) = @_;
15             # Ignore full path filenames
16             return undef if $filename =~ m{^/};
17             # Calculate package name
18             my $pkg = $filename;
19             $pkg =~ s/\.pm$//;
20             $pkg =~ s{/+}{::}g;
21             # For efficiency purposes, skip module unless it's one of the special case sensitive packages flagged to load case-sensitively.
22             $sensitive_modules->{$pkg} or $sensitive_modules->{'-all'} or return undef;
23              
24             # Skip the directories before me since they've already been tried (and obviously didn't find the file already or else we wouldn't be here)
25             my $keep = 0;
26             # Only look through regular directories after myself but ignore CODEREFs (such as myself) in @INC
27             my @scan = grep { $keep = 1 if $_ eq $self; !ref $_ and $keep; } @INC;
28             # Now that @scan has been built, it's safe to disable $pkg from the list.
29             __PACKAGE__->unimport($pkg);
30             my $found_wrong_case = 0;
31             foreach my $dir (@scan) {
32             if (open my $fh, "<", "$dir/$filename") {
33             # Found a matching file but might not have same case.
34             # Take a quick peek to make sure the case matches too.
35             my $contents = join "", <$fh>;
36             if ($contents =~ /^\s*package\s+\Q$pkg\E\s*;/m) {
37             # Smells like a pretty good package.
38             # Case matches case exactly.
39             # So rewind and return this handle.
40             seek($fh, 0, 0);
41             $INC{$filename} = "$dir/$filename";
42             return $fh;
43             }
44             else {
45             # Looks like we found a file with the wrong case, so ignore it.
46             $found_wrong_case ||= "$dir/$filename";
47             }
48             close $fh;
49             }
50             }
51             # Couldn't find the real module
52             if ($found_wrong_case) {
53             # Found a case insensitive match but did NOT find an exact match.
54             # We need to block Perl from continuing along @INC or else it will find the bad guy too.
55             my $error = "Can't locate $filename in \@INC except for decoy with wrong case [$found_wrong_case]";
56             if (eval { require Carp }) {
57             Carp::croak($error);
58             }
59             else {
60             die "$error\n";
61             }
62             }
63             # Can't even find a case-insensitive match, so just continue and let Perl try
64             return undef;
65             };
66              
67             our $sub_import = sub {
68 1     1   12 my $class = shift;
69 1         3 foreach (@_) {
70             # Autovivify $sensitive_modules only as needed
71 0         0 $sensitive_modules->{$_} = $_;
72             }
73 1 50 33     6 if (keys %$sensitive_modules and !grep { $_ eq $inc_sniffer } @INC) {
  0         0  
74             # Only inject the sniffer if there is something to smell
75             # and only if it's not already in the list.
76             # Search for the first regular directory (non-ref string) @INC setting
77             # Then jam the $inc_sniffer right before it.
78 0         0 for (my $i = 0; $i < @INC; $i++) {
79 0 0       0 if (!ref $INC[$i]) {
80 0         0 splice @INC, $i, 0, $inc_sniffer;
81 0         0 last;
82             }
83             }
84             }
85 1         15 return;
86             };
87              
88             my $sub_unimport = sub {
89 0     0     my $class = shift;
90 0           my $wiped = undef;
91 0 0         if ($sensitive_modules) {
92 0 0         if (@_) {
93             # Pick out module(s) to quit case sniffing for
94 0           foreach (@_) {
95 0   0       $wiped ||= delete $sensitive_modules->{$_};
96             }
97             }
98             else {
99             # No specific module provided, so just wipe everything
100 0           ($wiped) = keys %$sensitive_modules;
101 0           $sensitive_modules = undef;
102             }
103             }
104 0 0 0       if (!$sensitive_modules or !keys %$sensitive_modules) {
105             # No module case-sensitive modules left, so restore @INC without the sniffer
106 0           $sensitive_modules = undef;
107 0           @INC = grep { $_ ne $inc_sniffer } @INC;
  0            
108             }
109             # Return one of the modules that got wiped, if any.
110 0           return $wiped;
111             };
112              
113             {
114 1     1   9 no strict 'refs';
  1         4  
  1         412  
115             my $should_pkg = __PACKAGE__;
116             defined &{"$should_pkg\::import"} or *{"$should_pkg\::import"} = $sub_import;
117             defined &{"$should_pkg\::unimport"} or *{"$should_pkg\::unimport"} = $sub_unimport;
118             (my $should_file = "$should_pkg.pm") =~ s%::%/%g;
119             if (__FILE__ !~ m{\b\Q$should_file\E$} and
120             __FILE__ =~ m{\b(\Q$should_file\E)$}i) {
121             my $fake_file = $1;
122             my $fake_pkg = $fake_file;
123             $fake_pkg =~ s%/+%::%g;
124             $fake_pkg =~ s/\.pm//;
125             #if (eval { require Carp }) { Carp::carp("Case-ignorant filesystem exploited by loading module wrongly: $fake_pkg"); }
126             defined &{"$fake_pkg\::import"} or *{"$fake_pkg\::import"} = *{"$should_pkg\::import"};
127             defined &{"$fake_pkg\::unimport"} or *{"$fake_pkg\::unimport"} = *{"$should_pkg\::unimport"};
128             # Fake Preload Real Module
129             $INC{$should_file} = __FILE__;
130             }
131             }
132              
133             1;
134              
135             __END__