File Coverage

blib/lib/Hash/Wrap.pm
Criterion Covered Total %
statement 423 452 93.5
branch 184 234 78.6
condition 45 81 55.5
subroutine 83 95 87.3
pod n/a
total 735 862 85.2


line stmt bran cond sub pod time code
1             package Hash::Wrap;
2              
3             # ABSTRACT: create on-the-fly objects from hashes
4              
5 20     20   2820970 use 5.01000;
  20         136  
6              
7 20     20   493 use strict;
  20         49  
  20         328  
8 20     20   307 use warnings;
  20         52  
  20         400  
9              
10             ## no critic(ValuesAndExpressions::ProhibitAccessOfPrivateData)
11              
12 20     20   557 use Scalar::Util;
  20         57  
  20         665  
13 20     18   452 use Digest::MD5;
  18         49  
  18         3789  
14             our $VERSION = '0.21';
15              
16             our @EXPORT = qw[ wrap_hash ];
17              
18             our @CARP_NOT = qw( Hash::Wrap );
19             our $DEBUG = 0;
20              
21             # copied from Damian Conway's PPR: PerlIdentifier
22 18     18   270 use constant PerlIdentifier => qr/\A([^\W\d]\w*+)\z/;
  18         37  
  18         4104  
23              
24             our %REGISTRY;
25              
26             sub _croak {
27 21     21   119 require Carp;
28 21         3392 goto \&Carp::croak;
29             }
30              
31             sub _croak_class_method {
32 0     0   0 my ( $class, $method ) = @_;
33 0   0     0 $class = ref( $class ) || $class;
34 0         0 _croak ( qq[Can't locate class method "$method" via package "$class"] );
35             }
36              
37             sub _croak_object_method {
38 10     10   24 my ( $object, $method ) = @_;
39 10   33     61 my $class = Scalar::Util::blessed( $object ) || ref( $object ) || $object;
40 10         95 _croak ( qq[Can't locate object method "$method" via package "$class"] );
41             }
42              
43              
44             sub _find_symbol {
45 99     99   215 my ( $package, $symbol, $reftype ) = @_;
46              
47 18     18   336 no strict 'refs'; ## no critic (ProhibitNoStrict)
  18         53  
  18         9421  
48 99         122 my $candidate = *{"$package\::$symbol"}{SCALAR};
  99         318  
49              
50             return $$candidate
51             if defined $candidate
52             && 2 ==
53 99 100 66     690 grep { defined $_->[0] && defined $_->[1] ? $_->[0] eq $_->[1] : 1 }
  198 50 33     1026  
54             [ $reftype->[0], Scalar::Util::reftype $candidate ],
55             [ $reftype->[1], Scalar::Util::reftype $$candidate ];
56              
57 0         0 _croak( "Unable to find scalar \$$symbol in class $package" );
58             }
59              
60             # this is called only if the method doesn't exist.
61             sub _generate_accessor {
62 77     77   184 my ( $hash_class, $class, $key ) = @_;
63              
64 77         290 my %dict = (
65             key => $key,
66             class => $class,
67             );
68              
69 77         212 my $code = $REGISTRY{$hash_class}{accessor_template};
70 77         230 my $coderef = _compile_from_tpl( \$code, \%dict );
71 77 50       252 _croak_about_code( \$code, 'accessor' )
72             if $@;
73              
74 77         1451 return $coderef;
75             }
76              
77             sub _generate_predicate {
78 4     4   12 my ( $hash_class, $class, $key ) = @_;
79              
80 4         32 my %dict = (
81             key => $key,
82             class => $class,
83             );
84              
85 4         8 my $code = $REGISTRY{$hash_class}{predicate_template};
86 4         27 my $coderef = _compile_from_tpl( \$code, \%dict );
87 4 50       12 _croak_about_code( \$code, 'predicate' )
88             if $@;
89              
90 4         94 return $coderef;
91             }
92              
93              
94             sub _autoload {
95 61     61   150 my ( $hash_class, $method, $object ) = @_;
96              
97 61         316 my ( $class, $key ) = $method =~ /(.*)::(.*)/;
98              
99 61 50       243 _croak_class_method( $object, $key )
100             unless Scalar::Util::blessed( $object );
101              
102 61 100 66     283 if ( exists $REGISTRY{$hash_class}{predicate_template}
103             && $key =~ /^has_(.*)/ )
104             {
105 4         20 return _generate_predicate( $hash_class, $class, $1 );
106             }
107              
108             _croak_object_method( $object, $key )
109 57 100       895 unless $REGISTRY{$hash_class}{validate}->( $object, $key );
110              
111 50         135 _generate_accessor( $hash_class, $class, $key );
112             }
113              
114             sub _can {
115 55     55   157 my ( $self, $key, $CLASS ) = @_;
116              
117 55         155 my $class = Scalar::Util::blessed( $self );
118 55 50       174 return if !defined $class;
119              
120 55 100       240 if ( !exists $self->{$key} ) {
121              
122 23 100       94 if ( exists $Hash::Wrap::REGISTRY{$class}{methods}{$key} ) {
123             ## no critic (ProhibitNoStrict)
124 18     18   147 no strict 'refs';
  18         41  
  18         1442  
125 12         28 my $method = "${class}::$key";
126 12         17 return *{$method}{CODE};
  12         86  
127             }
128 11         184 return;
129             }
130              
131 32         95 my $method = "${class}::$key";
132              
133             ## no critic (ProhibitNoStrict)
134 18     18   258 no strict 'refs';
  18         50  
  18         19992  
135             return *{$method}{CODE}
136 32   66     65 || Hash::Wrap::_generate_accessor( $CLASS, $class, $key );
137             }
138              
139             sub import {
140 56     56   52218 shift;
141 56         211 my $caller = caller;
142              
143 56         140 my @imports = @_;
144 56 100       206 push @imports, @EXPORT unless @imports;
145              
146 56         116 my @return;
147              
148 56         130 for my $args ( @imports ) {
149 59 100       271 if ( !ref $args ) {
    50          
150             _croak( "$args is not exported by ", __PACKAGE__ )
151 5 100       12 unless grep { /$args/ } @EXPORT;
  5         75  
152              
153 4         13 $args = { -as => $args };
154             }
155              
156             elsif ( 'HASH' ne ref $args ) {
157             _croak(
158             "argument to ",
159             __PACKAGE__,
160             "::import must be string or hash"
161 0 0       0 ) unless grep { /$args/ } @EXPORT;
  0         0  
162             }
163             else {
164             # make a copy as it gets modified later on
165 54         232 $args = {%$args};
166             }
167              
168             _croak( "cannot mix -base and -class" )
169 58 100 100     249 if !!$args->{-base} && exists $args->{-class};
170              
171 57   33     322 $DEBUG = $ENV{HASH_WRAP_DEBUG} // delete $args->{-debug};
172              
173 57 100       156 $args->{-as} = 'wrap_hash' unless exists $args->{-as};
174 57         118 my $name = delete $args->{-as};
175              
176 57 100       151 if ( defined $name ) {
177              
178 56 100       272 if ( defined( my $reftype = Scalar::Util::reftype( $name ) ) ) {
    100          
179              
180 9 50 66     61 _croak(
      100        
      66        
      66        
      33        
181             "-as must be undefined or a string or a reference to a scalar"
182             )
183             if $reftype ne 'SCALAR'
184             && $reftype ne 'VSTRING'
185             && $reftype ne 'REF'
186             && $reftype ne 'GLOB'
187             && $reftype ne 'LVALUE'
188             && $reftype ne 'REGEXP';
189              
190 8         21 $args->{-as_scalar_ref} = $name;
191              
192             }
193              
194             elsif ( $name eq '-return' ) {
195 10         27 $args->{-as_return} = 1;
196             }
197             }
198              
199 56 100       139 if ( $args->{-base} ) {
200             _croak( "don't use -as => -return with -base" )
201 3 50       14 if $args->{-as_return};
202 3         5 $args->{-class} = $caller;
203 3 50       7 $args->{-new} = 1 unless !!$args->{-new};
204 3         6 _build_class( $caller, $name, $args );
205             }
206              
207             else {
208 53         159 _build_class( $caller, $name, $args );
209 48 100       118 if ( defined $name ) {
210 47         134 my $sub = _build_constructor( $caller, $name, $args );
211 45 100       153 push @return, $sub if $args->{-as_return};
212             }
213             }
214              
215             # clean out known attributes
216 49         252 delete @{$args}{
217 49         123 qw[ -as -as_return -as_scalar_ref -base -class -clone
218             -copy -defined -exists -immutable -lockkeys -lvalue
219             -methods -new -predicate -recurse -undef ]
220             };
221              
222 49 100       180 if ( keys %$args ) {
223 1         6 _croak( "unknown options passed to ",
224             __PACKAGE__, "::import: ", join( ', ', keys %$args ) );
225             }
226             }
227              
228 45         24464 return @return;
229             }
230              
231             sub _build_class {
232 56     56   144 my ( $caller, $name, $attr ) = @_;
233              
234             # in case we're called inside a recursion and the recurse count
235             # has hit zero, default behavior is no recurse, so remove it so
236             # the attr signature computed below isn't contaminated by a
237             # useless -recurse => 0 attribute.
238 56 100       162 if ( exists $attr->{-recurse} ) {
239             _croak( "-recurse must be a number" )
240 14 100       101 unless Scalar::Util::looks_like_number( $attr->{-recurse} );
241 13 100       51 delete $attr->{-recurse} if $attr->{-recurse} == 0;
242             }
243              
244 55 100       167 if ( !defined $attr->{-class} ) {
    100          
245              
246             my @class = map {
247 42         247 ( my $key = $_ ) =~ s/-//;
  74         271  
248 74 50       270 ( $key, defined $attr->{$_} ? $attr->{$_} : "" )
249             } sort keys %$attr;
250              
251 42         595 $attr->{-class} = join '::', 'Hash::Wrap::Class',
252             Digest::MD5::md5_hex( @class );
253             }
254              
255             elsif ( $attr->{-class} eq '-caller' ) {
256 2 100       9 _croak( "can't set -class => '-caller' if -as is not a plain string" )
257             if ref $name;
258 1         2 $attr->{-class} = $caller . '::' . $name;
259             }
260              
261 54         139 my $class = $attr->{-class};
262              
263 54 100       160 return $class if defined $REGISTRY{$class};
264 52         213 my $rentry = $REGISTRY{$class} = { methods => {} };
265              
266 52         90 my %closures;
267             my %dict = (
268             class => $class,
269             signature => '',
270             body => [],
271             autoload_attr => '',
272             validate_inline => 'exists $self->{\<>}',
273             validate_method => 'exists $self->{$key}',
274             set => '$self->{q[\<>]} = $_[0] if @_;',
275             return_value => '$self->{q[\<>]}',
276             recursion_constructor => '',
277 52         249 meta => [ map { ( qq[q($_) => q($attr->{$_}),] ) } keys %$attr ],
  140         1158  
278             predicate_template => '',
279             );
280              
281 52 100       268 if ( $attr->{-lvalue} ) {
282 5 50       28 if ( $] lt '5.016000' ) {
283             _croak( "lvalue accessors require Perl 5.16 or later" )
284 0 0       0 if $attr->{-lvalue} < 0;
285             }
286             else {
287 5         8 $dict{autoload_attr} = q[: lvalue];
288 5         7 $dict{signature} = q[: lvalue];
289             }
290             }
291              
292 52 100       160 if ( $attr->{-undef} ) {
293 7         10 $dict{validate_method} = q[ 1 ];
294 7         10 $dict{validate_inline} = q[ 1 ];
295             }
296              
297 52 100       127 if ( $attr->{-exists} ) {
298 14 100       170 $dict{exists} = $attr->{-exists} =~ PerlIdentifier ? $1 : 'exists';
299 14         27 push @{ $dict{body} }, q[ sub <> { exists $_[0]->{$_[1] } } ];
  14         61  
300 14         69 $rentry->{methods}{$dict{exists}} = undef;
301             }
302              
303 52 100       125 if ( $attr->{-defined} ) {
304 2 100       11 $dict{defined} = $attr->{-defined} =~ PerlIdentifier ? $1 : 'defined';
305 2         2 push @{ $dict{body} }, q[ sub <> { defined $_[0]->{$_[1] } } ];
  2         4  
306 2         6 $rentry->{methods}{$dict{defined}} = undef;
307             }
308              
309 52 100       160 if ( $attr->{-immutable} ) {
310 1         2 $dict{set} = <<'END';
311             if ( @_ ) {
312             require Carp;
313             Carp::croak( q[Modification of a read-only value attempted])
314             }
315             END
316             }
317              
318 52 100       122 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 8 100       42 $dict{recurse_limit} = --$attr->{-recurse} < 0 ? -1 : $attr->{-recurse};
327              
328 8         29 $dict{return_value} = <<'END';
329             return 'HASH' eq (Scalar::Util::reftype( $self->{q[\<>]} ) // '')
330             && ! Scalar::Util::blessed( $self->{q[\<>]} )
331             ? $<>::recurse_into_hash->( $self->{q[\<>]} )
332             : $self->{q[\<>]};
333             END
334              
335             # do a two-step initialization of the constructor. If
336             # the initialization sub is stored in $recurse_into_hash, and then
337             # $recurse_into_hash is set to the actual constructor I worry that
338             # Perl may decide to garbage collect the setup subroutine while it's
339             # busy setting $recurse_into_hash. So, store the
340             # initialization sub in something other than $recurse_into_hash.
341              
342 8         17 $dict{recursion_constructor} = <<'END';
343             our $recurse_into_hash;
344             our $setup_recurse_into_hash = sub {
345             require Hash::Wrap;
346             ( $recurse_into_hash ) = Hash::Wrap->import ( { %$attr, -as => '-return',
347             -recurse => <> } );
348             goto &$recurse_into_hash;
349             };
350             $recurse_into_hash = $setup_recurse_into_hash;
351             END
352              
353             my %attr = ( %$attr,
354             -recurse => --$attr->{-recurse} < 0 ? -1 : $attr->{-recurse},
355 8 100       107 );
356 8         49 delete @attr{ qw( -as_scalar_ref -class -base -as ) };
357 8         22 $closures{'$attr'} = \%attr;
358             }
359              
360              
361              
362 52 100       167 if ( $attr->{-predicate} ) {
363 1         1 $dict{predicate_template} = <<'END';
364             our $predicate_template = q[
365             package \<>;
366              
367             use Scalar::Util ();
368              
369             sub has_\<> {
370             my $self = shift;
371              
372             Hash::Wrap::_croak_class_method( $self, "has_\<>" )
373             unless Scalar::Util::blessed( $self );
374              
375             return exists $self->{\<>};
376             }
377              
378             $Hash::Wrap::REGISTRY{methods}{'has_\<>'} = undef;
379              
380             \&has_\<>;
381             ];
382             END
383             }
384              
385 52         96 my $class_template = <<'END';
386             package <>;
387              
388             <>
389              
390             use Scalar::Util ();
391              
392             our $meta = { <> };
393              
394             our $validate = sub {
395             my ( $self, $key ) = @_;
396             return <>;
397             };
398              
399             <>
400              
401             our $accessor_template = q[
402             package \<>;
403              
404             use Scalar::Util ();
405              
406             sub \<> <> {
407             my $self = shift;
408              
409             Hash::Wrap::_croak_class_method( $self, "\<>" )
410             unless Scalar::Util::blessed( $self );
411              
412             Hash::Wrap::_croak_object_method( $self, "\<>" )
413             unless ( <> );
414              
415             <>
416              
417             return <>;
418             }
419             \&\<>;
420             ];
421              
422             <>
423              
424              
425             <>
426              
427             our $AUTOLOAD;
428             sub AUTOLOAD <> {
429             goto &{ Hash::Wrap::_autoload( q[<>], $AUTOLOAD, $_[0] ) };
430             }
431              
432             sub DESTROY { }
433              
434             sub can {
435             return Hash::Wrap::_can( @_, q[<>] );
436             }
437              
438             1;
439             END
440              
441 52 50       192 _compile_from_tpl( \$class_template, \%dict, \%closures )
442             or _croak_about_code( \$class_template, "class $class" );
443              
444 52 100       213 if ( !!$attr->{-new} ) {
445 5 50       23 my $name = $attr->{-new} =~ PerlIdentifier ? $1 : 'new';
446 5         27 _build_constructor( $class, $name, { %$attr, -as_method => 1 } );
447             }
448              
449 52 100       138 if ( $attr->{-methods} ) {
450              
451 16         34 my $methods = $attr->{-methods};
452 16 100       73 _croak( "-methods option value must be a hashref" )
453             unless 'HASH' eq ref $methods;
454              
455 15         70 for my $mth ( keys %$methods ) {
456 16 100       123 _croak( "method name '$mth' is not a valid Perl identifier" )
457             if $mth !~ PerlIdentifier;
458              
459 15         38 my $code = $methods->{$mth};
460 15 100       75 _croak( qq{value for method "$mth" must be a coderef} )
461             unless 'CODE' eq ref $code;
462 18     18   251 no strict 'refs'; ## no critic (ProhibitNoStrict)
  18         42  
  18         18967  
463 14         25 *{"${class}::${mth}"} = $code;
  14         168  
464             }
465              
466 13         69 $rentry->{methods}{$_} = undef for keys %$methods;
467             }
468              
469 49         134 push @CARP_NOT, $class;
470             $rentry->{accessor_template}
471 49         215 = _find_symbol( $class, "accessor_template", [ "SCALAR", undef ] );
472              
473 49 100       171 if ( $attr->{-predicate} ) {
474             $rentry->{predicate_template}
475 1         2 = _find_symbol( $class, "predicate_template", [ "SCALAR", undef ] );
476             }
477              
478 49         169 $rentry->{validate} = _find_symbol( $class, 'validate', [ 'REF', 'CODE' ] );
479              
480 49         392 Scalar::Util::weaken( $rentry->{validate} );
481              
482 49         212 return $class;
483             }
484              
485             sub _build_constructor {
486 52     52   132 my ( $package, $name, $args ) = @_;
487              
488             # closure for user provided clone sub
489 52         64 my $clone;
490              
491             _croak( "cannot mix -copy and -clone" )
492 52 100 100     135 if exists $args->{-copy} && exists $args->{-clone};
493              
494 51         240 my %dict = (
495             package => $package,
496             constructor_name => $name,
497             use => [],
498             package_return_value => '1;',
499             );
500              
501 51         75 $dict{class} = do {
502 51 100       120 if ( $args->{-as_method} ) {
503 5         9 'shift;';
504             }
505             else {
506              
507 46         138 'q[' . $args->{-class} . '];';
508             }
509             };
510              
511 51         95 $dict{copy} = do {
512 51 100       260 if ( $args->{-copy} ) {
    100          
513 1         2 '$hash = { %{ $hash } };';
514             }
515              
516             elsif ( exists $args->{-clone} ) {
517 3 100       11 if ( 'CODE' eq ref $args->{-clone} ) {
518 1         2 $clone = $args->{-clone};
519 1         2 '$hash = $clone->($hash);';
520             }
521             else {
522 2         3 push @{ $dict{use} }, q[use Storable ();];
  2         4  
523 2         6 '$hash = Storable::dclone $hash;';
524             }
525             }
526             };
527              
528 51         103 $dict{lock} = do {
529 51 100       215 if ( $args->{-immutable} ) {
    100          
530 1         1 push @{ $dict{use} }, q[use Hash::Util ();];
  1         3  
531 1         9 'Hash::Util::lock_hash(%$hash)';
532             }
533             elsif ( defined $args->{-lockkeys} ) {
534              
535 3 100       8 if ( 'ARRAY' eq ref $args->{-lockkeys} ) {
    50          
536             _croak(
537             "-lockkeys: attribute name ($_) is not a valid Perl identifier"
538 2         2 ) for grep { $_ !~ PerlIdentifier } @{ $args->{-lockkeys} };
  4         23  
  2         5  
539              
540 1         2 push @{ $dict{use} }, q[use Hash::Util ();];
  1         2  
541             'Hash::Util::lock_keys_plus(%$hash, qw{ '
542 1         2 . join( ' ', @{ $args->{-lockkeys} } ) . ' });';
  1         5  
543             }
544             elsif ( $args->{-lockkeys} ) {
545 1         2 push @{ $dict{use} }, q[use Hash::Util ();];
  1         2  
546 1         4 'Hash::Util::lock_keys(%$hash)';
547             }
548             }
549             };
550              
551             # return the constructor sub from the factory and don't insert the
552             # name into the package namespace
553 50 100 100     303 if ( $args->{-as_scalar_ref} || $args->{-as_return} ) {
554 17         35 $dict{package_return_value} = '';
555 17         36 $dict{constructor_name} = '';
556             }
557              
558             #<<< no tidy
559 50         112 my $code = q[
560             package <>;
561             <>
562             <>
563             use Scalar::Util ();
564              
565             no warnings 'redefine';
566              
567             sub <> (;$) {
568             my $class = <>
569             my $hash = shift // {};
570              
571             if ( 'HASH' ne Scalar::Util::reftype($hash) ) {
572             require Carp;
573             Carp::croak( "argument to <>::<> must be a hashref" )
574             }
575             <>
576             bless $hash, $class;
577             <>
578             }
579             <>
580             ];
581             #>>>
582              
583 50   33     233 my $result = _compile_from_tpl( \$code, \%dict, { '$clone' => $clone } )
584             || _croak(
585             "error generating constructor (as $name) subroutine: $@\n$code" );
586              
587             # caller asked for a coderef to be stuffed into a scalar
588 50 100       171 ${$name} = $result if $args->{-as_scalar_ref};
  7         19  
589 50         388 return $result;
590             }
591              
592             sub _croak_about_code {
593 0     0   0 my ( $code, $what ) = @_;
594 0         0 my $error = $@;
595 0         0 _line_number_code( $code );
596 0         0 _croak( qq[error compiling $what: $error\n$$code] );
597             }
598              
599             sub _line_number_code {
600 0     0   0 my ( $code ) = @_;
601 0         0 my $space = length( $$code =~ tr/\n// );
602 0         0 my $line = 0;
603 0         0 $$code =~ s/^/sprintf "%${space}d: ", ++$line/emg;
  0         0  
604             }
605              
606              
607             sub _compile_from_tpl {
608 183     183   340 my ( $code, $dict, $closures ) = @_;
609              
610 183 100 100     593 if ( defined $closures && %$closures) {
611             $dict->{closures}
612 58         174 = join( "\n", map { "my $_ = \$closures->{'$_'};" } keys %$closures );
  58         279  
613             }
614              
615 183         473 _interpolate( $code, $dict );
616              
617 183 50       369 if ( $DEBUG ) {
618 0         0 my $code = $$code;
619 0         0 _line_number_code( \$code );
620 0         0 print STDERR $code;
621             }
622              
623              
624 18 50 66 18   221 eval( $$code ); ## no critic (ProhibitStringyEval)
  18 100 33 18   44  
  18 100 33 18   3226  
  18 100 33 17   3557  
  18 100 33 15   8590  
  18 100 66 14   588  
  18 100 33 12   117  
  18 50 33 11   42  
  18 100 50 10   1786  
  17 100 50 9   263  
  17 50 50 9   71  
  17 100   9   3112  
  15 100   5   388  
  15 50   5   57  
  15 50   4   883  
  14 100   4   212  
  14 100   4   38  
  14 50   4   1583  
  12 50   4   1081  
  12 50   3   3121  
  12 50   3   1110  
  11 50   3   726  
  11 50   3   2867  
  11 50   3   889  
  10 50   3   259  
  10 50   3   23  
  10 50   3   843  
  9 50   3   199  
  9 50   3   23  
  9 50   32   1011  
  9 50   21   201  
  9 50   3   19  
  9 50   2   703  
  9 50   0   366  
  9 50   3   29  
  9 50   0   722  
  5 50   0   51  
  5 50   0   14  
  5 50   0   503  
  5 50   0   168  
  5 50   0   14  
  5 50   0   625  
  4 50   0   178  
  4     23   13  
  4     18   160  
  4     10   147  
  4     9   12  
  4     20   428  
  4     11   233  
  4     17   12  
  4     18   356  
  4     8   40  
  4     6   11  
  4     5   215  
  4     3   173  
  4     10   12  
  4     10   473  
  4     7   126  
  3     8   5  
  3     2   238  
  3     8   16  
  3     1   6  
  3     1   441  
  3     2   25  
  3     16   6  
  3     7   333  
  3     3   18  
  3     3   5  
  3     1   404  
  3     1   24  
  3         7  
  3         539  
  3         18  
  3         5  
  3         162  
  3         15  
  3         6  
  3         344  
  3         20  
  3         6  
  3         437  
  3         21  
  3         7  
  3         257  
  3         27  
  3         87  
  3         556  
  183         15494  
  32         21875  
  32         122  
  21         5214  
  21         68  
  3         391  
  3         21  
  2         634  
  2         20  
  0         0  
  0         0  
  3         451  
  3         9  
  0         0  
  0         0  
  0         0  
  0         0  
  33         1014  
  33         245  
  33         164  
  33         223  
  33         211  
  27         697  
  26         159  
  27         157  
  26         101  
  26         206  
  15         4241  
  11         4441  
  7         5622  
  8         4001  
  7         2415  
  7         2106  
  8         2983  
  6         439  
  13         926  
  9         13118  
  23         1336  
  28         2054  
  20         139  
  19         1539  
  23         261  
  20         4580  
  19         100  
  17         118  
  15         39  
  17         105  
  7         68  
  5         2145  
  5         65  
  5         22  
  4         56  
  3         44  
  4         17  
  4         2148  
  3         14  
  3         18  
  1         4  
  1         10  
  3         1214  
  3         113  
  3         1847  
  2         9  
  2         26  
  2         121  
  7         13875  
  8         40  
  7         44  
  1         74  
  3         138  
  9         51  
  4         22  
  3         6  
  3         23  
  1         46  
  1         5  
  1         8  
  1         3  
  1         3  
  1         59  
  1         5  
  1         9  
  1         3  
  1         10  
  2         587  
  2         14  
  2         11  
  1         3  
  1         4  
  2         57  
  13         13896  
  8         39  
  8         62  
  4         9  
  4         15  
  7         125  
  6         9229  
  6         31  
  6         33  
  3         8  
  2         5160  
  5         36  
  1         7  
  0         0  
  0         0  
  1         7  
  1         10  
  1         3332  
  1         24  
  1         8  
  0         0  
  0         0  
  1         5  
  1         12  
625             }
626              
627             sub _interpolate {
628 2076     2076   2745 my ( $tpl, $dict, $work ) = @_;
629 2076 100       3061 $work = { loop => {} } unless defined $work;
630              
631 2076         4140 $$tpl =~ s{(\\)?\<\<(\w+)\>\>
632             }{
633 2375 100       4134 if ( defined $1 ) {
634 439         1266 "<<$2>>";
635             }
636             else {
637 1937         3398 my $key = lc $2;
638 1937         2528 my $v = $dict->{$key};
639 1937 100       2373 if ( defined $v ) {
640 1893 100       2977 $v = join( "\n", @$v )
641             if 'ARRAY' eq ref $v;
642              
643             _croak( "circular interpolation loop detected for $key" )
644 1892 100       3428 if $work->{loop}{$key}++;
645 1892         3280 _interpolate( \$v, $dict, $work );
646 1892         2526 --$work->{loop}{$key};
647 1892         6847 $v;
648             }
649             else {
650 44         174 '';
651             }
652             }
653             }gex;
654 2075         2902 return;
655             }
656              
657             1;
658              
659             #
660             # This file is part of Hash-Wrap
661             #
662             # This software is Copyright (c) 2017 by Smithsonian Astrophysical Observatory.
663             #
664             # This is free software, licensed under:
665             #
666             # The GNU General Public License, Version 3, June 2007
667             #
668              
669             __END__