File Coverage

blib/lib/Specio/OO.pm
Criterion Covered Total %
statement 153 160 95.6
branch 36 38 94.7
condition 8 11 72.7
subroutine 24 27 88.8
pod 0 1 0.0
total 221 237 93.2


line stmt bran cond sub pod time code
1              
2             use strict;
3 30     30   160 use warnings;
  30         50  
  30         782  
4 30     30   122  
  30         50  
  30         664  
5             use Carp qw( confess );
6 30     30   133 use List::Util qw( all );
  30         52  
  30         1306  
7 30     30   150 use MRO::Compat;
  30         44  
  30         2690  
8 30     30   11054 use Role::Tiny;
  30         43361  
  30         772  
9 30     30   5410 use Scalar::Util qw( weaken );
  30         41345  
  30         159  
10 30     30   4070 use Specio::Helpers qw( perlstring );
  30         51  
  30         1359  
11 30     30   3518 use Specio::PartialDump qw( partial_dump );
  30         52  
  30         1221  
12 30     30   10821 use Specio::TypeChecks;
  30         69  
  30         1698  
13 30     30   9216 use Storable qw( dclone );
  30         68  
  30         1125  
14 30     30   14268  
  30         66313  
  30         2014  
15             our $VERSION = '0.48';
16              
17             use Exporter qw( import );
18 30     30   213  
  30         49  
  30         4845  
19             ## no critic (Modules::ProhibitAutomaticExportation)
20             our @EXPORT = qw(
21             clone
22             _ooify
23             );
24             ## use critic
25              
26             ## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
27             my $class = shift;
28              
29 213     213   401 my $attrs = $class->_attrs;
30             for my $name ( sort keys %{$attrs} ) {
31 213         529 my $attr = $attrs->{$name};
32 213         322  
  213         1564  
33 2362         3722 _inline_reader( $class, $name, $attr );
34             _inline_predicate( $class, $name, $attr );
35 2362         4183 }
36 2362         4962  
37             _inline_constructor($class);
38             }
39 213         671 ## use critic
40              
41             my $class = shift;
42             my $name = shift;
43             my $attr = shift;
44 2362     2362   2610  
45 2362         2724 my $reader;
46 2362         2404 if ( $attr->{lazy} && ( my $builder = $attr->{builder} ) ) {
47             my $source = <<'EOF';
48 2362         2352 sub {
49 2362 100 66     6510 unless ( exists $_[0]->{%s} ) {
50 1026         1348 $_[0]->{%s} = $_[0]->%s;
51             Scalar::Util::weaken( $_[0]->{%s} ) if %s && ref $_[0]->{%s};
52             }
53             $_[0]->{%s};
54             }
55             EOF
56             $reader = sprintf(
57             $source,
58             $name,
59             $name,
60             $builder,
61             $name,
62             ( $attr->{weak_ref} ? 1 : 0 ),
63             $name,
64             $name,
65 1026 50       5276 );
66             }
67             else {
68             $reader = sprintf( 'sub { $_[0]->{%s} }', $name );
69             }
70              
71 1336         3899 {
72             ## no critic (TestingAndDebugging::ProhibitNoStrict)
73             no strict 'refs';
74             *{ $class . '::' . $name } = _eval_or_die(
75             $reader, $class . '->' . $name,
76 30     30   178 );
  30         45  
  30         2820  
  2362         2660  
77 2362         4726 }
  2362         12490  
78             }
79              
80             my $class = shift;
81             my $name = shift;
82             my $attr = shift;
83              
84 2362     2362   3230 return unless $attr->{predicate};
85 2362         2523  
86 2362         2517 my $predicate = "sub { exists \$_[0]->{$name} }";
87              
88 2362 100       5368 {
89             ## no critic (TestingAndDebugging::ProhibitNoStrict)
90 600         1146 no strict 'refs';
91             *{ $class . '::' . $attr->{predicate} } = _eval_or_die(
92             $predicate, $class . '->' . $attr->{predicate},
93             );
94 30     30   155 }
  30         47  
  30         3146  
  600         704  
95 600         3843 }
96              
97 600         1388 my @RolesWithBUILD = qw( Specio::Constraint::Role::Interface );
98              
99             # This is an optimization to avoid calling this many times over:
100             #
101             # Specio::TypeChecks->can( 'is_' . $attr->{isa} )
102             my %TypeChecks;
103              
104             BEGIN {
105             for my $sub (@Specio::TypeChecks::EXPORT_OK) {
106             my ($type) = $sub =~ /^is_(.+)$/
107             or next;
108             $TypeChecks{$type} = Specio::TypeChecks->can($sub);
109 30     30   113 }
110 240 100       2110 }
111              
112 180         749 my $class = shift;
113              
114             my @build_subs;
115             for my $parent ( @{ mro::get_linear_isa($class) } ) {
116             {
117 213     213   324 ## no critic (TestingAndDebugging::ProhibitNoStrict)
118             no strict 'refs';
119 213         292 push @build_subs, $parent . '::BUILD'
120 213         273 if defined &{ $parent . '::BUILD' };
  213         1110  
121             }
122             }
123 30     30   159  
  30         55  
  30         9369  
  213         348  
