File Coverage

blib/lib/exact.pm
Criterion Covered Total %
statement 183 194 94.3
branch 50 74 67.5
condition 17 36 47.2
subroutine 35 37 94.5
pod 8 8 100.0
total 293 349 83.9


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 13     13   2406518 use 5.014;
  13         69  
5 13     13   96 use strict;
  13         22  
  13         363  
6 13     13   56 use warnings;
  13         17  
  13         602  
7 13     13   5879 use namespace::autoclean;
  13         258626  
  13         68  
8 13     13   1142 use B::Deparse;
  13         29  
  13         603  
9 13     13   6630 use Import::Into;
  13         9456  
  13         503  
10 13     13   101 use Sub::Util 'set_subname';
  13         26  
  13         1045  
11 13     13   6794 use Syntax::Keyword::Defer;
  13         36330  
  13         103  
12 13     13   8093 use Syntax::Keyword::Try;
  13         20698  
  13         75  
13              
14             our $VERSION = '1.31'; # VERSION
15              
16 13     13   1491 use feature ();
  13         26  
  13         269  
17 13     13   816 use utf8 ();
  13         488  
  13         270  
18 13     13   707 use mro ();
  13         1162  
  13         380  
19 13     13   58 use Carp qw( croak carp confess cluck );
  13         18  
  13         970  
20 13     13   7105 use IO::File ();
  13         162706  
  13         516  
21 13     13   182 use IO::Handle ();
  13         39  
  13         331  
22 13     13   78 use Try::Tiny ();
  13         23  
  13         258  
23 13     13   7581 use PerlX::Maybe ();
  13         57411  
  13         22918  
