File Coverage

blib/lib/exact.pm
Criterion Covered Total %
statement 178 189 94.1
branch 48 72 66.6
condition 16 33 48.4
subroutine 34 36 94.4
pod 8 8 100.0
total 284 338 84.0


line stmt bran cond sub pod time code
1             package exact;
2             # ABSTRACT: Perl pseudo pragma to enable strict, warnings, features, mro, filehandle methods
3              
4 11     11   2038933 use 5.014;
  11         39  
5 11     11   85 use strict;
  11         20  
  11         301  
6 11     11   46 use warnings;
  11         17  
  11         530  
7 11     11   5230 use namespace::autoclean;
  11         218063  
  11         41  
8 11     11   787 use B::Deparse;
  11         22  
  11         439  
9 11     11   5172 use Import::Into;
  11         6194  
  11         449  
10 11     11   85 use Sub::Util 'set_subname';
  11         23  
  11         660  
11 11     11   5394 use Syntax::Keyword::Try;
  11         29600  
  11         94  
12              
13             our $VERSION = '1.29'; # VERSION
14              
15 11     11   1356 use feature ();
  11         62  
  11         282  
16 11     11   732 use utf8 ();
  11         506  
  11         228  
17 11     11   569 use mro ();
  11         887  
  11         284  
18 11     11   48 use Carp qw( croak carp confess cluck );
  11         18  
  11         678  
19 11     11   5606 use IO::File ();
  11         135675  
  11         444  
20 11     11   87 use IO::Handle ();
  11         39  
  11         1357  
21 11     11   68 use Try::Tiny ();
  11         27  
  11         197  
22 11     11   5292 use PerlX::Maybe ();
  11         36166  
  11         17073  