124             # This is all a hack to avoid needing Class::Method::Modifiers to add a
125 213 100       258 # BUILD from a role. We can't just call the method in the role "BUILD" or
  213         1075  
126             # it will be shadowed by a class's BUILD. So we give it a wacky unique
127             # name. We need to explicitly know which roles have a _X_BUILD method
128             # because Role::Tiny doesn't provide a way to list all the roles applied
129             # to a class.
130             for my $role (@RolesWithBUILD) {
131             if ( Role::Tiny::does_role( $class, $role ) ) {
132             ( my $build_name = $role ) =~ s/::/_/g;
133             $build_name = q{_} . $build_name . '_BUILD';
134             push @build_subs, $role . '::' . $build_name;
135 213         404 }
136 213 100       616 }
137 123         2045  
138 123         299 my $constructor = <<'EOF';
139 123         333 sub {
140             my $class = shift;
141              
142             my %p = do {
143 213         1848 if ( @_ == 1 ) {
144             if ( ref $_[0] eq 'HASH' ) {
145             %{ shift() };
146             }
147             else {
148             Specio::OO::_constructor_confess(
149             Specio::OO::_bad_args_message( $class, @_ ) );
150             }
151             }
152             else {
153             Specio::OO::_constructor_confess(
154             Specio::OO::_bad_args_message( $class, @_ ) )
155             if @_ % 2;
156             @_;
157             }
158             };
159              
160             my $self = bless {}, $class;
161              
162             EOF
163              
164             my $attrs = $class->_attrs;
165             for my $name ( sort keys %{$attrs} ) {
166             my $attr = $attrs->{$name};
167             my $key_name = defined $attr->{init_arg} ? $attr->{init_arg} : $name;
168              
169 213         579 if ( $attr->{required} ) {
170 213         310 $constructor .= <<"EOF";
  213         1354  
171 2362         2640 Specio::OO::_constructor_confess(
172 2362 100       3395 "$class->new requires a $key_name argument.")
173             unless exists \$p{$key_name};
174 2362 100       3258 EOF
175 494         986 }
176              
177             if ( $attr->{builder} && !$attr->{lazy} ) {
178             my $builder = $attr->{builder};
179             $constructor .= <<"EOF";
180             \$p{$key_name} = $class->$builder unless exists \$p{$key_name};
181             EOF
182 2362 100 100     4666 }
183 163         248  
184 163         444 if ( $attr->{isa} ) {
185             my $validator;
186             if ( $TypeChecks{ $attr->{isa} } ) {
187             $validator
188             = 'Specio::TypeChecks::is_'
189 2362 100       3153 . $attr->{isa}
190 1852         1738 . "( \$p{$key_name} )";
191 1852 100       2884 }
192             else {
193             my $quoted_class = perlstring( $attr->{isa} );
194             $validator
195 1665         2728 = "Specio::TypeChecks::isa_class( \$p{$key_name}, $quoted_class )";
196             }
197              
198 187         696 $constructor .= <<"EOF";
199 187         469 if ( exists \$p{$key_name} && !$validator ) {
200             Carp::confess(
201             Specio::OO::_bad_value_message(
202             "The value you provided to $class->new for $key_name is not a valid $attr->{isa}.",
203 1852         4733 \$p{$key_name},
204             )
205             );
206             }
207             EOF
208             }
209              
210             if ( $attr->{does} ) {
211             my $quoted_role = perlstring( $attr->{does} );
212             $constructor .= <<"EOF";
213             if ( exists \$p{$key_name} && !Specio::TypeChecks::does_role( \$p{$key_name}, $quoted_role ) ) {
214             Carp::confess(
215 2362 100       3289 Specio::OO::_bad_value_message(
216 238         765 "The value you provided to $class->new for $key_name does not do the $attr->{does} role.",
217 238         856 \$p{$key_name},
218             )
219             );
220             }
221             EOF
222             }
223              
224             if ( $attr->{weak_ref} ) {
225             $constructor .= " Scalar::Util::weaken( \$p{$key_name} );\n";
226             }
227              
228             $constructor
229 2362 100       2959 .= " \$self->{$name} = \$p{$key_name} if exists \$p{$key_name};\n";
230 30         66  
231             $constructor .= "\n";
232             }
233              
234 2362         3535 $constructor .= ' $self->' . $_ . "(\\%p);\n" for @build_subs;
235             $constructor .= <<'EOF';
236 2362         2852  
237             return $self;
238             }
239 213         721 EOF
240 213         311  
241             {
242             ## no critic (TestingAndDebugging::ProhibitNoStrict)
243             no strict 'refs';
244             *{ $class . '::new' } = _eval_or_die(
245             $constructor, $class . '->new',
246             );
247             }
248 30     30   170 }
  30         48  
  30         13468  
  213         256  