24              
25             my ($perl_version) = $^V =~ /^v5\.(\d+)/;
26              
27             my $features_available = ( %feature::feature_bundle and $feature::feature_bundle{all} )
28             ? $feature::feature_bundle{all}
29             : [ qw( say state switch unicode_strings ) ];
30              
31             my $functions_available = [ qw(
32             nostrict nowarnings
33             nofeatures nobundle noskipexperimentalwarnings
34             noutf8 noc3 nocarp notry trytiny nodefer nomaybe noautoclean
35             ) ];
36              
37             my $functions_deprecated = ['noexperiments'];
38              
39             my ( $no_parent, $late_parent );
40              
41             sub import {
42 20     20   4529 my ( $self, $caller ) = ( shift, caller() );
43              
44 20         50 my ( @features, @nofeatures, @functions, @bundles, @classes );
45 20         50 for (@_) {
46 7         17 ( my $opt = $_ ) =~ s/^\-//;
47              
48 7 50 66     31 if ( $opt eq 'class' ) {
    50          
    50          
    100          
    100          
    100          
    100          
49 0         0 push( @classes, $opt );
50             }
51             elsif ( $opt eq 'cor' ) {
52 0         0 push( @features, 'class' );
53             }
54             elsif ( $opt eq 'nocor' ) {
55 0         0 push( @nofeatures, 'class' );
56             }
57 182         253 elsif ( grep { $_ eq $opt } @$features_available ) {
58 1         5 push( @features, $opt );
59             }
60 156         241 elsif ( my ($nofeature) = grep { 'no' . $_ eq $opt } @$features_available ) {
61 1         2 push( @nofeatures, $nofeature );
62             }
63 70         258 elsif ( grep { $_ eq $opt } @$functions_available, @$functions_deprecated ) {
64 2 50       3 push( @functions, $opt ) if ( grep { $_ eq $opt } @$functions_available );
  26         31  
65             }
66             elsif ( $opt =~ /^:?v?5?\.?(\d+)/ and $1 >= 10 ) {
67 1         40 push( @bundles, $1 );
68             }
69             else {
70 2 50       12 push( @classes, $opt ) if ( $opt !~ /^no[a-z]{2}/ );
71             }
72             }
73              
74 20 50       176 strict ->import unless ( grep { $_ eq 'nostrict' } @functions );
  2         14  
75 20 50       639 warnings->import unless ( grep { $_ eq 'nowarnings' } @functions );
  2         68  
76              
77 20 100 66     140 if (@bundles) {
    100          
78 1         126 feature->import( ':5.' . $_ ) for (@bundles);
79             }
80             elsif (
81             not grep { $_ eq 'nofeatures' } @functions and
82             not grep { $_ eq 'nobundle' } @functions
83             ) {
84 18 50       3058 feature->import( $perl_version >= 16 ? ':all' : ':5.' . $perl_version );
85             }
86 20         282 feature->import($_) for (@features);
87 20         106 feature->unimport($_) for (@nofeatures);
88              
89 20 50       81 unless ( grep { $_ eq 'noutf8' } @functions ) {
  2         4  
90 20         154 utf8->import;
91 20         253 binmode( $_, ':utf8' ) for ( *STDIN, *STDERR, *STDOUT );
92 20         123 'open'->import::into( $caller, ':std', ':utf8' );
93             }
94              
95 20 50       37964 mro::set_mro( $caller, 'c3' ) unless ( grep { $_ eq 'noc3' } @functions );
  2         21  
96              
97             monkey_patch( $self, $caller,
98 80         113 ( map { $_ => \&{ 'Carp::' . $_ } } qw( croak carp confess cluck ) ),
  80         260  
99 40         60 ( map { $_ => \&{$_} } qw( deat deattry ) ),
  40         147  
100 20 50       73 ) unless ( grep { $_ eq 'nocarp' } @functions );
  2         8  
101              
102             feature->unimport('try') if (
103 520         933 grep { $_ eq 'try' } @$features_available and
104             (
105             grep { $_ eq 'notry' } @functions or
106 20 50 33     84 grep { $_ eq 'trytiny' } @functions
      33        
107             )
108             );
109             Syntax::Keyword::Try->import_into($caller) if (
110             $perl_version < 36 and
111 0         0 not grep { $_ eq 'notry' } @functions and
112 20 0 33     71 not grep { $_ eq 'trytiny' } @functions
  0   33     0  
113             );
114 20 50       57 Try::Tiny->import::into($caller) if ( grep { $_ eq 'trytiny' } @functions );
  2         4  
115              
116             Syntax::Keyword::Defer->import_into($caller) if (
117             $perl_version < 36 and
118 20 50 33     69 not grep { $_ eq 'nodefer' } @functions
  0         0  
119             );
120              
121 80         111 monkey_patch( $self, $caller, ( map { $_ => \&{ 'PerlX::Maybe::' . $_ } } qw(
  80         292  
122             maybe provided provided_deref provided_deref_with_maybe
123 20 50       65 ) ) ) unless ( grep { $_ eq 'nomaybe' } @functions );
  2         4  
124              
125 20         47 my @late_parents = ();
126             my $use = sub {
127 3     3   9 my ( $class, $pm, $caller, $params ) = @_;
128              
129 3         6 my $failed_require;
130             try {
131             require "$pm" unless ( do {
132 13     13   121 no strict 'refs';
  13         28  
  13         802  
133 13     13   69 no warnings 'once';
  13         24  
  13         5317  
134             ${"${caller}::INC"}{$pm};
135             } );
136             }
137 3         7 catch ($e) {
138             croak($e) unless ( index( $e, qq{Can't locate $pm in } ) == 0 );
139             return 0;
140             }
141              
142 2         7 ( $no_parent, $late_parent ) = ( undef, undef );
143              
144 2         3 my $is_exact_extension = 0;
145             {
146 13     13   89 no strict 'refs';
  13         20  
  13         11468  
  2         5  
147 2         4 $is_exact_extension = grep { index( $_, 'exact::' ) == 0 } $class, @{"${class}::ISA"};
  2         9  
  2         14  
148             }
149 2 100 66     38 $class->import( $params, $caller ) if ( $is_exact_extension and $class->can('import') );
150              
151 2 50 66     24 if ($late_parent) {
    100          
152 0         0 push( @late_parents, [ $class, $caller ] );
153             }
154             elsif ( not $no_parent and index( $class, 'exact::' ) != 0 ) {
155 1         7 $self->add_isa( $class, $caller );
156             }
157              
158 2         12 return 1;
159 20         119 };
160 20         74 for my $class (@classes) {
161 2 50       13 my $params = ( $class =~ s/\(([^\)]+)\)// ) ? $1 : undef;
162 2         11 ( my $pm = $class ) =~ s{::|'}{/}g;
163 2         5 $pm .= '.pm';
164              
165 2 50 66     8 $use->( 'exact::' . $class, 'exact/' . $pm, $caller, $params ) or
166             $use->( $class, $pm, $caller, $params ) or
167             croak(
168             "Can't locate exact/$pm or $pm in \@INC " .
169             "(you may need to install the exact::$class or $class module)" .
170             '(@INC contains: ' . join( ' ', @INC ) . ')'
171             );
172             }
173 20         45 $self->add_isa(@$_) for @late_parents;
174              
175             warnings->unimport('experimental')
176 20 50 33     572 unless ( $perl_version < 18 or grep { $_ eq 'noskipexperimentalwarnings' } @functions );
  2         40  
177              
178 20 100       172 namespace::autoclean->import( -cleanee => $caller ) unless ( grep { $_ eq 'noautoclean' } @functions );
  2         2050  
179             }
180              
181             sub monkey_patch {
182 45     45 1 209 my ( $self, $class, %patch ) = @_;
183             {
184 13     13   120 no strict 'refs';
  13         23  
  13         764  
  45         79  
185 13     13   71 no warnings 'redefine';
  13         21  
  13         2952  
186 45         469 *{"${class}::$_"} = set_subname( "${class}::$_", $patch{$_} ) for ( keys %patch );
  206         1508  
187             }
188 45         148 return;
189             }
190              
191             sub add_isa {
192 3     3 1 244079 my ( $self, $parent, $child ) = @_;
193             {
194 13     13   84 no strict 'refs';
  13         26  
  13         19446  
  3         4  
195 3 100       37 push( @{"${child}::ISA"}, $parent ) unless ( $child->isa($parent) );
  2         39  
196             }
197 3         10 return;
198             }
199              
200             sub no_parent {
201 0     0 1 0 $no_parent = 1;
202 0         0 return;
203             }
204              
205             sub late_parent {
206 0     0 1 0 $late_parent = 1;
207 0         0 return;
208             }
209              
210             sub _patch_import {
211 2     2   12 my ( $type, $self, @names ) = @_;
212              
213 2         43 my $target = ( caller(1) )[0];
214 2         29 my $original_import = $target->can('import');
215              
216 2         6 my %groups;
217 2 100       11 if ( $type eq 'provide' ) {
218 1         28 %groups = map { %$_ } grep { ref $_ eq 'HASH' } @names;
  1         5  
  3         9  
219 1         2 @names = grep { not ref $_ } @names;
  3         8  
220             }
221              
222             monkey_patch(
223             $self,
224             $target,
225             import => sub {
226 3     3   23 my ( $package, @exports ) = @_;
        3      
        3      
227              
228 3 50 33     25 if ( $original_import and ref $original_import eq 'CODE' ) {
229 3         2743 ( my $b_deparsed_sub = B::Deparse->new->coderef2text($original_import) ) =~ s/;//g;
230 3 100       44 $original_import->(@_) if ($b_deparsed_sub);
231             }
232              
233 3 100       21 if ( $type eq 'force' ) {
    50          
234 1         4 @exports = @names;
235             }
236             elsif ( $type eq 'provide' ) {
237 3         10 @exports = grep { defined } map {
238 2         7 my $name = $_;
  2         4  
239              
240 4         17 ( grep { $name eq $_ } @names ) ? $name :
241 2 50       4 ( exists $groups{$name} ) ? ( @{ $groups{$name} } ) : undef;
  1 100       5  
242             } @exports;
243             }
244              
245             monkey_patch(
246             $package,
247             ( caller(0) )[0],
248 3         51 map { $_ => \&{ $package . '::' . $_ } } @exports
  4         8  
  4         25  
249             );
250              
251 3         19 return;
252             },
253 2         19 );
254             }
255              
256             sub export {
257 1     1 1 298434 _patch_import( 'force', @_ );
258 1         3 return;
259             }
260              
261             sub exportable {
262 1     1 1 11770 _patch_import( 'provide', @_ );
263 1         2 return;
264             }
265              
266             sub deat ($) {
267 2     2 1 198953 ( my $e = reverse $_[0] ) =~ s/^\s*\.\d+\s+enil\s+.*?\s+ta\s+//;
268 2         24 return '' . reverse $e;
269             }
270              
271             sub deattry (&) {
272             try {
273             return $_[0]->();
274             }
275 2     2 1 888 catch ($e) {
276             die deat $e, "\n";
277             }
278             }
279              
280             1;
281              
282             __END__