File Coverage

lib/DBIx/DR/PerlishTemplate.pm
Criterion Covered Total %
statement 321 386 83.1
branch 44 54 81.4
condition 7 15 46.6
subroutine 166 166 100.0
pod 0 12 0.0
total 538 633 84.9


line stmt bran cond sub pod time code
1 5     5   24013 use utf8;
  3         3  
  5         41  
2 4     5   87 use strict;
  6         132  
  5         58  
3 5     5   71 use warnings;
  3         3  
  5         768  
4              
5             package DBIx::DR::PerlishTemplate;
6 3     5   406 use Mouse;
  4         18115  
  4         14  
7 5     5   657 use Carp;
  3         4  
  5         175  
8 4     5   14 use Scalar::Util;
  6         98  
  5         109  
9 5     5   306 use DBIx::DR::ByteStream;
  3         3  
  5         4283  
10              
11             has line_tag => (is => 'rw', isa => 'Str', default => '%');
12             has open_tag => (is => 'rw', isa => 'Str', default => '<%');
13             has close_tag => (is => 'rw', isa => 'Str', default => '%>');
14             has quote_mark => (is => 'rw', isa => 'Str', default => '=');
15             has immediate_mark => (is => 'rw', isa => 'Str', default => '==');
16              
17             has sql => (is => 'ro', isa => 'Str', default => '');
18             has variables => (is => 'ro', isa => 'ArrayRef');
19              
20             has template => (is => 'rw', isa => 'Str', default => '');
21             has template_file => (is => 'rw', isa => 'Str', default => '');
22              
23             has stashes => (is => 'ro', isa => 'ArrayRef');
24             has pretokens => (is => 'ro', isa => 'ArrayRef');
25             has prepretokens => (is => 'ro', isa => 'ArrayRef');
26             has parsed_template => (is => 'ro', isa => 'Str', default => '');
27             has namespace => (is => 'rw', isa => 'Str',
28             default => 'DBIx::DR::PerlishTemplate::Sandbox');
29              
30              
31             has sql_utf8 => (is => 'ro', isa => 'Bool', default => 1);
32             sub _render {
33 62     62   96 my ($_PTPL) = @_;
34 60         49 my $_PTSUB;
35              
36 61 50       170 unless ($_PTPL->parsed_template) {
37 62         151 $_PTSUB = $_PTPL->{parsed_template} = $_PTPL->_parse;
38             } else {
39 1         2 $_PTSUB = $_PTPL->parsed_template;
40             }
41              
42 60         68 $_PTPL->{parsed_template} = $_PTSUB;
43              
44 4     5   44 my $esub = eval $_PTSUB;
  1     5   5  
  5     5   145  
  5     5   14  
  6     5   181  
  3     5   7  
  3     5   135  
  12     5   18  
  5     5   122  
  1     5   2  
  3     5   119  
  0     5   0  
  5     5   71  
  2     5   6  
  3     16   104  
  3     2   11  
  5     2   131  
  1     2   2  
  3     2   109  
  0     2   0  
  5     2   97  
  1     2   9  
  4     2   185  
  2     2   5  
  5     2   66  
  2     2   6  
  5     2   155  
  6     2   11  
  4     2   108  
  1     2   2  
  3     2   166  
  2     2   4  
  5     2   127  
  1     2   2  
  3     2   126  
  3     2   6  
  5     2   110  
  3     2   36  
  4     2   204  
  19     2   48  
  5     2   104  
  4     2   10  
  3     2   154  
  5     2   72  
  3     2   92  
  1     2   55  
  5     2   257  
  11     2   15  
  4     2   143  
  43     2   62  
  6     2   162  
  1     2   4  
  3     2   95  
  6     2   9  
  62     2   6225  
  0     2   0  
  1     2   46  
  0     1   0  
  1     1   4  
  0     1   0  
  1     1   35  
  4     1   8  
  1     1   31  
  1     1   3  
  1     1   28  
  0     1   0  
  1     1   31  
  0     1   0  
  1     1   27  
  0     1   0  
  1     1   50  
  0     1   0  
  1     1   4  
  0     1   0  
  1     1   35  
  2     1   3  
  1     1   33  
  1     1   3  
  1     1   27  
  0     1   0  
  1     1   27  
  0     1   0  
  1     1   29  
  0     1   0  
  1     1   43  
  0     1   0  
  1     1   7  
  0     1   0  
  1     1   36  
  0     1   0  
  1     1   28  
  0     1   0  
  1     1   27  
  0     1   0  
  1     1   27  
  0     1   0  
  1     1   29  
  0     1   0  
  1     1   54  
  0     1   0  
  1     1   4  
  1     1   10  
  1     1   35  
  11     1   15  
  1     1   31  
  0     1   0  
  1     1   28  
  0     1   0  
  1     1   29  
  0     1   0  
  1     1   31  
  0     1   0  
  1     1   65  
  0     1   0  
  1     1   4  
  1     1   2  
  1     1   34  
  11     1   19  
  1     1   29  
  0     1   0  
  1     1   27  
  0     1   0  
  1     1   26  
  0     1   0  
  1     1   29  
  0     1   0  
  1     1   66  
  0     1   0  
  1     1   4  
  0     1   0  
  1     1   34  
  0     1   0  
  1     1   28  
  0     1   0  
  1     1   27  
  0     1   0  
  1     1   26  
  0     1   0  
  1     1   24  
  0     1   0  
  1     1   75  
  0         0  
  1         4  
  1         2  
  1         33  
  11         17  
  1         29  
  0         0  
  1         37  
  0         0  
  1         27  
  0         0  
  1         26  
  0         0  
  1         66  
  0         0  
  1         4  
  1         2  
  1         40  
  4         6  
  1         32  
  0         0  
  1         30  
  0         0  
  1         36  
  0         0  
  1         28  
  0         0  
  1         49  
  1         2  
  1         3  
  1         2  
  1         33  
  11         16  
  1         26  
  0         0  
  1         27  
  0         0  
  1         25  
  0         0  
  1         25  
  0         0  
  1         68  
  0         0  
  1         4  
  0         0  
  1         36  
  0         0  
  1         33  
  0         0  
  1         31  
  0         0  
  1         28  
  0         0  
  1         27  
  0         0  
  1         76  
  0         0  
  1         4  
  1         3  
  1         35  
  2         4  
  1         30  
  0         0  
  1         29  
  1         3  
  1         28  
  0         0  
  1         27  
  0         0  
  1         33  
  0            
45 60 100       165 if (my $e = $@) {
46 9         106 my $do_croak;
47             my $template;
48 22 100       46 if ($_PTPL->template_file) {
49 5         73 $template = $_PTPL->template_file;
50             } else {
51 4         9 $do_croak = 1;
52 5         73 $template = 'inline template';
53             };
54 6         74 $e =~ s{ at .*?line (\d+)(\.\s*|,\s+.*?)?$}
55 8         125 [" at $template line " . ( $1 - $_PTPL->pre_lines )]gsme;
56              
57 7 50       19 if ($1) {
58 7         123 $e =~ s/\s*$/\n/g;
59 7 100       60 die $e unless $do_croak;
60 5         386 croak $e;
61             }
62              
63 0         0 croak "$e at $template";
64             }
65              
66 57         185 $_PTPL->{sql} = '';
67 70         127 $_PTPL->{variables} = [];
68              
69 56         151 $esub->( @{ $_PTPL->stashes } );
  55         963  
70 55         367 1;
71             }
72              
73             sub render {
74 48     50 0 136 my ($self, $tpl, @args) = @_;
75 50         171 $self->{parsed_template} = '';
76 48         145 $self->template($tpl);
77 50         82 $self->template_file('');
78 48         58 $self->{stashes} = \@args;
79 50         166 $self->clean_namespace;
80 69         119 return $self->_render;
81             }
82              
83             sub render_file {
84 14     14 0 76 my ($self, $file, @args) = @_;
85 12 50 0     149 croak "File '@{[ $file // 'undef' ]}' not found or readable"
  2         59  
86             unless -r $file;
87 13         357 open my $fh, '<:raw', $file;
88 14         69 my $data;
89              
90 12         10 { local $/; $data = <$fh> }
  14         156  
  12         189  
91              
92 14 50       127 utf8::decode $data if $self->sql_utf8;
93              
94 13         22 $self->{parsed_template} = '';
95 15         110 $self->template_file($file);
96 14         26 $self->template($data);
97 13         56 $self->{stashes} = \@args;
98 16         27 $self->clean_namespace;
99 14         80 return $self->_render;
100             }
101              
102             sub clean_prepends {
103 48     50 0 53 my ($self) = @_;
104 50         127 $self->{pretokens} = [];
105 48         102 $self;
106             }
107              
108             sub clean_preprepends {
109 50     50 0 113 my ($self) = @_;
110 48         55 $self->{prepretokens} = [];
111 50         118 $self;
112             }
113              
114              
115             sub immediate {
116 269     270 0 256 my ($self, $str) = @_;
117 271 100       530 if (Scalar::Util::blessed $str) {
118 7 50       14 if ('DBIx::DR::ByteStream' eq Scalar::Util::blessed $str) {
    0          
119 8         40 $self->{sql} .= $str->content;
120             } elsif ($str->can('content')) {
121 0         0 $self->{sql} .= $str->content;
122             } else {
123 1         37 croak "Can't extract content from " . Scalar::Util::blessed $str;
124             }
125             } else {
126 265         316 $self->{sql} .= $str;
127             }
128 270         472 return DBIx::DR::ByteStream->new('');
129             }
130              
131             sub add_bind_value {
132 63     65 0 80 my ($self, @values) = @_;
133 65         107 push @{ $self->variables } => @values;
  63         164  
134             }
135              
136              
137             sub quote {
138 49     49 0 88 my ($self, $variable) = @_;
139              
140 49 100       123 if (Scalar::Util::blessed $variable) {
141 8 50       118 return $self->immediate($variable)
142             if 'DBIx::DR::ByteStream' eq Scalar::Util::blessed $variable;
143             }
144              
145 44         51 $self->{sql} .= '?';
146 44         122 $self->add_bind_value($variable);
147 43         89 return DBIx::DR::ByteStream->new('');
148             }
149              
150             sub _parse {
151 62     62   81 my ($self) = @_;
152              
153 61         109 my $result = '';
154              
155 61         131 my $immediate_mark = $self->immediate_mark;
156 63         100 my $quote_mark = $self->quote_mark;
157              
158             my $code_cb = sub {
159 72     74   59 my ($t) = @_;
160 74 50 33     293 return unless defined $t and length $t;
161              
162 72 100       220 if ($t =~ /^\Q$immediate_mark\E/) {
163 14         84 $result .= join '',
164             'immediate(',
165             substr($t, length($immediate_mark)),
166             ');';
167 12         15 return;
168             }
169              
170 62 100       198 if ($t =~ /^\Q$quote_mark\E/) {
171 38         120 $result .= join '',
172             'quote(',
173             substr($t, length($quote_mark)),
174             ');';
175 39         440 return;
176             }
177              
178 26         49 $result .= "$t;"; # always place ';' at end of code.
179 62         265 };
180              
181             my $text_cb = sub {
182 313     315   312 my ($content) = @_;
183 314 100 66     933 return unless defined $content and length $content;
184 250         215 $content =~ s/'/\\'/g;
185 249         405 $result .= "immediate('" . $content . "');";
186 62         156 };
187              
188 60         150 $self->_parse_ep($self->template, $text_cb, $code_cb);
189              
190 62         202 $result = join '',
191             'package ', $self->namespace, ';',
192             'BEGIN { ',
193             '*quote = sub { $_PTPL->quote(@_) };',
194             '*immediate = sub { $_PTPL->immediate(@_) };',
195             '};',
196             $self->preprepend,
197             'sub {', $self->prepend, $result, "\n}";
198              
199 60         425 return $result;
200             }
201              
202             sub _parse_ep {
203              
204 62     62   123 my ($self, $tpl, $text_cb, $code_cb) = @_;
205              
206             #---------------------------------------------------------
207             # единственные три переменные из self
208 61         99 my $line_tag = $self->line_tag;
209 62         146 my $open_tag = $self->open_tag;
210 61         92 my $close_tag = $self->close_tag;
211             # по идее это можно было оформить в виде независимого кода
212             #---------------------------------------------------------
213              
214 62         201 my @lines = split /\n/, $tpl;
215              
216 60         63 my $st = 'text';
217 63         102 my $code_text;
218              
219 60         132 for (my $i = 0; $i < @lines; $i++) {
220 182         218 local $_ = $lines[$i];
221              
222             CODE:
223 226 100       327 if ($st eq 'code') {
224 48 100       271 if (/^(.*?)\Q$close_tag\E(.*)/) {
225 43         59 $_ = $2;
226 44         163 $code_cb->($code_text . $1);
227 42         42 $code_text = undef;
228 44         90 $st = 'text';
229 42         142 goto ANYTEXT;
230             } else {
231 6         60 $code_text .= $_;
232 4         9 $code_text .= "\n";
233 6         118 next;
234             }
235             }
236              
237             TEXT_BEGIN:
238 177 100       666 if (/^(\s*)\Q$line_tag\E(.*)/) {
239 31         41 $text_cb->($1);
240 30 100       40 if ($i < $#lines) {
241 24         88 $code_cb->("$2\n");
242             } else {
243 10         15 $code_cb->($2);
244             }
245 31         86 next;
246             }
247              
248             ANYTEXT:
249 190 100       496 if (/^(.*?)\Q$open_tag\E(.*)/) {
250 43         111 $_ = $2;
251 42         87 $text_cb->($1);
252 43         81 $code_text = '';
253 42         37 $st = 'code';
254 43         203 goto CODE;
255             } else {
256 147         145 $text_cb->($_);
257 148 100       278 $text_cb->("\n") if $i < $#lines;
258 147         307 next;
259             }
260             }
261 61 50 33     158 $text_cb->("<%" . $code_text) if defined $code_text and length $code_text;
262             }
263              
264              
265             sub preprepend {
266 294     296 0 314 my ($self, @tokens) = @_;
267 295   100     470 $self->{prepretokens} ||= [];
268 297 100       367 push @{ $self->prepretokens } => map "$_;\n", @tokens if @tokens;
  229         627  
269 295 100       585 return join '' => @{ $self->prepretokens } if defined wantarray;
  67         272  
270             }
271              
272             sub prepend {
273 98     100 0 104 my ($self, @tokens) = @_;
274 99   50     199 $self->{pretokens} ||= [];
275 98 100       143 push @{ $self->pretokens } => map "$_;", @tokens if @tokens;
  33         126  
276 98 100       182 return join '' => @{ $self->pretokens } if defined wantarray;
  67         307  
277             }
278              
279              
280             sub pre_lines {
281 6     8 0 7 my ($self) = @_;
282 7         11 my $lines = 0;
283 7         12 $lines += @{[ /\n/g ]} for ($self->preprepend, $self->prepend);
  13         75  
284 9         36 return $lines;
285             }
286              
287             sub clean_prepend {
288 13     14 0 10600 my ($self) = shift;
289 13         20 $self->{pretokens} = [];
290             }
291              
292             sub clean_namespace {
293 61     62 0 94 my ($self) = @_;
294 60         103 my $sb = $self->namespace;
295              
296 3     5   15 no strict 'refs';
  5         50  
  3         201  
297 61         78 undef *{$sb . '::' . $_} for keys %{ $sb . '::' };
  60         401  
  178         752  
298             }
299              
300             1;
301              
302             =head1 NAME
303              
304             DBIx::DR::PerlishTemplate - template engine for L.
305              
306             =head1 COPYRIGHT
307              
308             Copyright (C) 2011 Dmitry E. Oboukhov
309             Copyright (C) 2011 Roman V. Nikolaev
310              
311             This program is free software, you can redistribute it and/or
312             modify it under the terms of the Artistic License.
313              
314             =cut
315              
316              
317              
318              
319