File Coverage

blib/lib/Mock/Data/Template.pm
Criterion Covered Total %
statement 72 75 96.0
branch 37 46 80.4
condition 11 17 64.7
subroutine 17 17 100.0
pod 6 6 100.0
total 143 161 88.8


line stmt bran cond sub pod time code
1             package Mock::Data::Template;
2 12     12   82 use strict;
  12         25  
  12         369  
3 12     12   60 use warnings;
  12         23  
  12         634  
4 12     12   80 use overload '""' => sub { shift->to_string };
  12     16   29  
  12         143  
  16         34  
5             require Carp;
6 12     12   1000 use Mock::Data::Util qw( _parse_context _escape_str );
  12         31  
  12         74  
7             require Mock::Data::Generator;
8             our @ISA= qw( Mock::Data::Generator );
9              
10             # ABSTRACT: Create a generator that plugs other templates into a string
11             our $VERSION = '0.03'; # VERSION
12              
13              
14             sub new {
15 88     88 1 144 my $class= shift;
16             my %self= (@_ == 1 && !ref $_[0])? ( template => $_[0] )
17 88 0 33     424 : (@_ == 1 && ref $_[0] eq 'HASH')? %{$_[0]}
  0 0 0     0  
    50          
18             : @_ > 1? @_
19             : Carp::croak("Invalid constructor arguments to $class");
20             # Parse now, to report errors
21 88         286 $self{_compiled}= $class->parse_template($self{template}, { compile => 1 });
22 86         362 bless \%self, $class;
23             }
24              
25              
26 16     16 1 96 sub template { shift->{template} }
27              
28              
29             sub compile {
30 4     4 1 10 my $cmp= $_[0]{_compiled};
31 4 100   1   25 return ref $cmp? $cmp : sub { $cmp };
  1         6  
32             }
33              
34             sub generate {
35 112     112 1 364 my $cmp= shift->{_compiled};
36 112 100       362 return ref $cmp? $cmp->(@_) : $cmp;
37             }
38              
39              
40             sub to_string {
41 16     16 1 31 "template('" . shift->template . "')";
42             }
43              
44              
45             sub parse_template {
46 88     88 1 175 my ($self, $str, $flags)= @_;
47 88         142 local $_= $str;
48 88         218 pos= 0;
49 88         190 my $ret;
50 88         121 local $@;
51 88 100 50     167 defined eval { $ret= _parse_template($flags || {}) }
  88         224  
52             or Carp::croak("$@ at "._parse_context);
53 86         263 return $ret;
54             }
55              
56             # Parse a template string in $_ from pos($_)
57             sub _parse_template {
58 102     102   166 my @parts;
59 102         190 my $outer= !$_[0]{inner};
60 102 100       255 local $_[0]{inner}= 1 if $outer;
61 102         152 while (1) {
62             # Consume run of literal characters
63 129 100       636 push @parts, $1 if $outer? /\G([^{]+)/gc : /\G([^ \t\{\}]+)/gc;
    100          
64             # at end of template, or beginning of a reference to something
65 129 100       417 last unless /\G(?=\{)/gc;
66 29         84 push @parts, _parse_template_reference(@_);
67             }
68             # Combine adjacent scalars in the list
69 100   100     508 @parts= grep ref $_ || length, @parts;
70 100         289 for (my $i= $#parts - 1; $i >= 0; --$i) {
71 8 100 100     36 if (!ref $parts[$i] and !ref $parts[$i+1]) {
72 5         16 $parts[$i] .= splice(@parts, $i+1, 1);
73             }
74             }
75 100 50       208 if ($_[0]{compile}) {
76             return @parts == 1 && !ref $parts[0]? $parts[0]
77 19 100   19   58 : sub { join '', map +(ref($_)? $_->(@_) : $_), @parts }
78 100 100 100     651 } else {
79 0         0 return \@parts;
80             }
81             }
82              
83             # Parse one of the curly-brace notations
84             sub _parse_template_reference {
85 29 100   29   92 if (/\G\{([\w:]+)/gc) {
86 20         52 my $generator_name= $1;
87 20         33 my (@named_param, @pos_param);
88 20 100       60 if (/\G[ \t]+/gc) {
89 13         33 while (!/\G\}/gc) {
90 14 100       42 if (/\G(\w+)=/gc) {
91 6         17 push @named_param, $1, _parse_template(@_);
92             } else {
93 8         18 push @pos_param, _parse_template(@_);
94             }
95 14         58 /\G[ \t]*/gc;
96             }
97             } else {
98 7 100       33 /\G\}/gc or die "Expected '}'";
99             }
100 19 50       66 if ($_[0]{compile}) {
101             # compile by making a list of which params are function calls, and update lists for only those positions
102 19         34 my @named_literal= @named_param;
103 19         54 my @dynamic_named= grep ref $named_param[$_], 0 .. $#named_param;
104 19         36 my @pos_literal= @pos_param;
105 19         40 my @dynamic_pos= grep ref $pos_literal[$_], 0 .. $#pos_param;
106 19 100       44 if (@named_param) {
107             return sub {
108 6     6   36 $named_literal[$_]= $named_param[$_]->(@_) for @dynamic_named;
109 6         11 $pos_literal[$_]= $pos_param[$_]->(@_) for @dynamic_pos;
110 6         23 $_[0]->call($generator_name, { @named_literal }, @pos_literal);
111             }
112 6         32 } else {
113             return sub {
114 14     14   43 $pos_literal[$_]= $pos_param[$_]->(@_) for @dynamic_pos;
115 14         79 $_[0]->call($generator_name, @pos_literal);
116             }
117 13         77 }
118             } else {
119 0 0       0 return [ $generator_name, (@named_param? { @named_param }:()), @pos_param ];
120             }
121             }
122 9 100       52 return chr hex $1 if /\G\{ [#] ([0-9A-Za-z]+) \}/xgc;
123 2 100       8 return '' if /\G\{\}/xgc;
124 1         11 die "Invalid template notation\n";
125             }
126              
127             1;
128              
129             __END__