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   40395 use strict;
  4         6  
  4         88  
4 4     4   12 use warnings;
  4         4  
  4         82  
5 4     4   1757 use parent 'Exporter::Tiny';
  4         949  
  4         20  
6              
7 4     4   10856 use Carp qw( croak );
  4         5  
  4         184  
8 4     4   430 use Package::Stash;
  4         4446  
  4         75  
9 4     4   12 use Scalar::Util qw( blessed reftype );
  4         14  
  4         198  
10 4     4   1845 use Sub::Install qw( install_sub );
  4         4691  
  4         12  
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   150 my $class = shift;
87 23 100 100     149 my $global_opts = { $_[ 0 ] && ref $_[ 0 ] eq 'HASH' ? %{+ shift } : () };
  3         9  
88 23         33 my $ex_class = delete $global_opts->{exception_class};
89             # uncoverable condition false
90 23   66     102 my $target = $global_opts->{into} //= caller;
91 23         37 my @want = @_;
92 23         25 my @args = ();
93              
94 23 100       68 $ex_class or $ex_class = $_exception_class->( $target );
95              
96 23         37 for my $sym (@want) {
97 29 100 100     193 if ($ex_class->can( 'is_exception' ) and $ex_class->is_exception( $sym )){
98 2     5   13 my $code = sub { sub { $sym } };
  5         1000  
  5         14  
99              
100 2         9 install_sub { as => $sym, code => $code, into => $target, };
101             }
102 27         42 else { push @args, $sym }
103             }
104              
105 23         146 $class->SUPER::import( $global_opts, @args );
106 23         209821 return;
107             }
108              
109             sub quote_bind_values { # Deprecated. Use third arg in inflate_placeholders defs
110 2 100   2 1 346 defined $_[ 1 ] and $Should_Quote = !!$_[ 1 ]; return $Should_Quote;
  2         4  
111             }
112              
113             # Public functions
114             sub parse_arg_list (;@) { # Coerce a hash ref from whatever was passed
115 43     43 1 1143 my $n = 0; $n++ while (defined $_[ $n ]);
  43         152  
116              
117             return ( $n == 0) ? {}
118             : (is_one_of_us( $_[ 0 ] )) ? $_clone_one_of_us->( @_ )
119             : ( ref $_[ 0 ] eq 'CODE') ? $_dereference_code->( @_ )
120 6         27 : ( ref $_[ 0 ] eq 'HASH') ? { %{ $_[ 0 ] } }
121             : ( $n == 1) ? { error => $_[ 0 ] }
122             : ( ref $_[ 1 ] eq 'ARRAY') ? { error => (shift), args => @_ }
123 43 100       106 : ( ref $_[ 1 ] eq 'HASH') ? { error => $_[ 0 ], %{ $_[ 1 ] } }
  1 100       5  
    100          
    100          
    100          
    100          
    100          
    100          
124             : ( $n % 2 == 1) ? { error => @_ }
125             : { @_ };
126             }
127              
128             sub catch_class ($@) {
129 3     3 1 11 my $check = $_gen_checker->( @{+ shift }, '*' => sub { die $_[ 0 ] } );
  11     11   4264  
  11         39  
130              
131 11 100       189 wantarray or croak 'Useless bare catch_class()';
132              
133 10   100 10   24 return $_catch->( sub { ($check->( $_[ 0 ] ) || return)->( $_[ 0 ] ) }, @_ );
  10         280  
134             }
135              
136             sub exception (;@) {
137 1     1 1 6 return $_exception_class->( caller )->caught( @_ );
138             }
139              
140             sub has_exception ($;@) {
141 5     5 1 550 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 69 return inflate_placeholders( [ '[?]', '[]' ], @_ );
148             }
149              
150             sub inflate_placeholders ($;@) { # Sub visible strings for null and undef
151 46     46 1 41 my $defaults = shift;
152 46         36 my $msg = shift;
153 507 100       602 my @vals = map { $defaults->[ 2 ] ? $_ : $_quote_maybe->( $_ ) }
154             # uncoverable condition false
155 507 100       560 map { (length) ? $_ : $defaults->[ 1 ] }
156 507   66     625 map { $_ // $defaults->[ 0 ] } @_,
157 46         72 map { $defaults->[ 0 ] } 0 .. 9;
  460         381  
158              
159 46         390 $msg =~ s{ \[ _ (\d+) \] }{$vals[ $1 - 1 ]}gmx;
160 46         216 return $msg;
161             }
162              
163             sub is_class_loaded ($) { # Lifted from Class::Load
164 17     17 1 747 my $class = shift; my $stash = Package::Stash->new( $class );
  17         123  
165              
166 17 100       147 if ($stash->has_symbol( '$VERSION' )) {
167 5         5 my $version = ${ $stash->get_symbol( '$VERSION' ) };
  5         14  
168              
169 5 100       11 if (defined $version) {
170 4 100       12 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         9  
173 2 100       10 blessed $version and return 1; # A version object
174             }
175             }
176              
177 14 100 100     50 $stash->has_symbol( '@ISA' ) and @{ $stash->get_symbol( '@ISA' ) }
  3         30  
178             and return 1;
179             # Check for any method
180 12 100       89 return $stash->list_all_symbols( 'CODE' ) ? 1 : 0;
181             }
182              
183             sub is_one_of_us ($) {
184 71   100 71 1 545 return $_[ 0 ] && (blessed $_[ 0 ]) && $_[ 0 ]->isa( $Exception_Class );
185             }
186              
187             sub throw (;@) {
188 3     3 1 958 $_exception_class->( caller )->throw( @_ );
189             }
190              
191             sub throw_on_error (;@) {
192 2     2 1 56 return $_exception_class->( caller )->throw_on_error( @_ );
193             }
194              
195             1;
196              
197             __END__