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