File Coverage

blib/lib/Hash/Wrap.pm
Criterion Covered Total %
statement 512 533 96.0
branch 201 254 79.1
condition 59 95 62.1
subroutine 110 113 97.3
pod n/a
total 882 995 88.6


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