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             package Specio::OO;
2              
3 29     29   204 use strict;
  29         114  
  29         842  
4 29     29   140 use warnings;
  29         55  
  29         771  
5              
6 29     29   147 use Carp qw( confess );
  29         74  
  29         1470  
7 29     29   201 use List::Util qw( all );
  29         68  
  29         3424  
8 29     29   15214 use MRO::Compat;
  29         53065  
  29         874  
9 29     29   7441 use Role::Tiny;
  29         52865  
  29         170  
10 29     29   4649 use Scalar::Util qw( weaken );
  29         72  
  29         1520  
11 29     29   4482 use Specio::Helpers qw( perlstring );
  29         73  
  29         1447  
12 29     29   13059 use Specio::PartialDump qw( partial_dump );
  29         85  
  29         2139  
13 29     29   12133 use Specio::TypeChecks;
  29         83  
  29         1369  
14 29     29   18675 use Storable qw( dclone );
  29         80699  
  29         2232  
15              
16             our $VERSION = '0.47';
17              
18 29     29   224 use Exporter qw( import );
  29         55  
  29         5585  
19              
20             ## no critic (Modules::ProhibitAutomaticExportation)
21             our @EXPORT = qw(
22             clone
23             _ooify
24             );
25             ## use critic
26              
27             ## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
28             sub _ooify {
29 207     207   468 my $class = shift;
30              
31 207         613 my $attrs = $class->_attrs;
32 207         352 for my $name ( sort keys %{$attrs} ) {
  207         1888  
33 2300         4111 my $attr = $attrs->{$name};
34              
35 2300         5037 _inline_reader( $class, $name, $attr );
36 2300         6137 _inline_predicate( $class, $name, $attr );
37             }
38              
39 207         807 _inline_constructor($class);
40             }
41             ## use critic
42              
43             sub _inline_reader {
44 2300     2300   3234 my $class = shift;
45 2300         2921 my $name = shift;
46 2300         2988 my $attr = shift;
47              
48 2300         2845 my $reader;
49 2300 100 66     7930 if ( $attr->{lazy} && ( my $builder = $attr->{builder} ) ) {
50 1000         1450 my $source = <<'EOF';
51             sub {
52             unless ( exists $_[0]->{%s} ) {
53             $_[0]->{%s} = $_[0]->%s;
54             Scalar::Util::weaken( $_[0]->{%s} ) if %s && ref $_[0]->{%s};
55             }
56             $_[0]->{%s};
57             }
58             EOF
59             $reader = sprintf(
60             $source,
61             $name,
62             $name,
63             $builder,
64             $name,
65 1000 50       6027 ( $attr->{weak_ref} ? 1 : 0 ),
66             $name,
67             $name,
68             );
69             }
70             else {
71 1300         4699 $reader = sprintf( 'sub { $_[0]->{%s} }', $name );
72             }
73              
74             {
75             ## no critic (TestingAndDebugging::ProhibitNoStrict)
76 29     29   207 no strict 'refs';
  29         57  
  29         3364  
  2300         3287  
77 2300         5867 *{ $class . '::' . $name } = _eval_or_die(
  2300         15418  
78             $reader, $class . '->' . $name,
79             );
80             }
81             }
82              
83             sub _inline_predicate {
84 2300     2300   3873 my $class = shift;
85 2300         2986 my $name = shift;
86 2300         2959 my $attr = shift;
87              
88 2300 100       6539 return unless $attr->{predicate};
89              
90 584         1356 my $predicate = "sub { exists \$_[0]->{$name} }";
91              
92             {
93             ## no critic (TestingAndDebugging::ProhibitNoStrict)
94 29     29   188 no strict 'refs';
  29         51  
  29         3735  
  584         845  
95 584         4548 *{ $class . '::' . $attr->{predicate} } = _eval_or_die(
96             $predicate, $class . '->' . $attr->{predicate},
97 584         1675 );
98             }
99             }
100              
101             my @RolesWithBUILD = qw( Specio::Constraint::Role::Interface );
102              
103             # This is an optimization to avoid calling this many times over:
104             #
105             # Specio::TypeChecks->can( 'is_' . $attr->{isa} )
106             my %TypeChecks;
107              
108             BEGIN {
109 29     29   127 for my $sub (@Specio::TypeChecks::EXPORT_OK) {
110 232 100       2543 my ($type) = $sub =~ /^is_(.+)$/
111             or next;
112 174         879 $TypeChecks{$type} = Specio::TypeChecks->can($sub);
113             }
114             }
115              
116             sub _inline_constructor {
117 207     207   375 my $class = shift;
118              
119 207         321 my @build_subs;
120 207         309 for my $parent ( @{ mro::get_linear_isa($class) } ) {
  207         1275  
121             {
122             ## no critic (TestingAndDebugging::ProhibitNoStrict)
123 29     29   181 no strict 'refs';
  29         99  
  29         11068  
  207         341  
124             push @build_subs, $parent . '::BUILD'
125 207 100       283 if defined &{ $parent . '::BUILD' };
  207         1224  
126             }
127             }
128              
129             # This is all a hack to avoid needing Class::Method::Modifiers to add a
130             # BUILD from a role. We can't just call the method in the role "BUILD" or
131             # it will be shadowed by a class's BUILD. So we give it a wacky unique
132             # name. We need to explicitly know which roles have a _X_BUILD method
133             # because Role::Tiny doesn't provide a way to list all the roles applied
134             # to a class.
135 207         451 for my $role (@RolesWithBUILD) {
136 207 100       717 if ( Role::Tiny::does_role( $class, $role ) ) {
137 120         2356 ( my $build_name = $role ) =~ s/::/_/g;
138 120         372 $build_name = q{_} . $build_name . '_BUILD';
139 120         400 push @build_subs, $role . '::' . $build_name;
140             }
141             }
142              
143 207         1634 my $constructor = <<'EOF';
144             sub {
145             my $class = shift;
146              
147             my %p = do {
148             if ( @_ == 1 ) {
149             if ( ref $_[0] eq 'HASH' ) {
150             %{ shift() };
151             }
152             else {
153             Specio::OO::_constructor_confess(
154             Specio::OO::_bad_args_message( $class, @_ ) );
155             }
156             }
157             else {
158             Specio::OO::_constructor_confess(
159             Specio::OO::_bad_args_message( $class, @_ ) )
160             if @_ % 2;
161             @_;
162             }
163             };
164              
165             my $self = bless {}, $class;
166              
167             EOF
168              
169 207         629 my $attrs = $class->_attrs;
170 207         347 for my $name ( sort keys %{$attrs} ) {
  207         1508  
171 2300         3050 my $attr = $attrs->{$name};
172 2300 100       3864 my $key_name = defined $attr->{init_arg} ? $attr->{init_arg} : $name;
173              
174 2300 100       3853 if ( $attr->{required} ) {
175 480         1120 $constructor .= <<"EOF";
176             Specio::OO::_constructor_confess(
177             "$class->new requires a $key_name argument.")
178             unless exists \$p{$key_name};
179             EOF
180             }
181              
182 2300 100 100     5501 if ( $attr->{builder} && !$attr->{lazy} ) {
183 160         285 my $builder = $attr->{builder};
184 160         568 $constructor .= <<"EOF";
185             \$p{$key_name} = $class->$builder unless exists \$p{$key_name};
186             EOF
187             }
188              
189 2300 100       3672 if ( $attr->{isa} ) {
190 1804         2357 my $validator;
191 1804 100       3287 if ( $TypeChecks{ $attr->{isa} } ) {
192             $validator
193             = 'Specio::TypeChecks::is_'
194             . $attr->{isa}
195 1622         3245 . "( \$p{$key_name} )";
196             }
197             else {
198 182         883 my $quoted_class = perlstring( $attr->{isa} );
199 182         529 $validator
200             = "Specio::TypeChecks::isa_class( \$p{$key_name}, $quoted_class )";
201             }
202              
203 1804         5511 $constructor .= <<"EOF";
204             if ( exists \$p{$key_name} && !$validator ) {
205             Carp::confess(
206             Specio::OO::_bad_value_message(
207             "The value you provided to $class->new for $key_name is not a valid $attr->{isa}.",
208             \$p{$key_name},
209             )
210             );
211             }
212             EOF
213             }
214              
215 2300 100       3825 if ( $attr->{does} ) {
216 231         954 my $quoted_role = perlstring( $attr->{does} );
217 231         1049 $constructor .= <<"EOF";
218             if ( exists \$p{$key_name} && !Specio::TypeChecks::does_role( \$p{$key_name}, $quoted_role ) ) {
219             Carp::confess(
220             Specio::OO::_bad_value_message(
221             "The value you provided to $class->new for $key_name does not do the $attr->{does} role.",
222             \$p{$key_name},
223             )
224             );
225             }
226             EOF
227             }
228              
229 2300 100       3471 if ( $attr->{weak_ref} ) {
230 29         78 $constructor .= " Scalar::Util::weaken( \$p{$key_name} );\n";
231             }
232              
233             $constructor
234 2300         4356 .= " \$self->{$name} = \$p{$key_name} if exists \$p{$key_name};\n";
235              
236 2300         3350 $constructor .= "\n";
237             }
238              
239 207         821 $constructor .= ' $self->' . $_ . "(\\%p);\n" for @build_subs;
240 207         333 $constructor .= <<'EOF';
241              
242             return $self;
243             }
244             EOF
245              
246             {
247             ## no critic (TestingAndDebugging::ProhibitNoStrict)
248 29     29   203 no strict 'refs';
  29         55  
  29         16170  
  207         291  
249 207         583 *{ $class . '::new' } = _eval_or_die(
  207         1770  
250             $constructor, $class . '->new',
251             );
252             }
253             }
254              
255             # This used to be done with Eval::Closure but that added a lot of unneeded
256             # overhead. We're never actually eval'ing a closure, just plain source, so
257             # doing it by hand is a worthwhile optimization.
258             sub _eval_or_die {
259 3091     3091   4820 local $@ = undef;
260             ## no critic (Variables::RequireInitializationForLocalVars)
261             # $SIG{__DIE__} = undef causes warnings with 5.8.x
262 3091         9124 local $SIG{__DIE__};
263             ## no critic (BuiltinFunctions::ProhibitStringyEval)
264 3091         661944 my $sub = eval <<"EOF";
265             #line 1 "$_[1]"
266             $_[0];
267             EOF
268 3091         45832 my $e = $@;
269 3091 50       7501 die $e if $e;
270 3091         11402 return $sub;
271             }
272              
273             ## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
274             sub _constructor_confess {
275 0     0   0 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
276 0         0 confess shift;
277             }
278              
279             sub _bad_args_message {
280 0     0   0 my $class = shift;
281              
282             return
283 0         0 "$class->new requires either a hashref or hash as arguments. You passed "
284             . partial_dump(@_);
285             }
286              
287             sub _bad_value_message {
288 0     0   0 my $message = shift;
289 0         0 my $value = shift;
290              
291 0         0 return $message . ' You passed ' . partial_dump($value);
292             }
293             ## use critic
294              
295             my %BuiltinTypes = map { $_ => 1 } qw(
296             SCALAR
297             ARRAY
298             HASH
299             CODE
300             REF
301             GLOB
302             LVALUE
303             FORMAT
304             IO
305             VSTRING
306             Regexp
307             );
308              
309             sub clone {
310 18320     18320 0 23912 my $self = shift;
311              
312             # Attributes which provide a clone method are cloned by calling that
313             # method on the _clone_ (not the original). This is primarily to allow us
314             # to clone the coercions contained by a type in a way that doesn't lead to
315             # circular clone (type clones coercions which in turn need to clone their
316             # to/from types which in turn ...).
317 18320         35929 my $attrs = $self->_attrs;
318 9159         22013 my %special = map { $_ => $attrs->{$_}{clone} }
319 18320         24443 grep { $attrs->{$_}{clone} } keys %{$attrs};
  165765         224421  
  18320         47838  
320              
321 18320         29696 my $new;
322 18320         21248 for my $key ( keys %{$self} ) {
  18320         43875  
323 99622         144812 my $value = $self->{$key};
324              
325 99622 100       154892 if ( $special{$key} ) {
326 9159         14216 $new->{$key} = $value;
327 9159         14023 next;
328             }
329              
330             # We need to special case arrays of Specio objects, as they may
331             # contain code refs which cannot be cloned with dclone. Not using
332             # blessed is a small optimization.
333 90463 100 66     161151 if ( ( ref $value eq 'ARRAY' )
334 279   50 5   1078 && all { ( ref($_) || q{} ) =~ /Specio/ } @{$value} ) {
  474         1786  
335              
336 474         623 $new->{$key} = [ map { $_->clone } @{$value} ];
  279         557  
  474         1134  
337 474         1440 next;
338             }
339              
340             # This is a weird hacky way of trying to avoid calling
341             # Scalar::Util::blessed, which showed up as a hotspot in profiling of
342             # loading DateTime. That's because we call ->clone a _lot_ (it's
343             # called every time a type is exported).
344 89989         116698 my $ref = ref $value;
345             $new->{$key}
346             = !$ref ? $value
347             : $ref eq 'CODE' ? $value
348 89989 100       211050 : $BuiltinTypes{$ref} ? dclone($value)
    100          
    100          
349             : $value->clone;
350             }
351              
352 18320         34041 bless $new, ( ref $self );
353              
354 18320         29875 for my $key ( keys %special ) {
355 9159         12484 my $method = $special{$key};
356 9159         22108 $new->{$key} = $new->$method;
357             }
358              
359 18320         42825 return $new;
360             }
361              
362             1;
363              
364             # ABSTRACT: A painfully poor reimplementation of Moo(se)
365              
366             __END__
367              
368             =pod
369              
370             =encoding UTF-8
371              
372             =head1 NAME
373              
374             Specio::OO - A painfully poor reimplementation of Moo(se)
375              
376             =head1 VERSION
377              
378             version 0.47
379              
380             =head1 DESCRIPTION
381              
382             Specio can't depend on Moo or Moose, so this module provides a terrible
383             reimplementation of a small slice of their features.
384              
385             =for Pod::Coverage .*
386              
387             =head1 SUPPORT
388              
389             Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
390              
391             I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
392              
393             =head1 SOURCE
394              
395             The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
396              
397             =head1 AUTHOR
398              
399             Dave Rolsky <autarch@urth.org>
400              
401             =head1 COPYRIGHT AND LICENSE
402              
403             This software is Copyright (c) 2012 - 2021 by Dave Rolsky.
404              
405             This is free software, licensed under:
406              
407             The Artistic License 2.0 (GPL Compatible)
408              
409             The full text of the license can be found in the
410             F<LICENSE> file included with this distribution.
411              
412             =cut