File Coverage

blib/lib/Specio/Subs.pm
Criterion Covered Total %
statement 87 89 97.7
branch 9 12 75.0
condition 2 2 100.0
subroutine 20 20 100.0
pod 0 1 0.0
total 118 124 95.1


line stmt bran cond sub pod time code
1             package Specio::Subs;
2              
3 2     2   759 use strict;
  2         4  
  2         50  
4 2     2   7 use warnings;
  2         4  
  2         76  
5              
6             our $VERSION = '0.46';
7              
8 2     2   9 use Carp qw( croak );
  2         4  
  2         89  
9 2     2   10 use Eval::Closure qw( eval_closure );
  2         2  
  2         70  
10 2     2   851 use Module::Runtime qw( use_package_optimistically );
  2         2974  
  2         9  
11 2     2   428 use Specio::Library::Perl;
  2         6  
  2         13  
12 2     2   10 use Specio::Registry qw( exportable_types_for_package );
  2         3  
  2         1406  
13              
14             my $counter = 0;
15              
16             sub import {
17 4     4   8320 shift;
18 4         9 my @libs = @_;
19              
20 4         9 my $caller = caller();
21              
22 4         13 my $ident = t('Identifier');
23              
24 4         17 use_package_optimistically($_) for @libs;
25              
26 4         143 for my $types ( map { exportable_types_for_package($_) } @libs ) {
  6         15  
27 6         8 for my $name ( keys %{$types} ) {
  6         22  
28 50 100       150 croak
29             qq{Cannot use '$name' type to create a check sub. It results in an invalid Perl subroutine name}
30             unless $ident->check( 'is_' . $name );
31              
32 49         1598 _export_subs( $name, $types->{$name}, $caller );
33             }
34             }
35             }
36              
37             sub _export_subs {
38 49     49   67 my $name = shift;
39 49         58 my $type = shift;
40 49         54 my $caller = shift;
41              
42 49         87 _export_validation_subs( $name, $type, $caller );
43              
44 49 100       202 return unless $type->has_coercions;
45              
46 1         4 _export_coercion_subs( $name, $type, $caller );
47             }
48              
49             sub _export_validation_subs {
50 49     49   55 my $name = shift;
51 49         51 my $type = shift;
52 49         48 my $caller = shift;
53              
54 49         73 my $is_name = 'is_' . $name;
55 49         66 my $assert_name = 'assert_' . $name;
56 49 100       91 if ( $type->can_be_inlined ) {
57 48         273 _make_sub(
58             $caller, $is_name,
59             $type->inline_check('$_[0]')
60             );
61 48         153 _make_sub(
62             $caller, $assert_name,
63             $type->inline_assert('$_[0]')
64             );
65             }
66             else {
67             _install_sub(
68             $caller, $is_name,
69 53     53   26486 sub { $type->value_is_valid( $_[0] ) }
        53      
70 1         16 );
71             _install_sub(
72             $caller, $assert_name,
73 53     53   2489 sub { $type->validate_or_die( $_[0] ) }
74 1         7 );
75             }
76             }
77              
78             sub _export_coercion_subs {
79 1     54   2 my $name = shift;
80 1         2 my $type = shift;
81 1         1 my $caller = shift;
82              
83 1         3 my $to_name = 'to_' . $name;
84 1 50       49 if ( $type->can_inline_coercion ) {
85 0         0 _make_sub(
86             $caller, $to_name,
87             $type->inline_coercion('$_[0]')
88             );
89             }
90             else {
91             _install_sub(
92             $caller, $to_name,
93 3     3   567 sub { $type->coerce_value( $_[0] ) }
94 1         24 );
95             }
96              
97 1         5 my $force_name = 'force_' . $name;
98 1 50       5 if ( $type->can_inline_coercion_and_check ) {
99 0         0 _make_sub(
100             $caller, $force_name,
101             $type->inline_coercion_and_check('$_[0]')
102             );
103             }
104             else {
105             _install_sub(
106             $caller, $force_name,
107             sub {
108 3     6   1606 my $val = $type->coerce_value( $_[0] );
109 3         32 $type->validate_or_die($val);
110 2         436 return $val;
111             }
112 1         10 );
113             }
114             }
115              
116             sub _make_sub {
117 96     99   125 my $caller = shift;
118 96         102 my $sub_name = shift;
119 96         105 my $source = shift;
120 96         100 my $env = shift;
121              
122 96         297 my $sub = eval_closure(
123             source => 'sub { ' . $source . ' }',
124             environment => $env,
125             description => $caller . '::'
126             . $sub_name
127             . ' generated by '
128             . __PACKAGE__,
129             );
130              
131 96         31263 _install_sub( $caller, $sub_name, $sub );
132              
133 96         142 return;
134             }
135              
136             my $sub_namer = do {
137             eval {
138             require Sub::Util;
139             Sub::Util->VERSION(1.40);
140             Sub::Util->can('set_subname');
141             } or eval {
142             require Sub::Name;
143             Sub::Name->can('subname');
144             }
145             or sub { return $_[1] };
146             };
147              
148             my %Installed;
149              
150             sub _install_sub {
151 100     100   142 my $caller = shift;
152 100         133 my $sub_name = shift;
153 100         118 my $sub = shift;
154              
155 100         166 my $fq_name = $caller . '::' . $sub_name;
156              
157             {
158             ## no critic (TestingAndDebugging::ProhibitNoStrict)
159 2     2   15 no strict 'refs';
  2         3  
  2         264  
  100         128  
160 100         509 *{$fq_name} = $sub_namer->( $fq_name, $sub );
  100         367  
161             }
162              
163 100   100     233 $Installed{$caller} ||= [];
164 100         103 push @{ $Installed{$caller} }, $sub_name;
  100         182  
165              
166 100         139 return;
167             }
168              
169             sub subs_installed_into {
170 1     1 0 5 my $package = shift;
171              
172 1 50       2 return @{ $Installed{$package} || [] };
  1         13  
173             }
174              
175             1;
176              
177             # ABSTRACT: Make validation and coercion subs from Specio types
178              
179             __END__
180              
181             =pod
182              
183             =encoding UTF-8
184              
185             =head1 NAME
186              
187             Specio::Subs - Make validation and coercion subs from Specio types
188              
189             =head1 VERSION
190              
191             version 0.46
192              
193             =head1 SYNOPSIS
194              
195             use Specio::Subs qw( Specio::Library::Builtins Specio::Library::Perl My::Lib );
196              
197             if ( is_PackageName($var) ) { ... }
198              
199             assert_Str($var);
200              
201             my $person1 = to_Person($var);
202             my $person2 = force_Person($var);
203              
204             =head1 DESCRIPTION
205              
206             This module generates a set of helpful validation and coercion subroutines for
207             all of the types defined in one or more libraries.
208              
209             To use it, simply import C<Specio::Subs> passing a list of one or more library
210             names. This module will load those libraries as needed.
211              
212             If any of the types in any libraries have names that do not work as part of a
213             Perl subroutine name, this module will throw an exception.
214              
215             If you have L<Sub::Util> or L<Sub::Name> installed, one of those will be used
216             to name the generated subroutines.
217              
218             =head1 "EXPORTS"
219              
220             The following subs are created in the importing package:
221              
222             =head2 is_$type($value)
223              
224             This subroutine returns a boolean indicating whether or not the C<$value> is
225             valid for the type.
226              
227             =head2 assert_$type($value)
228              
229             This subroutine dies if the C<$value> is not valid for the type.
230              
231             =head2 to_$type($value)
232              
233             This subroutine attempts to coerce C<$value> into the given type. If it cannot
234             be coerced it returns the original C<$value>.
235              
236             This is only created if the type has coercions.
237              
238             =head2 force_$type($value)
239              
240             This subroutine attempts to coerce C<$value> into the given type, and dies if
241             it cannot do so.
242              
243             This is only created if the type has coercions.
244              
245             =head1 ADDITIONAL API
246              
247             =for Pod::Coverage subs_installed_into
248              
249             This module has a subroutine named C<subs_installed_into>. It is not exported
250             but it can be called by its fully qualified name. It accepts a single
251             argument, a package name. It returns a list of subs that it generated and
252             installed in the given package, if any.
253              
254             This exists to make it easy to write a type library that combines other
255             library and generates helper subs for export all at once.
256              
257             =head1 SUPPORT
258              
259             Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
260              
261             I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
262              
263             =head1 SOURCE
264              
265             The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
266              
267             =head1 AUTHOR
268              
269             Dave Rolsky <autarch@urth.org>
270              
271             =head1 COPYRIGHT AND LICENSE
272              
273             This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
274              
275             This is free software, licensed under:
276              
277             The Artistic License 2.0 (GPL Compatible)
278              
279             The full text of the license can be found in the
280             F<LICENSE> file included with this distribution.
281              
282             =cut