File Coverage

lib/Test/Stream/Plugin/Hide.pm
Criterion Covered Total %
statement 90 90 100.0
branch 22 24 91.6
condition 4 4 100.0
subroutine 20 20 100.0
pod 3 4 75.0
total 139 142 97.8


line stmt bran cond sub pod time code
1             package Test::Stream::Plugin::Hide;
2 4     4   754917 use strict;
  4         10  
  4         96  
3 4     4   20 use warnings;
  4         7  
  4         103  
4              
5 4     4   20 use Test::Stream::Util qw/try/;
  4         11  
  4         25  
6              
7             our $VERSION = '0.000001';
8              
9             my %HIDE;
10              
11             # For testing
12             {
13 4     4   356 no warnings 'once';
  4         6  
  4         1070  
14 1     1   12 *HIDE = sub { \%HIDE } if $Test::Stream::Plugin::Hide::TESTING;
15             }
16              
17             # regexp for valid module name. Lifted from Module::Runtime and
18             # UNIVERSAL::require (modified to have anchors)
19             my $module_name_rx = qr/^[A-Z_a-z][0-9A-Z_a-z]*(?:::[0-9A-Z_a-z]+)*$/;
20              
21             # Subs we cannot prefix with underscore, but do not want to export.
22             my %BAD = (
23             import => 1,
24             unimport => 1,
25             load_ts_plugin => 1,
26             HIDE => 1,
27             );
28              
29             sub load_ts_plugin {
30 14     14 0 2826 my $class = shift;
31 14         97 my ($caller, @hide) = @_;
32              
33 14         42 my @real_hide;
34 14         26 for my $hide (@hide) {
35 16 100       73 if ($hide =~ m/^-([^_].*)$/) {
36 11         28 my $name = $1;
37              
38 11 100       26 if ($name eq 'all') {
39 3         7 my $stash = \%Test::Stream::Plugin::Hide::;
40 3         15 for my $name (keys %$stash) {
41 52 100       113 next if $BAD{$name};
42 42 100       97 next if $name =~ m/^_/;
43 24   100     194 my $sub = $class->can($name) || next;
44              
45 4     4   20 no strict 'refs';
  4         8  
  4         432  
46 18         22 *{$caller->[0] . "::$name"} = $sub;
  18         92  
47             }
48             }
49             else {
50 8         28 my $error = "$class does not export $name() at $caller->[1] line $caller->[2]\n";
51 8 100       42 die $error if $BAD{$name};
52 4   100     39 my $sub = $class->can($name) || die $error;
53              
54 4     4   19 no strict 'refs';
  4         9  
  4         3286  
55 2         2 *{$caller->[0] . "::$name"} = $sub;
  2         14  
56             }
57             }
58             else {
59 5         11 push @real_hide => $hide;
60             }
61             }
62 8         30 _hide($caller, @real_hide);
63             }
64              
65             sub import {
66 8     8   5686 my $class = shift;
67 8         28 my @caller = caller;
68 8         30 $class->load_ts_plugin(\@caller, @_);
69             }
70              
71             sub unimport {
72 2     2   875 my $class = shift;
73 2         6 unhide(@_);
74             }
75              
76             sub unhide {
77 5     5 1 1757 my @caller = caller;
78 5         13 _unhide(\@caller, map {_mod_to_file($_, \@caller) => 1} @_);
  5         16  
79             }
80              
81             sub hide {
82 6     6 1 1543 my @caller = caller;
83 6         16 _hide(\@caller, @_);
84             }
85              
86             sub do_hidden(&;@) {
87 3     3 1 1444 my ($code, @mods) = @_;
88              
89             # Copy %HIDE
90 3         10 my %orig = %HIDE;
91 3         15 my @caller = caller;
92              
93             my ($ok, $error) = try {
94 3     3   61 _hide(\@caller, @mods);
95 3         9 $code->();
96 3         20 };
97              
98             # Unhide the new keys
99 3         885 _unhide(\@caller, grep {!$orig{$_}} keys %HIDE);
  7         23  
100              
101             # Die if we failed, otherwise just return.
102 3 100       16 die $error unless $ok;
103 2         8 return;
104             }
105              
106             sub _mod_to_file {
107 23     23   758 my ($mod, $caller) = @_;
108              
109 23 100       188 die "'$mod' does not appear to be a valid module at $caller->[1] line $caller->[2]\n"
110             unless $mod =~ $module_name_rx;
111              
112 21         33 my $file = $mod;
113 21         63 $file =~ s{::}{/}g;
114 21         37 $file .= '.pm';
115 21         64 return $file;
116             }
117              
118             sub _hide {
119 17     17   29 my $caller = shift;
120 17         31 for my $mod (@_) {
121 13         30 my $file = _mod_to_file($mod, $caller);
122 13         30 $HIDE{$file} = 1;
123 13 100       59 next unless $INC{$file};
124 2         19 die "Module '$mod' was already loaded from '$INC{$file}' before hide was requested at $caller->[1] line $caller->[2]\n";
125             }
126 15         32 _munge_inc();
127             }
128              
129             sub _unhide {
130 8     8   12 my $caller = shift;
131 8         32 for my $file (@_) {
132 15         28 delete $HIDE{$file};
133 15 50       51 delete $INC{$file} unless defined $INC{$file};
134             }
135 8         18 _munge_inc();
136             }
137              
138 58 100   58   133 sub _munge_inc { @INC = (\&_hook, grep { !ref($_) || $_ != \&_hook } @INC); 1 }
  696         2213  
  58         2133  
139              
140             sub _hook {
141 35     35   551836 my ($this, $file) = @_;
142 35         86 _munge_inc(); # Try to keep the hook in front.
143 35 100       19941 return unless $HIDE{$file};
144              
145 5         12 my $error = "die q|Can't locate $file in \@INC (Hidden by request)\n|;\n";
146 2 50   2   20 open(my $handle, '<', \$error) || die $!;
  2         19  
  2         10  
  5         98  
147 5         1655 return $handle;
148             }
149              
150             1;
151              
152             __END__