File Coverage

blib/lib/Hash/Wrap.pm
Criterion Covered Total %
statement 425 452 94.0
branch 181 234 77.3
condition 45 81 55.5
subroutine 84 95 88.4
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   2689787 use 5.01000;
  20         132  
6              
7 20     20   467 use strict;
  20         49  
  20         284  
8 20     20   308 use warnings;
  20         54  
  20         354  
9              
10             ## no critic(ValuesAndExpressions::ProhibitAccessOfPrivateData)
11              
12 20     20   481 use Scalar::Util;
  20         47  
  20         664  
13 20     18   464 use Digest::MD5;
  18         35  
  18         3582  
14             our $VERSION = '0.22';
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   257 use constant PerlIdentifier => qr/\A([^\W\d]\w*+)\z/;
  18         36  
  18         3948  
23              
24             our %REGISTRY;
25              
26             sub _croak {
27 21     21   116 require Carp;
28 21         3090 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   25 my ( $object, $method ) = @_;
39 10   33     53 my $class = Scalar::Util::blessed( $object ) || ref( $object ) || $object;
40 10         64 _croak ( qq[Can't locate object method "$method" via package "$class"] );
41             }
42              
43              
44             sub _find_symbol {
45 99     99   202 my ( $package, $symbol, $reftype ) = @_;
46              
47 18     18   315 no strict 'refs'; ## no critic (ProhibitNoStrict)
  18         46  
  18         8864  
48 99         119 my $candidate = *{"$package\::$symbol"}{SCALAR};
  99         305  
49              
50             return $$candidate
51             if defined $candidate
52             && 2 ==
53 99 100 66     621 grep { defined $_->[0] && defined $_->[1] ? $_->[0] eq $_->[1] : 1 }
  198 50 33     924  
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   159 my ( $hash_class, $class, $key ) = @_;
63              
64 77         239 my %dict = (
65             key => $key,
66             class => $class,
67             );
68              
69 77         178 my $code = $REGISTRY{$hash_class}{accessor_template};
70 77         221 my $coderef = _compile_from_tpl( \$code, \%dict );
71 77 50       244 _croak_about_code( \$code, 'accessor' )
72             if $@;
73              
74 77         1317 return $coderef;
75             }
76              
77             sub _generate_predicate {
78 4     4   11 my ( $hash_class, $class, $key ) = @_;
79              
80 4         33 my %dict = (
81             key => $key,
82             class => $class,
83             );
84              
85 4         8 my $code = $REGISTRY{$hash_class}{predicate_template};
86 4         17 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   127 my ( $hash_class, $method, $object ) = @_;
96              
97 61         300 my ( $class, $key ) = $method =~ /(.*)::(.*)/;
98              
99 61 50       233 _croak_class_method( $object, $key )
100             unless Scalar::Util::blessed( $object );
101              
102 61 100 66     239 if ( exists $REGISTRY{$hash_class}{predicate_template}
103             && $key =~ /^has_(.*)/ )
104             {
105 4         10 return _generate_predicate( $hash_class, $class, $1 );
106             }
107              
108             _croak_object_method( $object, $key )
109 57 100       906 unless $REGISTRY{$hash_class}{validate}->( $object, $key );
110              
111 50         123 _generate_accessor( $hash_class, $class, $key );
112             }
113              
114             sub _can {
115 55     55   151 my ( $self, $key, $CLASS ) = @_;
116              
117 55         181 my $class = Scalar::Util::blessed( $self );
118 55 50       156 return if !defined $class;
119              
120 55 100       244 if ( !exists $self->{$key} ) {
121              
122 23 100       80 if ( exists $Hash::Wrap::REGISTRY{$class}{methods}{$key} ) {
123             ## no critic (ProhibitNoStrict)
124 18     18   153 no strict 'refs';
  18         34  
  18         1274  
125 12         29 my $method = "${class}::$key";
126 12         17 return *{$method}{CODE};
  12         66  
127             }
128 11         171 return;
129             }
130              
131 32         101 my $method = "${class}::$key";
132              
133             ## no critic (ProhibitNoStrict)
134 18     18   230 no strict 'refs';
  18         33  
  18         19480  
135             return *{$method}{CODE}
136 32   66     51 || Hash::Wrap::_generate_accessor( $CLASS, $class, $key );
137             }
138              
139             sub import {
140 56     56   49303 shift;
141 56         187 my $caller = caller;
142              
143 56         142 my @imports = @_;
144 56 100       181 push @imports, @EXPORT unless @imports;
145              
146 56         96 my @return;
147              
148 56         124 for my $args ( @imports ) {
149 59 100       246 if ( !ref $args ) {
    50          
150             _croak( "$args is not exported by ", __PACKAGE__ )
151 5 100       12 unless grep { /$args/ } @EXPORT;
  5         88  
152              
153 4         12 $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         185 $args = {%$args};
166             }
167              
168             _croak( "cannot mix -base and -class" )
169 58 100 100     216 if !!$args->{-base} && exists $args->{-class};
170              
171 57   33     296 $DEBUG = $ENV{HASH_WRAP_DEBUG} // delete $args->{-debug};
172              
173 57 100       147 $args->{-as} = 'wrap_hash' unless exists $args->{-as};
174 57         137 my $name = delete $args->{-as};
175              
176 57 100       139 if ( defined $name ) {
177              
178 56 100       262 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         20 $args->{-as_scalar_ref} = $name;
191              
192             }
193              
194             elsif ( $name eq '-return' ) {
195 10         31 $args->{-as_return} = 1;
196             }
197             }
198              
199 56 100       146 if ( $args->{-base} ) {
200             _croak( "don't use -as => -return with -base" )
201 3 50       8 if $args->{-as_return};
202 3         5 $args->{-class} = $caller;
203 3 50       8 $args->{-new} = 1 unless !!$args->{-new};
204 3         6 _build_class( $caller, $name, $args );
205             }
206              
207             else {
208 53         155 _build_class( $caller, $name, $args );
209 48 100       109 if ( defined $name ) {
210 47         117 my $sub = _build_constructor( $caller, $name, $args );
211 45 100       136 push @return, $sub if $args->{-as_return};
212             }
213             }
214              
215             # clean out known attributes
216 49         225 delete @{$args}{
217 49         94 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       185 if ( keys %$args ) {
223 1         6 _croak( "unknown options passed to ",
224             __PACKAGE__, "::import: ", join( ', ', keys %$args ) );
225             }
226             }
227              
228 45         22981 return @return;
229             }
230              
231             sub _build_class {
232 56     56   130 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       167 if ( exists $attr->{-recurse} ) {
239             _croak( "-recurse must be a number" )
240 14 100       94 unless Scalar::Util::looks_like_number( $attr->{-recurse} );
241 13 100       62 delete $attr->{-recurse} if $attr->{-recurse} == 0;
242             }
243              
244 55 100       217 if ( !defined $attr->{-class} ) {
    100          
245              
246             my @class = map {
247 42         221 ( my $key = $_ ) =~ s/-//;
  74         242  
248 74 50       275 ( $key, defined $attr->{$_} ? $attr->{$_} : "" )
249             } sort keys %$attr;
250              
251 42         514 $attr->{-class} = join '::', 'Hash::Wrap::Class',
252             Digest::MD5::md5_hex( @class );
253             }
254              
255             elsif ( $attr->{-class} eq '-caller' ) {
256 2 100       7 _croak( "can't set -class => '-caller' if -as is not a plain string" )
257             if ref $name;
258 1         3 $attr->{-class} = $caller . '::' . $name;
259             }
260              
261 54         114 my $class = $attr->{-class};
262              
263 54 100       142 return $class if defined $REGISTRY{$class};
264 52         178 my $rentry = $REGISTRY{$class} = { methods => {} };
265              
266 52         93 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         252 meta => [ map { ( qq[q($_) => q($attr->{$_}),] ) } keys %$attr ],
  140         737  
278             predicate_template => '',
279             );
280              
281 52 100       231 if ( $attr->{-lvalue} ) {
282 5 50       15 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         8 $dict{signature} = q[: lvalue];
289             }
290             }
291              
292 52 100       141 if ( $attr->{-undef} ) {
293 7         9 $dict{validate_method} = q[ 1 ];
294 7         8 $dict{validate_inline} = q[ 1 ];
295             }
296              
297 52 100       120 if ( $attr->{-exists} ) {
298 14 100       158 $dict{exists} = $attr->{-exists} =~ PerlIdentifier ? $1 : 'exists';
299 14         22 push @{ $dict{body} }, q[ sub <> { exists $_[0]->{$_[1] } } ];
  14         55  
300 14         52 $rentry->{methods}{$dict{exists}} = undef;
301             }
302              
303 52 100       127 if ( $attr->{-defined} ) {
304 2 100       13 $dict{defined} = $attr->{-defined} =~ PerlIdentifier ? $1 : 'defined';
305 2         4 push @{ $dict{body} }, q[ sub <> { defined $_[0]->{$_[1] } } ];
  2         5  
306 2         4 $rentry->{methods}{$dict{defined}} = undef;
307             }
308              
309 52 100       137 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       125 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         17 $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         13 $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       57 );
356 8         38 delete @attr{ qw( -as_scalar_ref -class -base -as ) };
357 8         23 $closures{'$attr'} = \%attr;
358             }
359              
360              
361              
362 52 100       102 if ( $attr->{-predicate} ) {
363 1         2 $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         108 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       159 _compile_from_tpl( \$class_template, \%dict, \%closures )
442             or _croak_about_code( \$class_template, "class $class" );
443              
444 52 100       207 if ( !!$attr->{-new} ) {
445 5 50       19 my $name = $attr->{-new} =~ PerlIdentifier ? $1 : 'new';
446 5         27 _build_constructor( $class, $name, { %$attr, -as_method => 1 } );
447             }
448              
449 52 100       139 if ( $attr->{-methods} ) {
450              
451 16         37 my $methods = $attr->{-methods};
452 16 100       63 _croak( "-methods option value must be a hashref" )
453             unless 'HASH' eq ref $methods;
454              
455 15         79 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         37 my $code = $methods->{$mth};
460 15 100       54 _croak( qq{value for method "$mth" must be a coderef} )
461             unless 'CODE' eq ref $code;
462 18     18   235 no strict 'refs'; ## no critic (ProhibitNoStrict)
  18         44  
  18         18717  
463 14         23 *{"${class}::${mth}"} = $code;
  14         94  
464             }
465              
466 13         60 $rentry->{methods}{$_} = undef for keys %$methods;
467             }
468              
469 49         122 push @CARP_NOT, $class;
470             $rentry->{accessor_template}
471 49         207 = _find_symbol( $class, "accessor_template", [ "SCALAR", undef ] );
472              
473 49 100       173 if ( $attr->{-predicate} ) {
474             $rentry->{predicate_template}
475 1         2 = _find_symbol( $class, "predicate_template", [ "SCALAR", undef ] );
476             }
477              
478 49         132 $rentry->{validate} = _find_symbol( $class, 'validate', [ 'REF', 'CODE' ] );
479              
480 49         262 Scalar::Util::weaken( $rentry->{validate} );
481              
482 49         205 return $class;
483             }
484              
485             sub _build_constructor {
486 52     52   114 my ( $package, $name, $args ) = @_;
487              
488             # closure for user provided clone sub
489 52         66 my $clone;
490              
491             _croak( "cannot mix -copy and -clone" )
492 52 100 100     146 if exists $args->{-copy} && exists $args->{-clone};
493              
494 51         217 my %dict = (
495             package => $package,
496             constructor_name => $name,
497             use => [],
498             package_return_value => '1;',
499             );
500              
501 51         71 $dict{class} = do {
502 51 100       106 if ( $args->{-as_method} ) {
503 5         8 'shift;';
504             }
505             else {
506              
507 46         141 'q[' . $args->{-class} . '];';
508             }
509             };
510              
511 51         71 $dict{copy} = do {
512 51 100       198 if ( $args->{-copy} ) {
    100          
513 1         6 '$hash = { %{ $hash } };';
514             }
515              
516             elsif ( exists $args->{-clone} ) {
517 3 100       8 if ( 'CODE' eq ref $args->{-clone} ) {
518 1         2 $clone = $args->{-clone};
519 1         3 '$hash = $clone->($hash);';
520             }
521             else {
522 2         6 push @{ $dict{use} }, q[use Storable ();];
  2         4  
523 2         7 '$hash = Storable::dclone $hash;';
524             }
525             }
526             };
527              
528 51         67 $dict{lock} = do {
529 51 100       261 if ( $args->{-immutable} ) {
    100          
530 1         2 push @{ $dict{use} }, q[use Hash::Util ();];
  1         2  
531 1         2 'Hash::Util::lock_hash(%$hash)';
532             }
533             elsif ( defined $args->{-lockkeys} ) {
534              
535 3 100       11 if ( 'ARRAY' eq ref $args->{-lockkeys} ) {
    50          
536             _croak(
537             "-lockkeys: attribute name ($_) is not a valid Perl identifier"
538 2         3 ) for grep { $_ !~ PerlIdentifier } @{ $args->{-lockkeys} };
  4         21  
  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         4  
543             }
544             elsif ( $args->{-lockkeys} ) {
545 1         1 push @{ $dict{use} }, q[use Hash::Util ();];
  1         3  
546 1         3 '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     281 if ( $args->{-as_scalar_ref} || $args->{-as_return} ) {
554 17         35 $dict{package_return_value} = '';
555 17         35 $dict{constructor_name} = '';
556             }
557              
558             #<<< no tidy
559 50         91 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     212 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       168 ${$name} = $result if $args->{-as_scalar_ref};
  7         17  
589 50         211 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   314 my ( $code, $dict, $closures ) = @_;
609              
610 183 100 100     599 if ( defined $closures && %$closures) {
611             $dict->{closures}
612 58         163 = join( "\n", map { "my $_ = \$closures->{'$_'};" } keys %$closures );
  58         266  
613             }
614              
615 183         493 _interpolate( $code, $dict );
616              
617 183 50       375 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   204 eval( $$code ); ## no critic (ProhibitStringyEval)
  18 100 33 18   39  
  18 100 33 18   3177  
  18 100 33 17   1544  
  18 100 33 15   5876  
  18 100 66 14   590  
  18 50 33 12   124  
  18 50 33 11   47  
  18 100 50 10   1766  
  17 100 50 9   237  
  17 50 50 9   65  
  17 100   9   2186  
  15 100   5   284  
  15 50   5   58  
  15 50   4   930  
  14 50   4   195  
  14 50   4   33  
  14 50   4   1480  
  12 50   4   1000  
  12 50   3   2830  
  12 50   3   1012  
  11 50   3   667  
  11 50   3   2749  
  11 50   3   799  
  10 50   3   212  
  10 50   3   24  
  10 50   3   849  
  9 50   3   189  
  9 50   3   22  
  9 50   28   818  
  9 50   20   200  
  9 50   8   24  
  9 50   1   649  
  9 50   1   344  
  9 50   3   21  
  9 50   0   707  
  5 50   0   59  
  5 50   0   14  
  5 50   0   480  
  5 50   0   155  
  5 50   0   15  
  5 50   0   597  
  4 50   0   176  
  4     20   13  
  4     16   133  
  4     12   192  
  4     13   12  
  4     15   379  
  4     13   247  
  4     19   13  
  4     17   341  
  4     9   47  
  4     6   12  
  4     3   208  
  4     5   155  
  4     11   12  
  4     8   513  
  4     10   115  
  3     5   6  
  3     3   202  
  3     8   21  
  3     1   6  
  3     1   393  
  3     4   24  
  3     14   12  
  3     7   336  
  3     3   18  
  3     3   7  
  3     1   323  
  3     1   22  
  3         6  
  3         519  
  3         20  
  3         7  
  3         153  
  3         16  
  3         14  
  3         323  
  3         19  
  3         8  
  3         365  
  3         21  
  3         4  
  3         324  
  3         24  
  3         95  
  3         528  
  183         14339  
  28         18564  
  28         91  
  20         4064  
  20         66  
  8         4678  
  8         22  
  1         3  
  1         4  
  1         671  
  1         10  
  3         370  
  3         10  
  0         0  
  0         0  
  0         0  
  0         0  
  30         902  
  30         192  
  30         160  
  30         135  
  30         186  
  25         701  
  25         155  
  24         121  
  23         57  
  24         161  
  16         2391  
  13         3218  
  11         7214  
  9         1342  
  10         2421  
  11         1290  
  10         2492  
  12         2104  
  17         2381  
  13         11585  
  24         1869  
  21         2160  
  17         111  
  15         1363  
  18         254  
  21         4586  
  20         91  
  19         105  
  17         47  
  18         102  
  7         85  
  5         2049  
  5         65  
  5         19  
  4         19  
  3         7  
  5         1147  
  5         2083  
  5         20  
  4         21  
  1         5  
  2         50  
  5         122  
  5         106  
  5         1750  
  4         12  
  4         28  
  2         135  
  7         13141  
  8         39  
  7         40  
  1         11  
  1         46  
  7         45  
  2         15  
  1         4  
  1         11  
  1         45  
  1         6  
  1         6  
  1         4  
  1         3  
  1         44  
  1         5  
  1         6  
  1         4  
  1         10  
  2         601  
  2         12  
  2         12  
  1         4  
  1         11  
  2         55  
  13         13230  
  8         38  
  8         56  
  4         12  
  4         15  
  7         72  
  6         7514  
  6         30  
  6         28  
  3         8  
  2         5045  
  5         23  
  1         5  
  0         0  
  0         0  
  1         5  
  1         9  
  1         3367  
  1         13  
  1         5  
  0         0  
  0         0  
  1         5  
  1         10  
625             }
626              
627             sub _interpolate {
628 2076     2076   2597 my ( $tpl, $dict, $work ) = @_;
629 2076 100       3088 $work = { loop => {} } unless defined $work;
630              
631 2076         3917 $$tpl =~ s{(\\)?\<\<(\w+)\>\>
632             }{
633 2375 100       4015 if ( defined $1 ) {
634 439         1195 "<<$2>>";
635             }
636             else {
637 1937         2848 my $key = lc $2;
638 1937         2482 my $v = $dict->{$key};
639 1937 100       2341 if ( defined $v ) {
640 1893 100       2995 $v = join( "\n", @$v )
641             if 'ARRAY' eq ref $v;
642              
643             _croak( "circular interpolation loop detected for $key" )
644 1892 100       3387 if $work->{loop}{$key}++;
645 1892         3251 _interpolate( \$v, $dict, $work );
646 1892         2214 --$work->{loop}{$key};
647 1892         6456 $v;
648             }
649             else {
650 44         163 '';
651             }
652             }
653             }gex;
654 2075         2669 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__