File Coverage

lib/Unexpected/Functions.pm
Criterion Covered Total %
statement 81 81 100.0
branch 44 44 100.0
condition 21 23 95.6
subroutine 22 22 100.0
pod 11 11 100.0
total 179 181 99.4


line stmt bran cond sub pod time code
1             package Unexpected::Functions;
2              
3 4     4   130772 use strict;
  4         4  
  4         93  
4 4     4   14 use warnings;
  4         4  
  4         78  
5 4     4   1546 use parent 'Exporter::Tiny';
  4         946  
  4         16  
6              
7 4     4   10630 use Carp qw( croak );
  4         5  
  4         173  
8 4     4   394 use Package::Stash;
  4         4137  
  4         95  
9 4     4   16 use Scalar::Util qw( blessed reftype );
  4         3  
  4         205  
10 4     4   1753 use Sub::Install qw( install_sub );
  4         5046  
  4         15  
11              
12             our @EXPORT_OK = qw( catch_class exception has_exception
13             inflate_message inflate_placeholders is_class_loaded
14             is_one_of_us parse_arg_list throw throw_on_error );
15              
16             my $Exception_Class = 'Unexpected'; my $Should_Quote = 1;
17              
18             # Private functions
19             my $_catch = sub {
20             my $block = shift; return ((bless \$block, 'Try::Tiny::Catch'), @_);
21             };
22              
23             my $_clone_one_of_us = sub {
24             return $_[ 1 ] ? { %{ $_[ 0 ] }, %{ $_[ 1 ] } } : { error => $_[ 0 ] };
25             };
26              
27             my $_dereference_code = sub {
28             my ($code, @args) = @_;
29              
30             $args[ 0 ] and ref $args[ 0 ] eq 'ARRAY' and unshift @args, 'args';
31              
32             return { class => $code->(), @args };
33             };
34              
35             my $_exception_class_cache = {};
36              
37             my $_exception_class = sub {
38             my $caller = shift;
39              
40             exists $_exception_class_cache->{ $caller }
41             and defined $_exception_class_cache->{ $caller }
42             and return $_exception_class_cache->{ $caller };
43              
44             my $code = $caller->can( 'EXCEPTION_CLASS' );
45             my $class = $code ? $code->() : $Exception_Class;
46              
47             return $_exception_class_cache->{ $caller } = $class;
48             };
49              
50             my $_match_class = sub {
51             my ($e, $ref, $blessed, $does, $key) = @_;
52              
53             return !defined $key ? !defined $e
54             : $key eq '*' ? 1
55             : $key eq ':str' ? !$ref
56             : $key eq $ref ? 1
57             : $blessed && $e->can( 'instance_of' ) ? $e->instance_of( $key )
58             : $blessed && $e->$does( $key ) ? 1
59             : 0;
60             };
61              
62             my $_quote_maybe = sub {
63             return $Should_Quote ? "'".$_[ 0 ]."'" : $_[ 0 ];
64             };
65              
66             my $_gen_checker = sub {
67             my @prototable = @_;
68              
69             return sub {
70             my $e = shift;
71             my $ref = ref $e;
72             my $blessed = blessed $e;
73             my $does = ($blessed && $e->can( 'DOES' )) || 'isa';
74             my @table = @prototable;
75              
76             while (my ($key, $value) = splice @table, 0, 2) {
77             $_match_class->( $e, $ref, $blessed, $does, $key ) and return $value
78             }
79              
80             return;
81             }
82             };
83              
84             # Package methods
85             sub import {
86 23     23   154 my $class = shift;
87 23 100 100     148 my $global_opts = { $_[ 0 ] && ref $_[ 0 ] eq 'HASH' ? %{+ shift } : () };
  3         10  
88 23         34 my $ex_class = delete $global_opts->{exception_class};
89             # uncoverable condition false
90 23   66     103 my $target = $global_opts->{into} //= caller;
91 23         37 my @want = @_;
92 23         28 my @args = ();
93              
94 23 100       71 $ex_class or $ex_class = $_exception_class->( $target );
95              
96 23         40 for my $sym (@want) {
97 29 100 100     190 if ($ex_class->can( 'is_exception' ) and $ex_class->is_exception( $sym )){
98 2     4   9 my $code = sub { sub { $sym } };
  4         886  
  4         13  
99              
100 2         9 install_sub { as => $sym, code => $code, into => $target, };
101             }
102 27         49 else { push @args, $sym }
103             }
104              
105 23         145 $class->SUPER::import( $global_opts, @args );
106 23         181758 return;
107             }
108              
109             sub quote_bind_values { # Deprecated. Use third arg in inflate_placeholders defs
110 2 100   2 1 248 defined $_[ 1 ] and $Should_Quote = !!$_[ 1 ]; return $Should_Quote;
  2         3  
111             }
112              
113             # Public functions
114             sub parse_arg_list (;@) { # Coerce a hash ref from whatever was passed
115 43     43 1 941 my $n = 0; $n++ while (defined $_[ $n ]);
  43         146  
116              
117             return ( $n == 0) ? {}
118             : (is_one_of_us( $_[ 0 ] )) ? $_clone_one_of_us->( @_ )
119             : ( ref $_[ 0 ] eq 'CODE') ? $_dereference_code->( @_ )
120 6         40 : ( ref $_[ 0 ] eq 'HASH') ? { %{ $_[ 0 ] } }
121             : ( $n == 1) ? { error => $_[ 0 ] }
122             : ( ref $_[ 1 ] eq 'ARRAY') ? { error => (shift), args => @_ }
123 43 100       101 : ( ref $_[ 1 ] eq 'HASH') ? { error => $_[ 0 ], %{ $_[ 1 ] } }
  1 100       4  
    100          
    100          
    100          
    100          
    100          
    100          
124             : ( $n % 2 == 1) ? { error => @_ }
125             : { @_ };
126             }
127              
128             sub catch_class ($@) {
129 3     3 1 10 my $check = $_gen_checker->( @{+ shift }, '*' => sub { die $_[ 0 ] } );
  11     11   3005  
  11         38  
130              
131 11 100       185 wantarray or croak 'Useless bare catch_class()';
132              
133 10   100 10   25 return $_catch->( sub { ($check->( $_[ 0 ] ) || return)->( $_[ 0 ] ) }, @_ );
  10         290  
134             }
135              
136             sub exception (;@) {
137 1     1 1 5 return $_exception_class->( caller )->caught( @_ );
138             }
139              
140             sub has_exception ($;@) {
141 5     5 1 554 my ($name, %args) = @_; my $exception_class = caller;
  5         6  
142              
143 5         10 return $exception_class->add_exception( $name, \%args );
144             }
145              
146             sub inflate_message ($;@) { # Expand positional parameters of the form [_]
147 31     31 1 72 return inflate_placeholders( [ '[?]', '[]' ], @_ );
148             }
149              
150             sub inflate_placeholders ($;@) { # Sub visible strings for null and undef
151 46     46 1 40 my $defaults = shift;
152 46         45 my $msg = shift;
153 507 100       592 my @vals = map { $defaults->[ 2 ] ? $_ : $_quote_maybe->( $_ ) }
154             # uncoverable condition false
155 507 100       526 map { (length) ? $_ : $defaults->[ 1 ] }
156 507   66     624 map { $_ // $defaults->[ 0 ] } @_,
157 46         74 map { $defaults->[ 0 ] } 0 .. 9;
  460         400  
158              
159 46         379 $msg =~ s{ \[ _ (\d+) \] }{$vals[ $1 - 1 ]}gmx;
160 46         215 return $msg;
161             }
162              
163             sub is_class_loaded ($) { # Lifted from Class::Load
164 17     17 1 760 my $class = shift; my $stash = Package::Stash->new( $class );
  17         122  
165              
166 17 100       135 if ($stash->has_symbol( '$VERSION' )) {
167 5         6 my $version = ${ $stash->get_symbol( '$VERSION' ) };
  5         12  
168              
169 5 100       10 if (defined $version) {
170 4 100       14 not ref $version and return 1;
171             # Sometimes $VERSION ends up as a reference to undef (weird)
172 3 100 100     12 reftype $version eq 'SCALAR' and defined ${ $version } and return 1;
  2         10  
173 2 100       9 blessed $version and return 1; # A version object
174             }
175             }
176              
177 14 100 100     52 $stash->has_symbol( '@ISA' ) and @{ $stash->get_symbol( '@ISA' ) }
  3         28  
178             and return 1;
179             # Check for any method
180 12 100       85 return $stash->list_all_symbols( 'CODE' ) ? 1 : 0;
181             }
182              
183             sub is_one_of_us ($) {
184 71   100 71 1 517 return $_[ 0 ] && (blessed $_[ 0 ]) && $_[ 0 ]->isa( $Exception_Class );
185             }
186              
187             sub throw (;@) {
188 3     3 1 922 $_exception_class->( caller )->throw( @_ );
189             }
190              
191             sub throw_on_error (;@) {
192 2     2 1 61 return $_exception_class->( caller )->throw_on_error( @_ );
193             }
194              
195             1;
196              
197             __END__