249 213         536  
  213         1513  
250             # This used to be done with Eval::Closure but that added a lot of unneeded
251             # overhead. We're never actually eval'ing a closure, just plain source, so
252             # doing it by hand is a worthwhile optimization.
253             local $@ = undef;
254             ## no critic (Variables::RequireInitializationForLocalVars)
255             # $SIG{__DIE__} = undef causes warnings with 5.8.x
256             local $SIG{__DIE__};
257             ## no critic (BuiltinFunctions::ProhibitStringyEval)
258             my $sub = eval <<"EOF";
259 3175     3175   4094 #line 1 "$_[1]"
260             $_[0];
261             EOF
262 3175         7508 my $e = $@;
263             die $e if $e;
264 3175         531014 return $sub;
265             }
266              
267             ## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
268 3175         37151 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
269 3175 50       5960 confess shift;
270 3175         9401 }
271              
272             my $class = shift;
273              
274             return
275 0     0   0 "$class->new requires either a hashref or hash as arguments. You passed "
276 0         0 . partial_dump(@_);
277             }
278              
279             my $message = shift;
280 0     0   0 my $value = shift;
281              
282             return $message . ' You passed ' . partial_dump($value);
283 0         0 }
284             ## use critic
285              
286             my %BuiltinTypes = map { $_ => 1 } qw(
287             SCALAR
288 0     0   0 ARRAY
289 0         0 HASH
290             CODE
291 0         0 REF
292             GLOB
293             LVALUE
294             FORMAT
295             IO
296             VSTRING
297             Regexp
298             );
299              
300             my $self = shift;
301              
302             # Attributes which provide a clone method are cloned by calling that
303             # method on the _clone_ (not the original). This is primarily to allow us
304             # to clone the coercions contained by a type in a way that doesn't lead to
305             # circular clone (type clones coercions which in turn need to clone their
306             # to/from types which in turn ...).
307             my $attrs = $self->_attrs;
308             my %special = map { $_ => $attrs->{$_}{clone} }
309             grep { $attrs->{$_}{clone} } keys %{$attrs};
310 18722     18722 0 20102  
311             my $new;
312             for my $key ( keys %{$self} ) {
313             my $value = $self->{$key};
314              
315             if ( $special{$key} ) {
316             $new->{$key} = $value;
317 18722         28955 next;
318 9360         18375 }
319 18722         20070  
  169407         185691  
  18722         38839  
320             # We need to special case arrays of Specio objects, as they may
321 18722         24251 # contain code refs which cannot be cloned with dclone. Not using
322 18722         17429 # blessed is a small optimization.
  18722         36874  
323 101788         119660 if ( ( ref $value eq 'ARRAY' )
324             && all { ( ref($_) || q{} ) =~ /Specio/ } @{$value} ) {
325 101788 100       128484  
326 9360         11564 $new->{$key} = [ map { $_->clone } @{$value} ];
327 9360         11666 next;
328             }
329              
330             # This is a weird hacky way of trying to avoid calling
331             # Scalar::Util::blessed, which showed up as a hotspot in profiling of
332             # loading DateTime. That's because we call ->clone a _lot_ (it's
333 92428 100 66     133700 # called every time a type is exported).
334 279   50 5   846 my $ref = ref $value;
  474         1334  
335             $new->{$key}
336 474         529 = !$ref ? $value
  279         483  
  474         681  
337 474         1118 : $ref eq 'CODE' ? $value
338             : $BuiltinTypes{$ref} ? dclone($value)
339             : $value->clone;
340             }
341              
342             bless $new, ( ref $self );
343              
344 91954         95573 for my $key ( keys %special ) {
345             my $method = $special{$key};
346             $new->{$key} = $new->$method;
347             }
348 91954 100       171754  
    100          
    100          
349             return $new;
350             }
351              
352 18722         27702 1;
353              
354 18722         24394 # ABSTRACT: A painfully poor reimplementation of Moo(se)
355 9360         10138  
356 9360         18284  
357             =pod
358              
359 18722         36150 =encoding UTF-8
360              
361             =head1 NAME
362              
363             Specio::OO - A painfully poor reimplementation of Moo(se)
364              
365             =head1 VERSION
366              
367             version 0.48
368              
369             =head1 DESCRIPTION
370              
371             Specio can't depend on Moo or Moose, so this module provides a terrible
372             reimplementation of a small slice of their features.
373              
374             =for Pod::Coverage .*
375              
376             =head1 SUPPORT
377              
378             Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
379              
380             =head1 SOURCE
381              
382             The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
383              
384             =head1 AUTHOR
385              
386             Dave Rolsky <autarch@urth.org>
387              
388             =head1 COPYRIGHT AND LICENSE
389              
390             This software is Copyright (c) 2012 - 2022 by Dave Rolsky.
391              
392             This is free software, licensed under:
393              
394             The Artistic License 2.0 (GPL Compatible)
395              
396             The full text of the license can be found in the
397             F<LICENSE> file included with this distribution.
398              
399             =cut