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   148128 use strict;
  4         14  
  4         171  
4 4     4   36 use warnings;
  4         12  
  4         191  
5 4     4   1483 use parent 'Exporter::Tiny';
  4         1447  
  4         29  
6              
7 4     4   18219 use Carp qw( croak );
  4         12  
  4         408  
8 4     4   442 use Package::Stash;
  4         5502  
  4         189  
9 4     4   77 use Scalar::Util qw( blessed reftype );
  4         21  
  4         318  
10 4     4   1775 use Sub::Install qw( install_sub );
  4         10702  
  4         26  
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   291 my $class = shift;
87 23 100 100     222 my $global_opts = { $_[ 0 ] && ref $_[ 0 ] eq 'HASH' ? %{+ shift } : () };
  3         15  
88 23         75 my $ex_class = delete $global_opts->{exception_class};
89             # uncoverable condition false
90 23   66     165 my $target = $global_opts->{into} //= caller;
91 23         84 my @want = @_;
92 23         66 my @args = ();
93              
94 23 100       123 $ex_class or $ex_class = $_exception_class->( $target );
95              
96 23         82 for my $sym (@want) {
97 29 100 100     1281 if ($ex_class->can( 'is_exception' ) and $ex_class->is_exception( $sym )){
98 2     5   16 my $code = sub { sub { $sym } };
  5         2009  
  5         33  
99              
100 2         21 install_sub { as => $sym, code => $code, into => $target, };
101             }
102 27         110 else { push @args, $sym }
103             }
104              
105 23         661 $class->SUPER::import( $global_opts, @args );
106 23         348806 return;
107             }
108              
109             sub quote_bind_values { # Deprecated. Use third arg in inflate_placeholders defs
110 2 100   2 1 728 defined $_[ 1 ] and $Should_Quote = !!$_[ 1 ]; return $Should_Quote;
  2         8  
111             }
112              
113             # Public functions
114             sub parse_arg_list (;@) { # Coerce a hash ref from whatever was passed
115 43     43 1 1311 my $n = 0; $n++ while (defined $_[ $n ]);
  43         287  
116              
117             return ( $n == 0) ? {}
118             : (is_one_of_us( $_[ 0 ] )) ? $_clone_one_of_us->( @_ )
119             : ( ref $_[ 0 ] eq 'CODE') ? $_dereference_code->( @_ )
120 6         57 : ( ref $_[ 0 ] eq 'HASH') ? { %{ $_[ 0 ] } }
121             : ( $n == 1) ? { error => $_[ 0 ] }
122             : ( ref $_[ 1 ] eq 'ARRAY') ? { error => (shift), args => @_ }
123 43 100       240 : ( ref $_[ 1 ] eq 'HASH') ? { error => $_[ 0 ], %{ $_[ 1 ] } }
  1 100       11  
    100          
    100          
    100          
    100          
    100          
    100          
124             : ( $n % 2 == 1) ? { error => @_ }
125             : { @_ };
126             }
127              
128             sub catch_class ($@) {
129 3     3 1 29 my $check = $_gen_checker->( @{+ shift }, '*' => sub { die $_[ 0 ] } );
  11     11   10653  
  11         99  
130              
131 11 100       332 wantarray or croak 'Useless bare catch_class()';
132              
133 10   100 10   68 return $_catch->( sub { ($check->( $_[ 0 ] ) || return)->( $_[ 0 ] ) }, @_ );
  10         630  
134             }
135              
136             sub exception (;@) {
137 1     1 1 9 return $_exception_class->( caller )->caught( @_ );
138             }
139              
140             sub has_exception ($;@) {
141 5     5 1 1497 my ($name, %args) = @_; my $exception_class = caller;
  5         14  
142              
143 5         16 return $exception_class->add_exception( $name, \%args );
144             }
145              
146             sub inflate_message ($;@) { # Expand positional parameters of the form [_]
147 31     31 1 133 return inflate_placeholders( [ '[?]', '[]' ], @_ );
148             }
149              
150             sub inflate_placeholders ($;@) { # Sub visible strings for null and undef
151 46     46 1 108 my $defaults = shift;
152 46         111 my $msg = shift;
153 507 100       1095 my @vals = map { $defaults->[ 2 ] ? $_ : $_quote_maybe->( $_ ) }
154             # uncoverable condition false
155 507 100       1121 map { (length) ? $_ : $defaults->[ 1 ] }
156 507   66     1134 map { $_ // $defaults->[ 0 ] } @_,
157 46         145 map { $defaults->[ 0 ] } 0 .. 9;
  460         991  
158              
159 46         681 $msg =~ s{ \[ _ (\d+) \] }{$vals[ $1 - 1 ]}gmx;
160 46         451 return $msg;
161             }
162              
163             sub is_class_loaded ($) { # Lifted from Class::Load
164 17     17 1 1106 my $class = shift; my $stash = Package::Stash->new( $class );
  17         157  
165              
166 17 100       182 if ($stash->has_symbol( '$VERSION' )) {
167 5         10 my $version = ${ $stash->get_symbol( '$VERSION' ) };
  5         26  
168              
169 5 100       15 if (defined $version) {
170 4 100       19 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         11  
173 2 100       11 blessed $version and return 1; # A version object
174             }
175             }
176              
177 14 100 100     74 $stash->has_symbol( '@ISA' ) and @{ $stash->get_symbol( '@ISA' ) }
  3         36  
178             and return 1;
179             # Check for any method
180 12 100       105 return $stash->list_all_symbols( 'CODE' ) ? 1 : 0;
181             }
182              
183             sub is_one_of_us ($) {
184 71   100 71 1 1096 return $_[ 0 ] && (blessed $_[ 0 ]) && $_[ 0 ]->isa( $Exception_Class );
185             }
186              
187             sub throw (;@) {
188 3     3 1 1995 $_exception_class->( caller )->throw( @_ );
189             }
190              
191             sub throw_on_error (;@) {
192 2     2 1 44 return $_exception_class->( caller )->throw_on_error( @_ );
193             }
194              
195             1;
196              
197             __END__