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   77 use strict;
  12         25  
  12         483  
3 12     12   59 use warnings;
  12         19  
  12         890  
4 12     12   92 use overload '""' => sub { shift->to_string };
  12     16   20  
  12         173  
  16         27  
5             require Carp;
6 12     12   1020 use Mock::Data::Util qw( _parse_context _escape_str );
  12         20  
  12         86  
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.04'; # VERSION
12              
13              
14             sub new {
15 88     88 1 126 my $class= shift;
16             my %self= (@_ == 1 && !ref $_[0])? ( template => $_[0] )
17 88 0 33     451 : (@_ == 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         327 $self{_compiled}= $class->parse_template($self{template}, { compile => 1 });
22 86         374 bless \%self, $class;
23             }
24              
25              
26 16     16 1 91 sub template { shift->{template} }
27              
28              
29             sub compile {
30 4     4 1 9 my $cmp= $_[0]{_compiled};
31 4 100   1   27 return ref $cmp? $cmp : sub { $cmp };
  1         3  
32             }
33              
34             sub generate {
35 106     106 1 427 my $cmp= shift->{_compiled};
36 106 100       281 return ref $cmp? $cmp->(@_) : $cmp;
37             }
38              
39              
40             sub to_string {
41 16     16 1 27 "template('" . shift->template . "')";
42             }
43              
44              
45             sub parse_template {
46 88     88 1 179 my ($self, $str, $flags)= @_;
47 88         144 local $_= $str;
48 88         280 pos= 0;
49 88         170 my $ret;
50 88         106 local $@;
51 88 100 50     123 defined eval { $ret= _parse_template($flags || {}) }
  88         207  
52             or Carp::croak("$@ at "._parse_context);
53 86         284 return $ret;
54             }
55              
56             # Parse a template string in $_ from pos($_)
57             sub _parse_template {
58 102     102   126 my @parts;
59 102         164 my $outer= !$_[0]{inner};
60 102 100       267 local $_[0]{inner}= 1 if $outer;
61 102         143 while (1) {
62             # Consume run of literal characters
63 129 100       615 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       431 last unless /\G(?=\{)/gc;
66 29         65 push @parts, _parse_template_reference(@_);
67             }
68             # Combine adjacent scalars in the list
69 100   100     442 @parts= grep ref $_ || length, @parts;
70 100         293 for (my $i= $#parts - 1; $i >= 0; --$i) {
71 8 100 100     60 if (!ref $parts[$i] and !ref $parts[$i+1]) {
72 5         12 $parts[$i] .= splice(@parts, $i+1, 1);
73             }
74             }
75 100 50       175 if ($_[0]{compile}) {
76             return @parts == 1 && !ref $parts[0]? $parts[0]
77 19 100   19   51 : sub { join '', map +(ref($_)? $_->(@_) : $_), @parts }
78 100 100 100     637 } 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   111 if (/\G\{([\w:]+)/gc) {
86 20         47 my $generator_name= $1;
87 20         45 my (@named_param, @pos_param);
88 20 100       61 if (/\G[ \t]+/gc) {
89 13         31 while (!/\G\}/gc) {
90 14 100       34 if (/\G(\w+)=/gc) {
91 6         12 push @named_param, $1, _parse_template(@_);
92             } else {
93 8         17 push @pos_param, _parse_template(@_);
94             }
95 14         42 /\G[ \t]*/gc;
96             }
97             } else {
98 7 100       33 /\G\}/gc or die "Expected '}'";
99             }
100 19 50       42 if ($_[0]{compile}) {
101             # compile by making a list of which params are function calls, and update lists for only those positions
102 19         31 my @named_literal= @named_param;
103 19         45 my @dynamic_named= grep ref $named_param[$_], 0 .. $#named_param;
104 19         32 my @pos_literal= @pos_param;
105 19         42 my @dynamic_pos= grep ref $pos_literal[$_], 0 .. $#pos_param;
106 19 100       31 if (@named_param) {
107             return sub {
108 6     6   26 $named_literal[$_]= $named_param[$_]->(@_) for @dynamic_named;
109 6         7 $pos_literal[$_]= $pos_param[$_]->(@_) for @dynamic_pos;
110 6         18 $_[0]->call($generator_name, { @named_literal }, @pos_literal);
111             }
112 6         34 } else {
113             return sub {
114 14     14   79 $pos_literal[$_]= $pos_param[$_]->(@_) for @dynamic_pos;
115 14         74 $_[0]->call($generator_name, @pos_literal);
116             }
117 13         82 }
118             } else {
119 0 0       0 return [ $generator_name, (@named_param? { @named_param }:()), @pos_param ];
120             }
121             }
122 9 100       43 return chr hex $1 if /\G\{ [#] ([0-9A-Za-z]+) \}/xgc;
123 2 100       7 return '' if /\G\{\}/xgc;
124 1         9 die "Invalid template notation\n";
125             }
126              
127             1;
128              
129             __END__