File Coverage

blib/lib/Hash/Wrap.pm
Criterion Covered Total %
statement 503 524 95.9
branch 202 252 80.1
condition 57 92 61.9
subroutine 107 110 97.2
pod n/a
total 869 978 88.8


line stmt bran cond sub pod time code
1             package Hash::Wrap;
2              
3             # ABSTRACT: create on-the-fly objects from hashes
4              
5 21     21   5019667 use 5.01000;
  21         85  
6              
7 21     20   431 use strict;
  20         138  
  20         615  
8 20     20   115 use warnings;
  20         33  
  20         1106  
9              
10 20     20   109 use Scalar::Util;
  20         59  
  20         1138  
11 20     20   112 use Digest::MD5;
  20         36  
  20         7020  
12             our $VERSION = '1.07';
13              
14             our @EXPORT = qw[ wrap_hash ];
15              
16             our @CARP_NOT = qw( Hash::Wrap );
17             our $DEBUG = 0;
18              
19             # copied from Damian Conway's PPR: PerlIdentifier
20 20     20   156 use constant PerlIdentifier => qr/\A([^\W\d]\w*+)\z/;
  20         38  
  20         5519  
21              
22             our %REGISTRY;
23              
24             sub _croak {
25 26     26   176 require Carp;
26 26         9218 goto \&Carp::croak;
27             }
28              
29             sub _croak_class_method {
30 0     0   0 my ( $class, $method ) = @_;
31 0   0     0 $class = ref( $class ) || $class;
32 0         0 _croak( qq[Can't locate class method "$method" via package "$class"] );
33             }
34              
35             sub _croak_object_method {
36 11     11   26 my ( $object, $method ) = @_;
37 11   33     46 my $class = Scalar::Util::blessed( $object ) || ref( $object ) || $object;
38 11         84 _croak( qq[Can't locate object method "$method" via package "$class"] );
39             }
40              
41             sub _find_symbol {
42 115     115   242 my ( $package, $symbol, $reftype ) = @_;
43              
44 20     20   132 no strict 'refs'; ## no critic (ProhibitNoStrict)
  20         61  
  20         14159  
45 115         145 my $candidate = *{"$package\::$symbol"}{SCALAR};
  115         421  
46              
47             return $$candidate
48             if defined $candidate
49 115 100 66     582 && 2 == grep { defined $_->[0] && defined $_->[1] ? $_->[0] eq $_->[1] : 1 }
  230 50 33     1254  
50             [ $reftype->[0], Scalar::Util::reftype $candidate ],
51             [ $reftype->[1], Scalar::Util::reftype $$candidate ];
52              
53 0         0 _croak( "Unable to find scalar \$$symbol in class $package" );
54             }
55              
56             # this is called only if the method doesn't exist.
57             sub _generate_accessor {
58 90     90   221 my ( $hash_class, $class, $key ) = @_;
59              
60 90         335 my %dict = (
61             key => $key,
62             class => $class,
63             );
64              
65 90         225 my $code = $REGISTRY{$hash_class}{accessor_template};
66 90         253 my $coderef = _compile_from_tpl( \$code, \%dict );
67 90 50       301 _croak_about_code( \$code, 'accessor' )
68             if $@;
69              
70 90         2272 return $coderef;
71             }
72              
73             sub _generate_predicate {
74 4     4   12 my ( $hash_class, $class, $key ) = @_;
75              
76 4         28 my %dict = (
77             key => $key,
78             class => $class,
79             );
80              
81 4         9 my $code = $REGISTRY{$hash_class}{predicate_template};
82 4         10 my $coderef = _compile_from_tpl( \$code, \%dict );
83 4 50       9 _croak_about_code( \$code, 'predicate' )
84             if $@;
85              
86 4         83 return $coderef;
87             }
88              
89             sub _autoload {
90 75     75   203 my ( $hash_class, $method, $object ) = @_;
91              
92 75         543 my ( $class, $key ) = $method =~ /(.*)::(.*)/;
93              
94 75 50       263 _croak_class_method( $object, $key )
95             unless Scalar::Util::blessed( $object );
96              
97 75 100 66     318 if ( exists $REGISTRY{$hash_class}{predicate_template}
98             && $key =~ /^has_(.*)/ )
99             {
100 4         8 return _generate_predicate( $hash_class, $class, $1 );
101             }
102              
103             _croak_object_method( $object, $key )
104 71 100       1583 unless $REGISTRY{$hash_class}{validate}->( $object, $key );
105              
106 63         186 _generate_accessor( $hash_class, $class, $key );
107             }
108              
109             sub _can {
110 58     58   167 my ( $self, $key, $CLASS ) = @_;
111              
112 58         119 my $class = Scalar::Util::blessed( $self );
113 58 50       158 return () if !defined $class;
114              
115 58 100       204 if ( !exists $self->{$key} ) {
116              
117 26 100       75 if ( exists $Hash::Wrap::REGISTRY{$class}{methods}{$key} ) {
118             ## no critic (ProhibitNoStrict)
119 20     20   175 no strict 'refs';
  20         42  
  20         2135  
120 12         20 my $method = "${class}::$key";
121 12         13 return *{$method}{CODE};
  12         48  
122             }
123 14         572 return ();
124             }
125              
126 32         54 my $method = "${class}::$key";
127              
128             ## no critic (ProhibitNoStrict PrivateSubs)
129 20     20   113 no strict 'refs';
  20         33  
  20         34652  
130             return *{$method}{CODE}
131 32   66     36 || Hash::Wrap::_generate_accessor( $CLASS, $class, $key );
132             }
133              
134             sub import { ## no critic(ExcessComplexity)
135 68     68   903262 shift;
136              
137 68         173 my @imports = @_;
138 68 100       246 push @imports, @EXPORT unless @imports;
139              
140 68         102 my @return;
141              
142 68         200 for my $args ( @imports ) {
143 72 100       343 if ( !ref $args ) {
    50          
144             _croak( "$args is not exported by ", __PACKAGE__ )
145 5 100       16 unless grep { /$args/ } @EXPORT; ## no critic (BooleanGrep)
  5         129  
146              
147 4         14 $args = { -as => $args };
148             }
149              
150             elsif ( 'HASH' ne ref $args ) {
151             _croak( 'argument to ', __PACKAGE__, '::import must be string or hash' )
152 0 0       0 unless grep { /$args/ } @EXPORT; ## no critic (BooleanGrep)
  0         0  
153             }
154             else {
155             # make a copy as it gets modified later on
156 67         241 $args = {%$args};
157             }
158              
159             _croak( 'cannot mix -base and -class' )
160 71 100 100     292 if !!$args->{-base} && exists $args->{-class};
161              
162 70   33     396 $DEBUG = $ENV{HASH_WRAP_DEBUG} // delete $args->{-debug};
163              
164             # -as may be explicitly 'undef' to indicate use in a standalone class
165 70 100       221 $args->{-as} = 'wrap_hash' unless exists $args->{-as};
166 70         156 my $name = delete $args->{-as};
167              
168 70   66     340 my $target = delete $args->{-into} // caller;
169              
170 70 100       183 if ( defined $name ) {
171              
172 69 100       256 if ( defined( my $reftype = Scalar::Util::reftype( $name ) ) ) {
    100          
173 9 50 66     84 _croak( '-as must be undefined or a string or a reference to a scalar' )
      100        
      66        
      66        
      33        
174             if $reftype ne 'SCALAR'
175             && $reftype ne 'VSTRING'
176             && $reftype ne 'REF'
177             && $reftype ne 'GLOB'
178             && $reftype ne 'LVALUE'
179             && $reftype ne 'REGEXP';
180              
181 8         38 $args->{-as_scalar_ref} = $name;
182              
183             }
184              
185             elsif ( $name eq '-return' ) {
186 20         41 $args->{-as_return} = 1;
187             }
188             }
189              
190 69 100       151 if ( $args->{-base} ) {
191             _croak( q{don't use -as => -return with -base} )
192 3 50       10 if $args->{-as_return};
193 3         7 $args->{-class} = $target;
194 3 50       14 $args->{-new} = 1 if !exists $args->{-new};
195 3         11 _build_class( $target, $name, $args );
196             }
197              
198             else {
199 66         198 _build_class( $target, $name, $args );
200 61 100       131 if ( defined $name ) {
201 60         165 my $sub = _build_constructor( $target, $name, $args );
202 58 100       196 push @return, $sub if $args->{-as_return};
203             }
204             }
205              
206             # clean out known attributes
207 62         260 delete @{$args}{
208 62         141 qw[ -as -as_return -as_scalar_ref -base -class -clone
209             -copy -defined -exists -immutable -lockkeys -lvalue
210             -methods -new -predicate -recurse -undef ]
211             };
212              
213 62 100       223 if ( keys %$args ) {
214 1         10 _croak( 'unknown options passed to ', __PACKAGE__, '::import: ', join( ', ', keys %$args ) );
215             }
216             }
217              
218 57         32274 return @return;
219             }
220              
221             sub _build_class { ## no critic(ExcessComplexity)
222 69     69   169 my ( $target, $name, $attr ) = @_;
223              
224             # in case we're called inside a recursion and the recurse count
225             # has hit zero, default behavior is no recurse, so remove it so
226             # the attr signature computed below isn't contaminated by a
227             # useless -recurse => 0 attribute.
228 69 100       229 if ( exists $attr->{-recurse} ) {
229             _croak( '-recurse must be a number' )
230 24 100       130 unless Scalar::Util::looks_like_number( $attr->{-recurse} );
231 23 100       61 delete $attr->{-recurse} if $attr->{-recurse} == 0;
232             }
233              
234 68 100 100     249 if ( !defined $attr->{-class} ) {
    100          
235              
236             ## no critic (ComplexMappings)
237             my @class = map {
238 52         225 ( my $key = $_ ) =~ s/-//;
  108         321  
239 108 50       365 ( $key, defined $attr->{$_} ? $attr->{$_} : '' )
240             } sort keys %$attr;
241              
242 52         472 $attr->{-class} = join q{::}, 'Hash::Wrap::Class', Digest::MD5::md5_hex( @class );
243             }
244              
245             elsif ( $attr->{-class} eq '-target' || $attr->{-class} eq '-caller' ) {
246 3 100       11 _croak( "can't set -class => '@{[ $attr->{-class} ]}' if '-as' is not a plain string" )
  1         7  
247             if ref $name;
248 2         7 $attr->{-class} = $target . q{::} . $name;
249             }
250              
251 67         133 my $class = $attr->{-class};
252              
253 67 100       195 return $class if defined $REGISTRY{$class};
254 60         232 my $rentry = $REGISTRY{$class} = { methods => {} };
255              
256 60         146 my %closures;
257             my @BODY;
258 60         511 my %dict = (
259             class => $class,
260             signature => q{},
261             body => \@BODY,
262             autoload_attr => q{},
263             validate_inline => 'exists $self->{\<>}',
264             validate_method => 'exists $self->{$key}',
265             set => '$self->{q[\<>]} = $_[0] if @_;',
266             return_value => '$self->{q[\<>]}',
267             recursion_constructor => q{},
268             predicate_template => q{},
269             );
270              
271 60 100       194 if ( $attr->{-lvalue} ) {
272 5 50       17 if ( $] lt '5.016000' ) {
273             _croak( 'lvalue accessors require Perl 5.16 or later' )
274 0 0       0 if $attr->{-lvalue} < 0;
275             }
276             else {
277 5         10 $dict{autoload_attr} = q[: lvalue];
278 5         9 $dict{signature} = q[: lvalue];
279             }
280             }
281              
282 60 100       155 if ( $attr->{-undef} ) {
283 7         12 $dict{validate_method} = q[ 1 ];
284 7         14 $dict{validate_inline} = q[ 1 ];
285             }
286              
287 60 100       142 if ( $attr->{-exists} ) {
288 14 100       92 $dict{exists} = $attr->{-exists} =~ PerlIdentifier ? $1 : 'exists';
289 14         24 push @BODY, q[ sub <> { exists $_[0]->{$_[1] } } ];
290 14         33 $rentry->{methods}{ $dict{exists} } = undef;
291             }
292              
293 60 100       142 if ( $attr->{-defined} ) {
294 2 100       14 $dict{defined} = $attr->{-defined} =~ PerlIdentifier ? $1 : 'defined';
295 2         3 push @BODY, q[ sub <> { defined $_[0]->{$_[1] } } ];
296 2         6 $rentry->{methods}{ $dict{defined} } = undef;
297             }
298              
299 60 100       137 if ( $attr->{-immutable} ) {
300 5         13 $dict{set} = <<'END';
301             Hash::Wrap::_croak( q[Modification of a read-only value attempted])
302             if @_;
303             END
304             }
305              
306 60 100       135 if ( $attr->{-recurse} ) {
307              
308             # decrement recursion limit. It's infinite recursion if
309             # -recurse < 0; always set to -1 so we keep using the same
310             # class. Note that -recurse will never be zero upon entrance
311             # of this block, as -recurse => 0 is removed from the
312             # attributes way upstream.
313              
314 13 100       47 $dict{recurse_limit} = --$attr->{-recurse} < 0 ? -1 : $attr->{-recurse};
315              
316 13         18 $dict{quoted_key} = 'q[\<>]';
317 13         26 $dict{hash_value} = '$self->{<>}';
318              
319 13         21 $dict{recurse_wrap_hash} = '$<>::recurse_into_hash->( <> )';
320              
321 13         18 $dict{return_value} = <<'END';
322             'HASH' eq (Scalar::Util::reftype( <> ) // q{})
323             && ! Scalar::Util::blessed( <> )
324             ? <>
325             : <>;
326             END
327 13 100       21 if ( $attr->{-copy} ) {
328              
329 4 100       10 if ( $attr->{-immutable} ) {
330 3         7 $dict{wrap_hash_entry} = <<'END';
331             do { Hash::Util::unlock_ref_value( $self, <> );
332             <> = <>;
333             Hash::Util::lock_ref_value( $self, <> );
334             <>;
335             }
336             END
337             }
338             else {
339 1         1 $dict{wrap_hash_entry} = '<> = <>';
340             }
341              
342             }
343             else {
344 9         12 $dict{wrap_hash_entry} = '<>';
345             }
346              
347             # do a two-step initialization of the constructor. If
348             # the initialization sub is stored in $recurse_into_hash, and then
349             # $recurse_into_hash is set to the actual constructor I worry that
350             # Perl may decide to garbage collect the setup subroutine while it's
351             # busy setting $recurse_into_hash. So, store the
352             # initialization sub in something other than $recurse_into_hash.
353              
354 13         20 $dict{recursion_constructor} = <<'END';
355             our $recurse_into_hash;
356             our $setup_recurse_into_hash = sub {
357             require Hash::Wrap;
358             ( $recurse_into_hash ) = Hash::Wrap->import ( { %$attr, -as => '-return',
359             -recurse => <> } );
360             goto &$recurse_into_hash;
361             };
362             $recurse_into_hash = $setup_recurse_into_hash;
363             END
364              
365 13 100       66 my %attr = ( %$attr, -recurse => --$attr->{-recurse} < 0 ? -1 : $attr->{-recurse}, );
366 13         38 delete @attr{qw( -as_scalar_ref -class -base -as )};
367 13         27 $closures{'$attr'} = \%attr;
368             }
369              
370 60 100       142 if ( $attr->{-predicate} ) {
371 1         3 $dict{predicate_template} = <<'END';
372             our $predicate_template = q[
373             package \<>;
374              
375             use Scalar::Util ();
376              
377             sub has_\<> {
378             my $self = shift;
379              
380             Hash::Wrap::_croak_class_method( $self, 'has_\<>' )
381             unless Scalar::Util::blessed( $self );
382              
383             return exists $self->{\<>};
384             }
385              
386             $Hash::Wrap::REGISTRY{methods}{'has_\<>'} = undef;
387              
388             \&has_\<>;
389             ];
390             END
391             }
392              
393 60         117 my $class_template = <<'END';
394             package <>;
395              
396             <>
397              
398             use Scalar::Util ();
399              
400             our $validate = sub {
401             my ( $self, $key ) = @_;
402             return <>;
403             };
404              
405             <>
406              
407             our $accessor_template = q[
408             package \<>;
409              
410             use Scalar::Util ();
411              
412             sub \<> <> {
413             my $self = shift;
414              
415             Hash::Wrap::_croak_class_method( $self, '\<>' )
416             unless Scalar::Util::blessed( $self );
417              
418             Hash::Wrap::_croak_object_method( $self, '\<>' )
419             unless ( <> );
420              
421             <>
422              
423             return <>;
424             }
425             \&\<>;
426             ];
427              
428             <>
429              
430              
431             <>
432              
433             our $AUTOLOAD;
434             sub AUTOLOAD <> {
435             goto &{ Hash::Wrap::_autoload( q[<>], $AUTOLOAD, $_[0] ) };
436             }
437              
438             sub DESTROY { }
439              
440             sub can {
441             return Hash::Wrap::_can( @_, q[<>] );
442             }
443              
444             1;
445             END
446              
447 60 100       273 _compile_from_tpl( \$class_template, \%dict, keys %closures ? \%closures : () )
    50          
448             or _croak_about_code( \$class_template, "class $class" );
449              
450 60 100       254 if ( !!$attr->{-new} ) {
451 5 50       34 my $lname = $attr->{-new} =~ PerlIdentifier ? $1 : 'new';
452 5         38 _build_constructor( $class, $lname, { %$attr, -as_method => 1 } );
453             }
454              
455 60 100       174 if ( $attr->{-methods} ) {
456              
457 16         22 my $methods = $attr->{-methods};
458 16 100       58 _croak( '-methods option value must be a hashref' )
459             unless 'HASH' eq ref $methods;
460              
461 15         46 for my $mth ( keys %$methods ) {
462 16 100       89 _croak( "method name '$mth' is not a valid Perl identifier" )
463             if $mth !~ PerlIdentifier;
464              
465 15         22 my $code = $methods->{$mth};
466 15 100       31 _croak( qq{value for method "$mth" must be a coderef} )
467             unless 'CODE' eq ref $code;
468 20     20   168 no strict 'refs'; ## no critic (ProhibitNoStrict)
  20         39  
  20         41694  
469 14         16 *{"${class}::${mth}"} = $code;
  14         76  
470             }
471              
472 13         49 $rentry->{methods}{$_} = undef for keys %$methods;
473             }
474              
475 57         147 push @CARP_NOT, $class;
476             $rentry->{accessor_template}
477 57         209 = _find_symbol( $class, 'accessor_template', [ 'SCALAR', undef ] );
478              
479 57 100       208 if ( $attr->{-predicate} ) {
480             $rentry->{predicate_template}
481 1         4 = _find_symbol( $class, 'predicate_template', [ 'SCALAR', undef ] );
482             }
483              
484 57         145 $rentry->{validate} = _find_symbol( $class, 'validate', [ 'REF', 'CODE' ] );
485              
486 57         166 Scalar::Util::weaken( $rentry->{validate} );
487              
488 57         292 return $class;
489             }
490              
491             sub _build_constructor { ## no critic (ExcessComplexity)
492 65     65   149 my ( $package, $name, $args ) = @_;
493              
494             # closure for user provided clone sub
495 65         98 my %closures;
496              
497             _croak( 'cannot mix -copy and -clone' )
498 65 100 100     184 if exists $args->{-copy} && exists $args->{-clone};
499              
500 64         83 my @USE;
501 64         316 my %dict = (
502             package => $package,
503             constructor_name => $name,
504             use => \@USE,
505             package_return_value => '1;',
506             );
507              
508 64         125 $dict{class} = do {
509 64 100       156 if ( $args->{-as_method} ) {
510 5         15 'shift;';
511             }
512             else {
513              
514 59         180 'q[' . $args->{-class} . '];';
515             }
516             };
517              
518 64         146 my @copy = (
519             'Hash::Wrap::_croak(q{the argument to <>::<> must not be an object})',
520             ' if Scalar::Util::blessed( $hash );',
521             );
522              
523 64 100       204 if ( $args->{-copy} ) {
    100          
524 9         15 push @copy, '$hash = { %{ $hash } };';
525             }
526              
527             elsif ( exists $args->{-clone} ) {
528              
529 3 100       14 if ( 'CODE' eq ref $args->{-clone} ) {
530 1         4 $closures{'clone'} = $args->{-clone};
531             # overwrite @copy, as the clone sub could take an object.
532 1         5 @copy = (
533             'state $clone = $CLOSURES->{clone};',
534             '$hash = $clone->($hash);',
535             'Hash::Wrap::_croak(q{the custom clone routine for <> returned an object instead of a plain hash})',
536             ' if Scalar::Util::blessed( $hash );',
537             );
538             }
539             else {
540 2         6 push @USE, q[use Storable ();];
541 2         28 push @copy, '$hash = Storable::dclone $hash;';
542             }
543             }
544              
545 64         229 $dict{copy} = join "\n", @copy;
546              
547 64         117 $dict{lock} = do {
548 64         104 my @eval;
549              
550 64 100 100     479 if ( defined( my $opts = $args->{-immutable} || undef ) ) {
    100 100        
551              
552 8         15 push @USE, q[use Hash::Util ();];
553              
554 8 100       22 if ( 'ARRAY' eq ref $opts ) {
555             _croak( "-immutable: attribute name ($_) is not a valid Perl identifier" )
556 1         3 for grep { $_ !~ PerlIdentifier } @{$opts};
  2         13  
  1         4  
557              
558             push @eval,
559 1         3 'Hash::Util::lock_keys_plus(%$hash, qw{ ' . join( q{ }, @{$opts} ) . ' });',
  1         4  
560             '@{$hash}{Hash::Util::hidden_keys(%$hash)} = ();',
561             ;
562             }
563              
564 8         12 push @eval, 'Hash::Util::lock_hash(%$hash)';
565             }
566             elsif ( defined( $opts = $args->{-lockkeys} || undef ) ) {
567              
568 3         8 push @USE, q[use Hash::Util ();];
569              
570 3 100       12 if ( 'ARRAY' eq ref $args->{-lockkeys} ) {
    50          
571             _croak( "-lockkeys: attribute name ($_) is not a valid Perl identifier" )
572 2         5 for grep { $_ !~ PerlIdentifier } @{ $args->{-lockkeys} };
  4         25  
  2         7  
573              
574             push @eval,
575 1         3 'Hash::Util::lock_keys_plus(%$hash, qw{ ' . join( q{ }, @{ $args->{-lockkeys} } ) . ' });';
  1         5  
576             }
577             elsif ( $args->{-lockkeys} ) {
578              
579 1         2 push @eval, 'Hash::Util::lock_keys(%$hash)';
580             }
581             }
582              
583 63         184 join( "\n", @eval );
584              
585             };
586              
587             # return the constructor sub from the factory and don't insert the
588             # name into the package namespace
589 63 100 100     283 if ( $args->{-as_scalar_ref} || $args->{-as_return} ) {
590 27         41 $dict{package_return_value} = q{};
591 27         60 $dict{constructor_name} = q{};
592             }
593              
594             #<<< no tidy
595 63         126 my $code = <<'ENDCODE';
596             package <>;
597              
598             <>
599             use Scalar::Util ();
600              
601             no warnings 'redefine';
602              
603             sub <> (;$) {
604             my $class = <>
605             my $hash = shift // {};
606              
607             Hash::Wrap::_croak( 'argument to <>::<> must be a hashref' )
608             if 'HASH' ne Scalar::Util::reftype($hash);
609             <>
610             bless $hash, $class;
611             <>
612             }
613             <>
614              
615             ENDCODE
616             #>>>
617              
618 63   33     197 my $result = _compile_from_tpl( \$code, \%dict, keys %closures ? \%closures : () )
619             || _croak_about_code( \$code, "constructor (as $name) subroutine" );
620              
621             # caller asked for a coderef to be stuffed into a scalar
622 63 100       234 ${$name} = $result if $args->{-as_scalar_ref};
  7         20  
623 63         364 return $result;
624             }
625              
626             sub _croak_about_code {
627 0     0   0 my ( $code, $what, $error ) = @_;
628 0   0     0 $error //= $@;
629 0         0 _line_number_code( $code );
630 0         0 _croak( qq[error compiling $what: $error\n$$code] );
631             }
632              
633             sub _line_number_code {
634 0     0   0 my ( $code ) = @_;
635 0         0 chomp( $$code );
636 0         0 $$code .= "\n";
637 0         0 my $space = length( $$code =~ tr/\n// );
638 0         0 my $line = 0;
639 0         0 $$code =~ s/^/sprintf "%${space}d: ", ++$line/emg;
  0         0  
640             }
641              
642             sub _compile_from_tpl {
643 217     217   445 my ( $code, $dict, $closures ) = @_;
644              
645 217 100 66     545 if ( defined $closures && %$closures ) {
646              
647             # add code to create lexicals if the keys begin with a q{$}
648             $dict->{closures} = join( "\n",
649 13         42 map { "my $_ = \$CLOSURES->{'$_'};" }
650 14         28 grep { substr( $_, 0, 1 ) eq q{$} }
  14         48  
651             keys %$closures );
652             }
653              
654 217         516 _interpolate( $code, $dict );
655              
656 217 50       468 if ( $DEBUG ) {
657 0         0 my $lcode = $$code;
658 0         0 _line_number_code( \$lcode );
659 0         0 print STDERR $lcode; ## no critic (CheckedSyscalls)
660             }
661              
662 217 100       571 _clean_eval( $code, exists $dict->{closures} ? $closures : () );
663              
664             }
665              
666             # eval in a clean lexical space.
667             sub _clean_eval {
668             ## no critic (StringyEval RequireCheckingReturnValueOfEval )
669 217 100   217   440 if ( @_ > 1 ) {
670             ## no critic (UnusedVars)
671 14         21 my $CLOSURES = $_[1];
672 14     1   15 eval( ${ $_[0] } );
  14     1   1565  
  1         1  
  1         139  
  1         6  
  1         1  
  1         153  
  1         6  
673             }
674             else {
675 203 100 100 59   253 eval( ${ $_[0] } );
  203 100 100 30   20911  
  39 50 66 24   469215  
  31 100 66 22   2950  
  31 100 33 19   11212  
  62 50 66 16   2823  
  62 100 33 16   9644  
  56 100 33 12   267896  
  54 50 50 14   5717  
  53 50 50 10   293  
  59 100   10   3386  
  57 100   9   390  
  53 100   5   308489  
  53 100   5   3676  
  49 50   3   561  
  27 50   4   143  
  27 100   3   1285  
  25 100   30   248  
  28 100   34   5783  
  24 50   11   1786  
  43 100   29   11507  
  40 100   19   8254  
  38 50   41   4846  
  35 50   23   4667  
  37 50   11   5900  
  24 50   10   3099  
  23 50   7   5619  
  23 50   6   1794  
  20 50   6   1273  
  22 50   11   193  
  46 50   11   487767  
  46 50   8   1494  
  46 50   7   2102  
  45 50   2   226  
  46 50   13   224662  
  32 50   5   7829  
  32 50   8   152  
  32 50   6   1268  
  26 50   5   250  
  24 50   4   5433  
  12 50   1   724  
  12 50   1   62  
  12 50   1   34  
  11 50   1   652  
  10 50   1   3113  
  10 50   1   33  
  10     1   302  
  10     1   85  
  10     1   57  
  7     1   619  
  4     1   23  
  4     1   10  
  4     1   148  
  4     1   21  
  12     1   1601104  
  12     1   475  
  12     1   67  
  12     1   43  
  11     1   371  
  7     1   148  
  7     1   23  
  7     1   434  
  6     1   28  
  6     1   27  
  4     1   683  
  4     1   26  
  4     1   12  
  4     1   228  
  4     1   29  
  4     1   52  
  6     1   1520  
  6     1   55  
  6     1   20  
  6     1   539  
  6     1   76  
  5     1   760  
  5     1   267  
  6     1   31  
  6     1   14  
  6     1   821  
  4     1   23  
  4     1   10  
  4         492  
  4         26  
  3         7  
  3         468  
  3         21  
  3         7  
  3         401  
  4         29  
  4         8  
  4         619  
  3         22  
  3         5  
  3         556  
  2         13  
  2         4  
  2         154  
  2         27  
  2         3  
  2         358  
  1         5  
  1         1  
  1         173  
  1         4  
  1         1  
  1         20  
  1         2  
  1         2  
  1         104  
  1         4  
  1         1  
  1         106  
  1         5  
  1         2  
  1         110  
  1         8  
  1         2  
  1         295  
  1         7  
  1         2  
  1         42  
  1         5  
  1         1  
  1         145  
  1         6  
  1         1  
  1         153  
  1         5  
  1         2  
  1         123  
  1         5  
  1         1  
  1         237  
  1         4  
  1         2  
  1         44  
  1         4  
  1         1  
  1         125  
  1         5  
  1         2  
  1         138  
  1         5  
  1         1  
  1         137  
  1         5  
  1         2  
  1         241  
  1         5  
  1         2  
  1         18  
  1         4  
  1         1  
  1         104  
  1         5  
  1         2  
  1         162  
  1         5  
  1         1  
  1         130  
  1         5  
  1         1  
  1         167  
  1         4  
  1         1  
  1         19  
  1         3  
  1         2  
  1         111  
  1         5  
  1         2  
  1         90  
  1         6  
  1         2  
  1         218  
  1         6  
  1         1  
  1         30  
  1         4  
  1         1  
  1         140  
  1         6  
  1         2  
  1         162  
  1         5  
  1         2  
  1         19  
  1         3  
  1         2  
  1         113  
  1         6  
  1         2  
  1         171  
  1         7  
  1         2  
  1         313  
  1         5  
  1         1  
  1         13  
  1         3  
  1         2  
  1         15  
  1         3  
  1         1  
  1         24  
  1         3  
  1         2  
  1         15  
  1         3  
  1         1  
  1         125  
  1         6  
  1         2  
  1         221  
  1         6  
  1         1  
  1         16  
  1         3  
  1         2  
  1         23  
  1         3  
  1         2  
  1         133  
  1         10  
  1         1  
  1         230  
  1         9  
  1         2  
  1         49  
  1         4  
  1         1  
  1         132  
  1         6  
  1         1  
  1         136  
  1         6  
  1         1  
  1         19  
  1         4  
  1         1  
  1         118  
  1         5  
  1         1  
  1         129  
  1         4  
  1         2  
  1         175  
676             }
677              
678             }
679              
680             sub _interpolate {
681 2693     2693   37702 my ( $tpl, $dict, $work ) = @_;
682 2693 100       4480 $work = { loop => {} } unless defined $work;
683              
684 2682         21376 $$tpl =~ s{(\\)?\<\<(\w+)\>\>
685             }{
686 3038 100       5192 if ( defined $1 ) {
687 527         2702 "<<$2>>";
688             }
689             else {
690 2490         3984 my $key = lc $2;
691 2492         4324 my $v = $dict->{$key};
692 2492 100       3437 if ( defined $v ) {
693 2445 100       7245 $v = join( "\n", @$v )
694             if 'ARRAY' eq ref $v;
695              
696             _croak( "circular interpolation loop detected for $key" )
697 2445 100       4784 if $work->{loop}{$key}++;
698 2446         5842 _interpolate( \$v, $dict, $work );
699 2446         3528 --$work->{loop}{$key};
700 2443         9550 $v;
701             }
702             else {
703 48         235 q{};
704             }
705             }
706             }gex;
707 2662         6603 return;
708             }
709              
710             1;
711              
712             #
713             # This file is part of Hash-Wrap
714             #
715             # This software is Copyright (c) 2017 by Smithsonian Astrophysical Observatory.
716             #
717             # This is free software, licensed under:
718             #
719             # The GNU General Public License, Version 3, June 2007
720             #
721              
722             __END__