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 28     28   157 use strict;
  28         48  
  28         712  
4 28     28   112 use warnings;
  28         46  
  28         653  
5              
6 28     28   116 use Carp qw( confess );
  28         51  
  28         1265  
7 28     28   163 use List::Util qw( all );
  28         44  
  28         1538  
8 28     28   12095 use MRO::Compat;
  28         43626  
  28         797  
9 28     28   5849 use Role::Tiny;
  28         43678  
  28         144  
10 28     28   3989 use Scalar::Util qw( weaken );
  28         53  
  28         1359  
11 28     28   3613 use Specio::Helpers qw( perlstring );
  28         54  
  28         1273  
12 28     28   10904 use Specio::PartialDump qw( partial_dump );
  28         74  
  28         1852  
13 28     28   10111 use Specio::TypeChecks;
  28         74  
  28         1194  
14 28     28   15302 use Storable qw( dclone );
  28         68609  
  28         1942  
15              
16             our $VERSION = '0.46';
17              
18 28     28   182 use Exporter qw( import );
  28         48  
  28         4633  
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 194     194   389 my $class = shift;
30              
31 194         488 my $attrs = $class->_attrs;
32 194         292 for my $name ( sort keys %{$attrs} ) {
  194         1515  
33 2132         3319 my $attr = $attrs->{$name};
34              
35 2132         4021 _inline_reader( $class, $name, $attr );
36 2132         4851 _inline_predicate( $class, $name, $attr );
37             }
38              
39 194         622 _inline_constructor($class);
40             }
41             ## use critic
42              
43             sub _inline_reader {
44 2132     2132   2608 my $class = shift;
45 2132         2404 my $name = shift;
46 2132         2393 my $attr = shift;
47              
48 2132         2319 my $reader;
49 2132 100 66     6649 if ( $attr->{lazy} && ( my $builder = $attr->{builder} ) ) {
50 919         1230 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 919 50       4754 ( $attr->{weak_ref} ? 1 : 0 ),
66             $name,
67             $name,
68             );
69             }
70             else {
71 1213         3827 $reader = sprintf( 'sub { $_[0]->{%s} }', $name );
72             }
73              
74             {
75             ## no critic (TestingAndDebugging::ProhibitNoStrict)
76 28     28   166 no strict 'refs';
  28         46  
  28         3132  
  2132         2743  
77 2132         4628 *{ $class . '::' . $name } = _eval_or_die(
  2132         12422  
78             $reader, $class . '->' . $name,
79             );
80             }
81             }
82              
83             sub _inline_predicate {
84 2132     2132   3156 my $class = shift;
85 2132         2438 my $name = shift;
86 2132         2359 my $attr = shift;
87              
88 2132 100       5296 return unless $attr->{predicate};
89              
90 546         1124 my $predicate = "sub { exists \$_[0]->{$name} }";
91              
92             {
93             ## no critic (TestingAndDebugging::ProhibitNoStrict)
94 28     28   156 no strict 'refs';
  28         45  
  28         3184  
  546         700  
95 546         3854 *{ $class . '::' . $attr->{predicate} } = _eval_or_die(
96             $predicate, $class . '->' . $attr->{predicate},
97 546         1318 );
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 28     28   110 for my $sub (@Specio::TypeChecks::EXPORT_OK) {
110 224 100       2317 my ($type) = $sub =~ /^is_(.+)$/
111             or next;
112 168         768 $TypeChecks{$type} = Specio::TypeChecks->can($sub);
113             }
114             }
115              
116             sub _inline_constructor {
117 194     194   304 my $class = shift;
118              
119 194         281 my @build_subs;
120 194         284 for my $parent ( @{ mro::get_linear_isa($class) } ) {
  194         1041  
121             {
122             ## no critic (TestingAndDebugging::ProhibitNoStrict)
123 28     28   163 no strict 'refs';
  28         42  
  28         9742  
  194         303  
124             push @build_subs, $parent . '::BUILD'
125 194 100       233 if defined &{ $parent . '::BUILD' };
  194         1010  
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 194         375 for my $role (@RolesWithBUILD) {
136 194 100       555 if ( Role::Tiny::does_role( $class, $role ) ) {
137 110         1912 ( my $build_name = $role ) =~ s/::/_/g;
138 110         304 $build_name = q{_} . $build_name . '_BUILD';
139 110         339 push @build_subs, $role . '::' . $build_name;
140             }
141             }
142              
143 194         1352 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 194         537 my $attrs = $class->_attrs;
170 194         282 for my $name ( sort keys %{$attrs} ) {
  194         1244  
171 2132         2597 my $attr = $attrs->{$name};
172 2132 100       3201 my $key_name = defined $attr->{init_arg} ? $attr->{init_arg} : $name;
173              
174 2132 100       3139 if ( $attr->{required} ) {
175 450         967 $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 2132 100 100     4643 if ( $attr->{builder} && !$attr->{lazy} ) {
183 144         236 my $builder = $attr->{builder};
184 144         466 $constructor .= <<"EOF";
185             \$p{$key_name} = $class->$builder unless exists \$p{$key_name};
186             EOF
187             }
188              
189 2132 100       3069 if ( $attr->{isa} ) {
190 1668         1678 my $validator;
191 1668 100       2693 if ( $TypeChecks{ $attr->{isa} } ) {
192             $validator
193             = 'Specio::TypeChecks::is_'
194             . $attr->{isa}
195 1499         2814 . "( \$p{$key_name} )";
196             }
197             else {
198 169         674 my $quoted_class = perlstring( $attr->{isa} );
199 169         429 $validator
200             = "Specio::TypeChecks::isa_class( \$p{$key_name}, $quoted_class )";
201             }
202              
203 1668         4442 $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 2132 100       3153 if ( $attr->{does} ) {
216 219         776 my $quoted_role = perlstring( $attr->{does} );
217 219         848 $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 2132 100       3016 if ( $attr->{weak_ref} ) {
230 28         91 $constructor .= " Scalar::Util::weaken( \$p{$key_name} );\n";
231             }
232              
233             $constructor
234 2132         3585 .= " \$self->{$name} = \$p{$key_name} if exists \$p{$key_name};\n";
235              
236 2132         2805 $constructor .= "\n";
237             }
238              
239 194         701 $constructor .= ' $self->' . $_ . "(\\%p);\n" for @build_subs;
240 194         272 $constructor .= <<'EOF';
241              
242             return $self;
243             }
244             EOF
245              
246             {
247             ## no critic (TestingAndDebugging::ProhibitNoStrict)
248 28     28   172 no strict 'refs';
  28         44  
  28         13654  
  194         248  
249 194         499 *{ $class . '::new' } = _eval_or_die(
  194         1522  
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 2872     2872   4068 local $@ = undef;
260             ## no critic (Variables::RequireInitializationForLocalVars)
261             # $SIG{__DIE__} = undef causes warnings with 5.8.x
262 2872         7501 local $SIG{__DIE__};
263             ## no critic (BuiltinFunctions::ProhibitStringyEval)
264 2872         530987 my $sub = eval <<"EOF";
265             #line 1 "$_[1]"
266             $_[0];
267             EOF
268 2872         37374 my $e = $@;
269 2872 50       6124 die $e if $e;
270 2872         9382 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 17218     17218 0 20010 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 17218         28966 my $attrs = $self->_attrs;
318 8608         18148 my %special = map { $_ => $attrs->{$_}{clone} }
319 17218         19729 grep { $attrs->{$_}{clone} } keys %{$attrs};
  155765         186519  
  17218         38896  
320              
321 17218         24855 my $new;
322 17218         18077 for my $key ( keys %{$self} ) {
  17218         36529  
323 93672         119033 my $value = $self->{$key};
324              
325 93672 100       128395 if ( $special{$key} ) {
326 8608         11850 $new->{$key} = $value;
327 8608         12060 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 85064 100 66     133038 if ( ( ref $value eq 'ARRAY' )
334 279   50 5   833 && all { ( ref($_) || q{} ) =~ /Specio/ } @{$value} ) {
  474         1306  
335              
336 474         502 $new->{$key} = [ map { $_->clone } @{$value} ];
  279         464  
  474         703  
337 474         1127 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 84590         96106 my $ref = ref $value;
345             $new->{$key}
346             = !$ref ? $value
347             : $ref eq 'CODE' ? $value
348 84590 100       171007 : $BuiltinTypes{$ref} ? dclone($value)
    100          
    100          
349             : $value->clone;
350             }
351              
352 17218         28206 bless $new, ( ref $self );
353              
354 17218         24738 for my $key ( keys %special ) {
355 8608         10266 my $method = $special{$key};
356 8608         17695 $new->{$key} = $new->$method;
357             }
358              
359 17218         35398 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.46
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 - 2020 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