23              
24             my ($perl_version) = $^V =~ /^v5\.(\d+)/;
25              
26             my $features_available = ( %feature::feature_bundle and $feature::feature_bundle{all} )
27             ? $feature::feature_bundle{all}
28             : [ qw( say state switch unicode_strings ) ];
29              
30             my $functions_available = [ qw(
31             nostrict nowarnings
32             nofeatures nobundle noskipexperimentalwarnings
33             noutf8 noc3 nocarp notry trytiny nomaybe noautoclean
34             ) ];
35              
36             my $functions_deprecated = ['noexperiments'];
37              
38             my ( $no_parent, $late_parent );
39              
40             sub import {
41 18     18   4551 my ( $self, $caller ) = ( shift, caller() );
42              
43 18         43 my ( @features, @nofeatures, @functions, @bundles, @classes );
44 18         48 for (@_) {
45 6         16 ( my $opt = $_ ) =~ s/^\-//;
46              
47 6 50 66     34 if ( $opt eq 'class' ) {
    50          
    50          
    100          
    50          
    100          
    100          
48 0         0 push( @classes, $opt );
49             }
50             elsif ( $opt eq 'cor' ) {
51 0         0 push( @features, 'class' );
52             }
53             elsif ( $opt eq 'nocor' ) {
54 0         0 push( @nofeatures, 'class' );
55             }
56 156         246 elsif ( grep { $_ eq $opt } @$features_available ) {
57 1         4 push( @features, $opt );
58             }
59 130         245 elsif ( my ($nofeature) = grep { 'no' . $_ eq $opt } @$features_available ) {
60 0         0 push( @nofeatures, $nofeature );
61             }
62 65         124 elsif ( grep { $_ eq $opt } @$functions_available, @$functions_deprecated ) {
63 2 50       3 push( @functions, $opt ) if ( grep { $_ eq $opt } @$functions_available );
  24         101  
64             }
65             elsif ( $opt =~ /^:?v?5?\.?(\d+)/ and $1 >= 10 ) {
66 1         6 push( @bundles, $1 );
67             }
68             else {
69 2 50       10 push( @classes, $opt ) if ( $opt !~ /^no[a-z]{2}/ );
70             }
71             }
72              
73 18 50       228 strict ->import unless ( grep { $_ eq 'nostrict' } @functions );
  2         21  
74 18 50       369 warnings->import unless ( grep { $_ eq 'nowarnings' } @functions );
  2         50  
75              
76 18 100 66     168 if (@bundles) {
    100          
77 1         174 feature->import( ':5.' . $_ ) for (@bundles);
78             }
79             elsif (
80             not grep { $_ eq 'nofeatures' } @functions and
81             not grep { $_ eq 'nobundle' } @functions
82             ) {
83 16 50       2101 feature->import( $perl_version >= 16 ? ':all' : ':5.' . $perl_version );
84             }
85 18         190 feature->import($_) for (@features);
86 18         32 feature->unimport($_) for (@nofeatures);
87              
88 18 50       232 unless ( grep { $_ eq 'noutf8' } @functions ) {
  2         9  
89 18         196 utf8->import;
90 18         280 binmode( $_, ':utf8' ) for ( *STDIN, *STDERR, *STDOUT );
91 18         118 'open'->import::into( $caller, ':std', ':utf8' );
92             }
93              
94 18 50       30593 mro::set_mro( $caller, 'c3' ) unless ( grep { $_ eq 'noc3' } @functions );
  2         18  
95              
96             monkey_patch( $self, $caller,
97 72         97 ( map { $_ => \&{ 'Carp::' . $_ } } qw( croak carp confess cluck ) ),
  72         232  
98 36         55 ( map { $_ => \&{$_} } qw( deat deattry ) ),
  36         116  
99 18 50       50 ) unless ( grep { $_ eq 'nocarp' } @functions );
  2         8  
100              
101             feature->unimport('try') if (
102 468         857 grep { $_ eq 'try' } @$features_available and
103             (
104             grep { $_ eq 'notry' } @functions or
105 18 50 33     52 grep { $_ eq 'trytiny' } @functions
      33        
106             )
107             );
108             Syntax::Keyword::Try->import_into($caller) if (
109             $perl_version < 36 and
110 0         0 not grep { $_ eq 'notry' } @functions and
111 18 0 33     64 not grep { $_ eq 'trytiny' } @functions
  0   33     0  
112             );
113 18 50       41 Try::Tiny->import::into($caller) if ( grep { $_ eq 'trytiny' } @functions );
  2         6  
114              
115 72         117 monkey_patch( $self, $caller, ( map { $_ => \&{ 'PerlX::Maybe::' . $_ } } qw(
  72         226  
116             maybe provided provided_deref provided_deref_with_maybe
117 18 50       52 ) ) ) unless ( grep { $_ eq 'nomaybe' } @functions );
  2         8  
118              
119 18         36 my @late_parents = ();
120             my $use = sub {
121 3     3   9 my ( $class, $pm, $caller, $params ) = @_;
122              
123 3         5 my $failed_require;
124             try {
125             require "$pm" unless ( do {
126 11     11   94 no strict 'refs';
  11         21  
  11         557  
127 11     11   59 no warnings 'once';
  11         32  
  11         2711  
128             ${"${caller}::INC"}{$pm};
129             } );
130             }
131 3         7 catch ($e) {
132             croak($e) unless ( index( $e, qq{Can't locate $pm in } ) == 0 );
133             return 0;
134             }
135              
136 2         4 ( $no_parent, $late_parent ) = ( undef, undef );
137              
138 2         4 my $is_exact_extension = 0;
139             {
140 11     11   77 no strict 'refs';
  11         15  
  11         9682  
  2         30  
141 2         4 $is_exact_extension = grep { index( $_, 'exact::' ) == 0 } $class, @{"${class}::ISA"};
  2         6  
  2         14  
142             }
143 2 100 66     13 $class->import( $params, $caller ) if ( $is_exact_extension and $class->can('import') );
144              
145 2 50 66     22 if ($late_parent) {
    100          
146 0         0 push( @late_parents, [ $class, $caller ] );
147             }
148             elsif ( not $no_parent and index( $class, 'exact::' ) != 0 ) {
149 1         4 $self->add_isa( $class, $caller );
150             }
151              
152 2         10 return 1;
153 18         98 };
154 18         58 for my $class (@classes) {
155 2 50       11 my $params = ( $class =~ s/\(([^\)]+)\)// ) ? $1 : undef;
156 2         8 ( my $pm = $class ) =~ s{::|'}{/}g;
157 2         4 $pm .= '.pm';
158              
159 2 50 66     9 $use->( 'exact::' . $class, 'exact/' . $pm, $caller, $params ) or
160             $use->( $class, $pm, $caller, $params ) or
161             croak(
162             "Can't locate exact/$pm or $pm in \@INC " .
163             "(you may need to install the exact::$class or $class module)" .
164             '(@INC contains: ' . join( ' ', @INC ) . ')'
165             );
166             }
167 18         35 $self->add_isa(@$_) for @late_parents;
168              
169             warnings->unimport('experimental')
170 18 50 33     401 unless ( $perl_version < 18 or grep { $_ eq 'noskipexperimentalwarnings' } @functions );
  2         50  
171              
172 18 100       121 namespace::autoclean->import( -cleanee => $caller ) unless ( grep { $_ eq 'noautoclean' } @functions );
  2         1680  
173             }
174              
175             sub monkey_patch {
176 41     41 1 236 my ( $self, $class, %patch ) = @_;
177             {
178 11     11   122 no strict 'refs';
  11         20  
  11         629  
  41         59  
179 11     11   67 no warnings 'redefine';
  11         18  
  11         2540  
180 41         462 *{"${class}::$_"} = set_subname( "${class}::$_", $patch{$_} ) for ( keys %patch );
  186         1093  
181             }
182 41         130 return;
183             }
184              
185             sub add_isa {
186 3     3 1 219541 my ( $self, $parent, $child ) = @_;
187             {
188 11     11   71 no strict 'refs';
  11         19  
  11         13944  
  3         5  
189 3 100       31 push( @{"${child}::ISA"}, $parent ) unless ( $child->isa($parent) );
  2         22  
190             }
191 3         8 return;
192             }
193              
194             sub no_parent {
195 0     0 1 0 $no_parent = 1;
196 0         0 return;
197             }
198              
199             sub late_parent {
200 0     0 1 0 $late_parent = 1;
201 0         0 return;
202             }
203              
204             sub _patch_import {
205 2     2   10 my ( $type, $self, @names ) = @_;
206              
207 2         43 my $target = ( caller(1) )[0];
208 2         29 my $original_import = $target->can('import');
209              
210 2         4 my %groups;
211 2 100       10 if ( $type eq 'provide' ) {
212 1         3 %groups = map { %$_ } grep { ref $_ eq 'HASH' } @names;
  1         5  
  3         7  
213 1         24 @names = grep { not ref $_ } @names;
  3         8  
214             }
215              
216             monkey_patch(
217             $self,
218             $target,
219             import => sub {
220 3     3   24 my ( $package, @exports ) = @_;
        3      
        3      
221              
222 3 50 33     22 if ( $original_import and ref $original_import eq 'CODE' ) {
223 3         2934 ( my $b_deparsed_sub = B::Deparse->new->coderef2text($original_import) ) =~ s/;//g;
224 3 100       61 $original_import->(@_) if ($b_deparsed_sub);
225             }
226              
227 3 100       22 if ( $type eq 'force' ) {
    50          
228 1         4 @exports = @names;
229             }
230             elsif ( $type eq 'provide' ) {
231 3         10 @exports = grep { defined } map {
232 2         5 my $name = $_;
  2         4  
233              
234 4         19 ( grep { $name eq $_ } @names ) ? $name :
235 2 50       5 ( exists $groups{$name} ) ? ( @{ $groups{$name} } ) : undef;
  1 100       5  
236             } @exports;
237             }
238              
239             monkey_patch(
240             $package,
241             ( caller(0) )[0],
242 3         55 map { $_ => \&{ $package . '::' . $_ } } @exports
  4         10  
  4         28  
243             );
244              
245 3         22 return;
246             },
247 2         18 );
248             }
249              
250             sub export {
251 1     1 1 212936 _patch_import( 'force', @_ );
252 1         4 return;
253             }
254              
255             sub exportable {
256 1     1 1 11310 _patch_import( 'provide', @_ );
257 1         2 return;
258             }
259              
260             sub deat ($) {
261 2     2 1 336258 ( my $e = reverse $_[0] ) =~ s/^\s*\.\d+\s+enil\s+.*?\s+ta\s+//;
262 2         45 return '' . reverse $e;
263             }
264              
265             sub deattry (&) {
266             try {
267             return $_[0]->();
268             }
269 2     2 1 1159 catch ($e) {
270             die deat $e, "\n";
271             }
272             }
273              
274             1;
275              
276             __END__