File Coverage

blib/lib/Hash/Wrap.pm
Criterion Covered Total %
statement 418 452 92.4
branch 179 234 76.5
condition 44 81 54.3
subroutine 84 95 88.4
pod n/a
total 725 862 84.1


line stmt bran cond sub pod time code
1             package Hash::Wrap;
2              
3             # ABSTRACT: create on-the-fly objects from hashes
4              
5 19     19   3051110 use 5.01000;
  19         148  
6              
7 19     19   533 use strict;
  19         58  
  19         332  
8 19     19   398 use warnings;
  19         64  
  19         428  
9              
10             ## no critic(ValuesAndExpressions::ProhibitAccessOfPrivateData)
11              
12 19     19   484 use Scalar::Util;
  19         54  
  19         689  
13 19     17   568 use Digest::MD5;
  17         37  
  17         4175  
14             our $VERSION = '0.19';
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 17     17   312 use constant PerlIdentifier => qr/\A([^\W\d]\w*+)\z/;
  17         41  
  17         4435  
23              
24             our %REGISTRY;
25              
26             sub _croak {
27 21     21   121 require Carp;
28 21         3503 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     55 my $class = Scalar::Util::blessed( $object ) || ref( $object ) || $object;
40 10         75 _croak ( qq[Can't locate object method "$method" via package "$class"] );
41             }
42              
43              
44             sub _find_symbol {
45 96     96   283 my ( $package, $symbol, $reftype ) = @_;
46              
47 17     17   358 no strict 'refs'; ## no critic (ProhibitNoStrict)
  17         81  
  17         10053  
48 96         143 my $candidate = *{"$package\::$symbol"}{SCALAR};
  96         341  
49              
50             return $$candidate
51             if defined $candidate
52             && 2 ==
53 96 100 66     645 grep { defined $_->[0] && defined $_->[1] ? $_->[0] eq $_->[1] : 1 }
  192 50 33     1032  
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   223 my ( $hash_class, $class, $key ) = @_;
63              
64 77         269 my %dict = (
65             key => $key,
66             class => $class,
67             );
68              
69 77         183 my $code = $REGISTRY{$hash_class}{accessor_template};
70 77         254 my $coderef = _compile_from_tpl( \$code, \%dict );
71 77 50       276 _croak_about_code( \$code, 'accessor' )
72             if $@;
73              
74 77         1576 return $coderef;
75             }
76              
77             sub _generate_predicate {
78 0     0   0 my ( $hash_class, $class, $key ) = @_;
79              
80 0         0 my %dict = (
81             key => $key,
82             class => $class,
83             );
84              
85 0         0 my $code = $REGISTRY{$hash_class}{predicate_template};
86 0         0 my $coderef = _compile_from_tpl( \$code, \%dict );
87 0 0       0 _croak_about_code( \$code, 'predicate' )
88             if $@;
89              
90 0         0 return $coderef;
91             }
92              
93              
94             sub _autoload {
95 57     57   152 my ( $hash_class, $method, $object ) = @_;
96              
97 57         352 my ( $class, $key ) = $method =~ /(.*)::(.*)/;
98              
99 57 50       283 _croak_class_method( $object, $key )
100             unless Scalar::Util::blessed( $object );
101              
102 57 50 33     256 if ( exists $REGISTRY{$hash_class}{predicate_template}
103             && $key =~ /^has_(.*)/ )
104             {
105 0         0 return _generate_predicate( $hash_class, $class, $1 );
106             }
107              
108             _croak_object_method( $object, $key )
109 57 100       1081 unless $REGISTRY{$hash_class}{validate}->( $object, $key );
110              
111 50         163 _generate_accessor( $hash_class, $class, $key );
112             }
113              
114             sub _can {
115 55     55   147 my ( $self, $key, $CLASS ) = @_;
116              
117 55         162 my $class = Scalar::Util::blessed( $self );
118 55 50       165 return if !defined $class;
119              
120 55 100       233 if ( !exists $self->{$key} ) {
121              
122 23 100       74 if ( exists $Hash::Wrap::REGISTRY{$class}{methods}{$key} ) {
123             ## no critic (ProhibitNoStrict)
124 17     17   158 no strict 'refs';
  17         47  
  17         1489  
125 12         32 my $method = "${class}::$key";
126 12         22 return *{$method}{CODE};
  12         57  
127             }
128 11         199 return;
129             }
130              
131 32         96 my $method = "${class}::$key";
132              
133             ## no critic (ProhibitNoStrict)
134 17     17   241 no strict 'refs';
  17         47  
  17         22453  
135             return *{$method}{CODE}
136 32   66     47 || Hash::Wrap::_generate_accessor( $CLASS, $class, $key );
137             }
138              
139             sub import {
140 55     55   53066 shift;
141 55         187 my $caller = caller;
142              
143 55         135 my @imports = @_;
144 55 100       174 push @imports, @EXPORT unless @imports;
145              
146 55         92 my @return;
147              
148 55         121 for my $args ( @imports ) {
149 58 100       232 if ( !ref $args ) {
    50          
150             _croak( "$args is not exported by ", __PACKAGE__ )
151 5 100       11 unless grep { /$args/ } @EXPORT;
  5         89  
152              
153 4         16 $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 53         220 $args = {%$args};
166             }
167              
168             _croak( "cannot mix -base and -class" )
169 57 100 100     218 if !!$args->{-base} && exists $args->{-class};
170              
171 56   33     283 $DEBUG = $ENV{HASH_WRAP_DEBUG} // delete $args->{-debug};
172              
173 56 100       150 $args->{-as} = 'wrap_hash' unless exists $args->{-as};
174 56         113 my $name = delete $args->{-as};
175              
176 56 100       168 if ( defined $name ) {
177              
178 55 100       248 if ( defined( my $reftype = Scalar::Util::reftype( $name ) ) ) {
    100          
179              
180 9 50 66     45 _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         19 $args->{-as_scalar_ref} = $name;
191              
192             }
193              
194             elsif ( $name eq '-return' ) {
195 10         22 $args->{-as_return} = 1;
196             }
197             }
198              
199 55 100       135 if ( $args->{-base} ) {
200             _croak( "don't use -as => -return with -base" )
201 3 50       10 if $args->{-as_return};
202 3         7 $args->{-class} = $caller;
203 3 50       9 $args->{-new} = 1 unless !!$args->{-new};
204 3         7 _build_class( $caller, $name, $args );
205             }
206              
207             else {
208 52         141 _build_class( $caller, $name, $args );
209 47 100       108 if ( defined $name ) {
210 46         106 my $sub = _build_constructor( $caller, $name, $args );
211 44 100       135 push @return, $sub if $args->{-as_return};
212             }
213             }
214              
215             # clean out known attributes
216 48         221 delete @{$args}{
217 48         99 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 48 100       175 if ( keys %$args ) {
223 1         5 _croak( "unknown options passed to ",
224             __PACKAGE__, "::import: ", join( ', ', keys %$args ) );
225             }
226             }
227              
228 44         25073 return @return;
229             }
230              
231             sub _build_class {
232 55     55   121 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 55 100       143 if ( exists $attr->{-recurse} ) {
239             _croak( "-recurse must be a number" )
240 14 100       44 unless Scalar::Util::looks_like_number( $attr->{-recurse} );
241 13 100       31 delete $attr->{-recurse} if $attr->{-recurse} == 0;
242             }
243              
244 54 100       155 if ( !defined $attr->{-class} ) {
    100          
245              
246             my @class = map {
247 41         185 ( my $key = $_ ) =~ s/-//;
  73         300  
248 73 50       297 ( $key, defined $attr->{$_} ? $attr->{$_} : "" )
249             } sort keys %$attr;
250              
251 41         489 $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         4 $attr->{-class} = $caller . '::' . $name;
259             }
260              
261 53         109 my $class = $attr->{-class};
262              
263 53 100       149 return $class if defined $REGISTRY{$class};
264 51         179 my $rentry = $REGISTRY{$class} = { methods => {} };
265              
266 51         94 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 51         244 meta => [ map { ( qq[q($_) => q($attr->{$_}),] ) } keys %$attr ],
  138         754  
278             predicate_template => '',
279             );
280              
281 51 100       229 if ( $attr->{-lvalue} ) {
282 5 50       16 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         11 $dict{autoload_attr} = q[: lvalue];
288 5         10 $dict{signature} = q[: lvalue];
289             }
290             }
291              
292 51 100       144 if ( $attr->{-undef} ) {
293 7         13 $dict{validate_method} = q[ 1 ];
294 7         13 $dict{validate_inline} = q[ 1 ];
295             }
296              
297 51 100       130 if ( $attr->{-exists} ) {
298 14 100       106 $dict{exists} = $attr->{-exists} =~ PerlIdentifier ? $1 : 'exists';
299 14         22 push @{ $dict{body} }, q[ sub <> { exists $_[0]->{$_[1] } } ];
  14         33  
300 14         44 $rentry->{methods}{$dict{exists}} = undef;
301             }
302              
303 51 100       118 if ( $attr->{-defined} ) {
304 2 100       16 $dict{defined} = $attr->{-defined} =~ PerlIdentifier ? $1 : 'defined';
305 2         3 push @{ $dict{body} }, q[ sub <> { defined $_[0]->{$_[1] } } ];
  2         4  
306 2         6 $rentry->{methods}{$dict{defined}} = undef;
307             }
308              
309 51 100       126 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 51 100       111 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       55 $dict{recurse_limit} = --$attr->{-recurse} < 0 ? -1 : $attr->{-recurse};
327              
328 8         15 $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         12 $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       43 );
356 8         26 delete @attr{ qw( -as_scalar_ref -class -base -as ) };
357 8         20 $closures{'$attr'} = \%attr;
358             }
359              
360              
361              
362 51 50       112 if ( $attr->{-predicate} ) {
363 0         0 $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 51         93 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 51 50       145 _compile_from_tpl( \$class_template, \%dict, \%closures )
442             or _croak_about_code( \$class_template, "class $class" );
443              
444 51 100       212 if ( !!$attr->{-new} ) {
445 5 50       28 my $name = $attr->{-new} =~ PerlIdentifier ? $1 : 'new';
446 5         31 _build_constructor( $class, $name, { %$attr, -as_method => 1 } );
447             }
448              
449 51 100       147 if ( $attr->{-methods} ) {
450              
451 16         28 my $methods = $attr->{-methods};
452 16 100       50 _croak( "-methods option value must be a hashref" )
453             unless 'HASH' eq ref $methods;
454              
455 15         53 for my $mth ( keys %$methods ) {
456 16 100       121 _croak( "method name '$mth' is not a valid Perl identifier" )
457             if $mth !~ PerlIdentifier;
458              
459 15         29 my $code = $methods->{$mth};
460 15 100       41 _croak( qq{value for method "$mth" must be a coderef} )
461             unless 'CODE' eq ref $code;
462 17     17   282 no strict 'refs'; ## no critic (ProhibitNoStrict)
  17         44  
  17         20806  
463 14         22 *{"${class}::${mth}"} = $code;
  14         90  
464             }
465              
466 13         51 $rentry->{methods}{$_} = undef for keys %$methods;
467             }
468              
469 48         119 push @CARP_NOT, $class;
470             $rentry->{accessor_template}
471 48         172 = _find_symbol( $class, "accessor_template", [ "SCALAR", undef ] );
472              
473 48 50       177 if ( $attr->{-predicate} ) {
474             $rentry->{predicate_template}
475 0         0 = _find_symbol( $class, "predicate_template", [ "SCALAR", undef ] );
476             }
477              
478 48         139 $rentry->{validate} = _find_symbol( $class, 'validate', [ 'REF', 'CODE' ] );
479              
480 48         266 Scalar::Util::weaken( $rentry->{validate} );
481              
482 48         212 return $class;
483             }
484              
485             sub _build_constructor {
486 51     51   119 my ( $package, $name, $args ) = @_;
487              
488             # closure for user provided clone sub
489 51         78 my $clone;
490              
491             _croak( "cannot mix -copy and -clone" )
492 51 100 100     142 if exists $args->{-copy} && exists $args->{-clone};
493              
494 50         206 my %dict = (
495             package => $package,
496             constructor_name => $name,
497             use => [],
498             package_return_value => '1;',
499             );
500              
501 50         85 $dict{class} = do {
502 50 100       111 if ( $args->{-as_method} ) {
503 5         11 'shift;';
504             }
505             else {
506              
507 45         137 'q[' . $args->{-class} . '];';
508             }
509             };
510              
511 50         86 $dict{copy} = do {
512 50 100       225 if ( $args->{-copy} ) {
    100          
513 1         3 '$hash = { %{ $hash } };';
514             }
515              
516             elsif ( exists $args->{-clone} ) {
517 3 100       10 if ( 'CODE' eq ref $args->{-clone} ) {
518 1         2 $clone = $args->{-clone};
519 1         4 '$hash = $clone->($hash);';
520             }
521             else {
522 2         3 push @{ $dict{use} }, q[use Storable ();];
  2         5  
523 2         7 '$hash = Storable::dclone $hash;';
524             }
525             }
526             };
527              
528 50         89 $dict{lock} = do {
529 50 100       213 if ( $args->{-immutable} ) {
    100          
530 1         1 push @{ $dict{use} }, q[use Hash::Util ();];
  1         3  
531 1         3 'Hash::Util::lock_hash(%$hash)';
532             }
533             elsif ( defined $args->{-lockkeys} ) {
534              
535 3 100       10 if ( 'ARRAY' eq ref $args->{-lockkeys} ) {
    50          
536             _croak(
537             "-lockkeys: attribute name ($_) is not a valid Perl identifier"
538 2         5 ) for grep { $_ !~ PerlIdentifier } @{ $args->{-lockkeys} };
  4         26  
  2         4  
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         6  
543             }
544             elsif ( $args->{-lockkeys} ) {
545 1         1 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 49 100 100     283 if ( $args->{-as_scalar_ref} || $args->{-as_return} ) {
554 17         30 $dict{package_return_value} = '';
555 17         24 $dict{constructor_name} = '';
556             }
557              
558             #<<< no tidy
559 49         109 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 49   33     229 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 49 100       167 ${$name} = $result if $args->{-as_scalar_ref};
  7         16  
589 49         221 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 177     177   360 my ( $code, $dict, $closures ) = @_;
609              
610 177 100 100     594 if ( defined $closures && %$closures) {
611             $dict->{closures}
612 57         178 = join( "\n", map { "my $_ = \$closures->{'$_'};" } keys %$closures );
  57         275  
613             }
614              
615 177         476 _interpolate( $code, $dict );
616              
617 177 50       386 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 17 50 66 17   245 eval( $$code ); ## no critic (ProhibitStringyEval)
  17 100 33 17   52  
  17 100 33 17   3433  
  17 100 33 16   3148  
  17 100 33 14   8078  
  17 100 66 13   662  
  17 100 33 11   130  
  17 50 33 11   49  
  17 100 50 10   2004  
  16 100 50 9   221  
  16 50 50 9   81  
  16 100   9   2555  
  14 100   5   255  
  14 50   5   59  
  14 50   4   964  
  13 100   4   228  
  13 50   4   36  
  13 50   4   1679  
  11 50   4   1095  
  11 50   3   3409  
  11 50   3   1047  
  11 50   3   754  
  11 50   3   3501  
  11 50   3   898  
  10 50   3   198  
  10 50   3   28  
  10 50   3   994  
  9 50   3   218  
  9 50   3   23  
  9 50   26   1110  
  9 50   17   777  
  9 50   3   25  
  9 50   3   790  
  9 50   4   393  
  9 50   3   28  
  9 50   1   827  
  5 50   0   60  
  5 50   0   22  
  5 50   0   570  
  5 50   0   145  
  5 50   0   16  
  5 50   0   691  
  4 50   0   186  
  4     22   22  
  4     17   146  
  4     11   259  
  4     13   14  
  4     14   427  
  4     15   310  
  4     20   13  
  4     18   371  
  4     7   41  
  4     7   12  
  4     5   269  
  4     5   204  
  4     9   14  
  4     8   474  
  4     8   145  
  3     4   7  
  3     2   273  
  3     7   23  
  3     1   6  
  3     1   437  
  3     2   23  
  3     13   5  
  3     6   345  
  3     2   22  
  3     2   6  
  3     1   448  
  3     3   24  
  3         6  
  3         586  
  3         23  
  3         20  
  3         175  
  3         20  
  3         7  
  3         314  
  3         24  
  3         7  
  3         466  
  3         21  
  3         8  
  3         322  
  3         22  
  3         46  
  3         628  
  177         15482  
  26         19275  
  26         177  
  17         4700  
  17         72  
  3         453  
  3         10  
  3         464  
  3         11  
  4         1351  
  4         16  
  3         472  
  3         11  
  1         3  
  1         4  
  0         0  
  0         0  
  32         740  
  32         261  
  32         189  
  32         162  
  32         253  
  26         1264  
  26         248  
  24         88  
  25         124  
  25         217  
  14         2693  
  16         3341  
  11         3467  
  9         2046  
  7         2240  
  12         2578  
  12         5744  
  12         675  
  15         743  
  13         17410  
  20         907  
  21         2454  
  17         120  
  16         1722  
  20         1618  
  20         5235  
  19         114  
  17         123  
  14         47  
  16         127  
  8         77  
  6         2556  
  6         74  
  5         23  
  4         42  
  4         38  
  4         25  
  4         2554  
  4         20  
  4         27  
  1         4  
  1         9  
  2         96  
  2         122  
  2         2469  
  2         8  
  2         13  
  1         48  
  6         15702  
  7         40  
  6         36  
  1         24  
  1         47  
  6         46  
  2         16  
  1         5  
  1         2  
  1         49  
  1         5  
  1         7  
  1         8  
  1         3  
  1         48  
  1         5  
  1         7  
  1         4  
  1         10  
  2         736  
  2         14  
  2         22  
  1         6  
  1         5  
  2         60  
  12         17195  
  7         38  
  7         63  
  3         12  
  3         13  
  6         83  
  5         11768  
  5         31  
  5         33  
  2         8  
  1         6102  
  4         25  
  1         8  
  0         0  
  0         0  
  1         7  
  1         8  
  1         3957  
  1         16  
  1         7  
  0         0  
  0         0  
  1         5  
  1         25  
625             }
626              
627             sub _interpolate {
628 2023     2023   2961 my ( $tpl, $dict, $work ) = @_;
629 2023 100       3623 $work = { loop => {} } unless defined $work;
630              
631 2023         4486 $$tpl =~ s{(\\)?\<\<(\w+)\>\>
632             }{
633 2313 100       4832 if ( defined $1 ) {
634 427         1540 "<<$2>>";
635             }
636             else {
637 1891         3350 my $key = lc $2;
638 1891         2940 my $v = $dict->{$key};
639 1891 100       2895 if ( defined $v ) {
640 1848 100       3568 $v = join( "\n", @$v )
641             if 'ARRAY' eq ref $v;
642              
643             _croak( "circular interpolation loop detected for $key" )
644 1845 100       4013 if $work->{loop}{$key}++;
645 1845         4860 _interpolate( \$v, $dict, $work );
646 1845         2730 --$work->{loop}{$key};
647 1845         7769 $v;
648             }
649             else {
650 43         180 '';
651             }
652             }
653             }gex;
654 2022         3280 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__