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   26203 use utf8;
  3         4  
  5         53  
2 4     5   74 use strict;
  6         111  
  5         67  
3 5     5   64 use warnings;
  3         4  
  5         135  
4              
5             package DBIx::DR::PerlishTemplate;
6 3     5   450 use Mouse;
  4         20729  
  4         20  
7 5     5   739 use Carp;
  3         4  
  5         207  
8 4     5   14 use Scalar::Util;
  6         115  
  5         123  
9 5     5   397 use DBIx::DR::ByteStream;
  3         5  
  5         4218  
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         48 my $_PTSUB;
35              
36 61 50       173 unless ($_PTPL->parsed_template) {
37 61         104 $_PTSUB = $_PTPL->{parsed_template} = $_PTPL->_parse;
38             } else {
39 2         51 $_PTSUB = $_PTPL->parsed_template;
40             }
41              
42 60         68 $_PTPL->{parsed_template} = $_PTSUB;
43              
44 4     5   51 my $esub = eval $_PTSUB;
  1     5   6  
  5     5   133  
  5     5   15  
  6     5   167  
  3     5   10  
  3     5   129  
  12     5   17  
  5     5   130  
  1     5   3  
  3     5   143  
  0     5   0  
  5     5   73  
  2     5   7  
  3     8   100  
  3     2   8  
  5     2   134  
  1     2   3  
  3     2   84  
  0     2   0  
  5     2   105  
  1     2   12  
  4     2   159  
  2     2   8  
  5     2   81  
  2     2   5  
  5     2   151  
  6     2   15  
  4     2   163  
  1     2   2  
  3     2   172  
  2     2   4  
  5     2   182  
  1     2   2  
  3     2   136  
  3     2   7  
  5     2   170  
  3     2   49  
  4     2   148  
  19     2   37  
  5     2   81  
  4     2   9  
  3     2   151  
  5     2   82  
  3     2   60  
  1     2   61  
  5     2   158  
  11     2   20  
  4     2   139  
  43     2   60  
  6     2   230  
  1     2   3  
  3     2   116  
  6     2   7  
  62     2   6920  
  0     2   0  
  1     2   50  
  0     1   0  
  1     1   3  
  0     1   0  
  1     1   37  
  4     1   6  
  1     1   30  
  1     1   3  
  1     1   44  
  0     1   0  
  1     1   36  
  0     1   0  
  1     1   27  
  0     1   0  
  1     1   45  
  0     1   0  
  1     1   5  
  0     1   0  
  1     1   33  
  2     1   5  
  1     1   30  
  1     1   3  
  1     1   30  
  0     1   0  
  1     1   25  
  0     1   0  
  1     1   29  
  0     1   0  
  1     1   43  
  0     1   0  
  1     1   3  
  0     1   0  
  1     1   35  
  0     1   0  
  1     1   33  
  0     1   0  
  1     1   27  
  0     1   0  
  1     1   31  
  0     1   0  
  1     1   27  
  0     1   0  
  1     1   51  
  0     1   0  
  1     1   4  
  1     1   16  
  1     1   35  
  11     1   19  
  1     1   34  
  0     1   0  
  1     1   30  
  0     1   0  
  1     1   28  
  0     1   0  
  1     1   26  
  0     1   0  
  1     1   64  
  0     1   0  
  1     1   3  
  1     1   4  
  1     1   35  
  11     1   22  
  1     1   29  
  0     1   0  
  1     1   27  
  0     1   0  
  1     1   27  
  0     1   0  
  1     1   25  
  0     1   0  
  1     1   64  
  0     1   0  
  1     1   5  
  0     1   0  
  1     1   37  
  0     1   0  
  1     1   31  
  0     1   0  
  1     1   28  
  0     1   0  
  1     1   33  
  0     1   0  
  1     1   36  
  0     1   0  
  1     1   79  
  0         0  
  1         4  
  1         3  
  1         37  
  11         16  
  1         30  
  0         0  
  1         27  
  0         0  
  1         28  
  0         0  
  1         28  
  0         0  
  1         64  
  0         0  
  1         4  
  1         2  
  1         37  
  4         9  
  1         31  
  0         0  
  1         28  
  0         0  
  1         30  
  0         0  
  1         33  
  1         4  
  1         48  
  0         0  
  1         4  
  1         4  
  1         31  
  11         22  
  1         26  
  0         0  
  1         27  
  0         0  
  1         27  
  0         0  
  1         24  
  0         0  
  1         85  
  0         0  
  1         4  
  0         0  
  1         38  
  0         0  
  1         31  
  0         0  
  1         32  
  0         0  
  1         30  
  0         0  
  1         27  
  0         0  
  1         78  
  0         0  
  1         4  
  1         4  
  1         39  
  2         4  
  1         35  
  0         0  
  1         30  
  1         5  
  1         37  
  0         0  
  1         29  
  0         0  
  1         36  
  0            
45 60 100       162 if (my $e = $@) {
46 9         162 my $do_croak;
47             my $template;
48 22 100       79 if ($_PTPL->template_file) {
49 5         88 $template = $_PTPL->template_file;
50             } else {
51 3         5 $do_croak = 1;
52 5         93 $template = 'inline template';
53             };
54 6         87 $e =~ s{ at .*?line (\d+)(\.\s*|,\s+.*?)?$}
55 8         195 [" at $template line " . ( $1 - $_PTPL->pre_lines )]gsme;
56              
57 8 50       26 if ($1) {
58 7         134 $e =~ s/\s*$/\n/g;
59 7 100       91 die $e unless $do_croak;
60 5         586 croak $e;
61             }
62              
63 0         0 croak "$e at $template";
64             }
65              
66 57         171 $_PTPL->{sql} = '';
67 70         99 $_PTPL->{variables} = [];
68              
69 56         127 $esub->( @{ $_PTPL->stashes } );
  54         1049  
70 55         423 1;
71             }
72              
73             sub render {
74 48     50 0 145 my ($self, $tpl, @args) = @_;
75 50         179 $self->{parsed_template} = '';
76 49         159 $self->template($tpl);
77 50         108 $self->template_file('');
78 48         61 $self->{stashes} = \@args;
79 50         200 $self->clean_namespace;
80 69         134 return $self->_render;
81             }
82              
83             sub render_file {
84 14     14 0 80 my ($self, $file, @args) = @_;
85 12 50 0     155 croak "File '@{[ $file // 'undef' ]}' not found or readable"
  2         58  
86             unless -r $file;
87 12         535 open my $fh, '<:raw', $file;
88 14         90 my $data;
89              
90 12         14 { local $/; $data = <$fh> }
  14         155  
  13         210  
91              
92 14 50       151 utf8::decode $data if $self->sql_utf8;
93              
94 13         25 $self->{parsed_template} = '';
95 15         114 $self->template_file($file);
96 14         29 $self->template($data);
97 13         74 $self->{stashes} = \@args;
98 16         38 $self->clean_namespace;
99 14         89 return $self->_render;
100             }
101              
102             sub clean_prepends {
103 48     50 0 54 my ($self) = @_;
104 50         123 $self->{pretokens} = [];
105 48         133 $self;
106             }
107              
108             sub clean_preprepends {
109 50     50 0 113 my ($self) = @_;
110 48         73 $self->{prepretokens} = [];
111 50         139 $self;
112             }
113              
114              
115             sub immediate {
116 269     270 0 249 my ($self, $str) = @_;
117 271 100       553 if (Scalar::Util::blessed $str) {
118 7 50       22 if ('DBIx::DR::ByteStream' eq Scalar::Util::blessed $str) {
    0          
119 8         48 $self->{sql} .= $str->content;
120             } elsif ($str->can('content')) {
121 0         0 $self->{sql} .= $str->content;
122             } else {
123 1         62 croak "Can't extract content from " . Scalar::Util::blessed $str;
124             }
125             } else {
126 265         343 $self->{sql} .= $str;
127             }
128 270         539 return DBIx::DR::ByteStream->new('');
129             }
130              
131             sub add_bind_value {
132 63     65 0 91 my ($self, @values) = @_;
133 65         147 push @{ $self->variables } => @values;
  63         182  
134             }
135              
136              
137             sub quote {
138 49     49 0 104 my ($self, $variable) = @_;
139              
140 48 100       132 if (Scalar::Util::blessed $variable) {
141 8 50       132 return $self->immediate($variable)
142             if 'DBIx::DR::ByteStream' eq Scalar::Util::blessed $variable;
143             }
144              
145 44         64 $self->{sql} .= '?';
146 44         140 $self->add_bind_value($variable);
147 43         84 return DBIx::DR::ByteStream->new('');
148             }
149              
150             sub _parse {
151 62     62   88 my ($self) = @_;
152              
153 61         122 my $result = '';
154              
155 61         143 my $immediate_mark = $self->immediate_mark;
156 63         100 my $quote_mark = $self->quote_mark;
157              
158             my $code_cb = sub {
159 73     74   77 my ($t) = @_;
160 74 50 33     320 return unless defined $t and length $t;
161              
162 72 100       209 if ($t =~ /^\Q$immediate_mark\E/) {
163 14         91 $result .= join '',
164             'immediate(',
165             substr($t, length($immediate_mark)),
166             ');';
167 13         19 return;
168             }
169              
170 62 100       233 if ($t =~ /^\Q$quote_mark\E/) {
171 37         110 $result .= join '',
172             'quote(',
173             substr($t, length($quote_mark)),
174             ');';
175 39         166 return;
176             }
177              
178 26         58 $result .= "$t;"; # always place ';' at end of code.
179 62         282 };
180              
181             my $text_cb = sub {
182 314     315   317 my ($content) = @_;
183 314 100 66     949 return unless defined $content and length $content;
184 250         238 $content =~ s/'/\\'/g;
185 249         402 $result .= "immediate('" . $content . "');";
186 62         168 };
187              
188 60         153 $self->_parse_ep($self->template, $text_cb, $code_cb);
189              
190 62         228 $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         418 return $result;
200             }
201              
202             sub _parse_ep {
203              
204 62     62   154 my ($self, $tpl, $text_cb, $code_cb) = @_;
205              
206             #---------------------------------------------------------
207             # единственные три переменные из self
208 61         111 my $line_tag = $self->line_tag;
209 62         156 my $open_tag = $self->open_tag;
210 60         91 my $close_tag = $self->close_tag;
211             # по идее это можно было оформить в виде независимого кода
212             #---------------------------------------------------------
213              
214 62         223 my @lines = split /\n/, $tpl;
215              
216 60         55 my $st = 'text';
217 63         116 my $code_text;
218              
219 60         146 for (my $i = 0; $i < @lines; $i++) {
220 182         288 local $_ = $lines[$i];
221              
222             CODE:
223 226 100       315 if ($st eq 'code') {
224 48 100       289 if (/^(.*?)\Q$close_tag\E(.*)/) {
225 43         58 $_ = $2;
226 44         164 $code_cb->($code_text . $1);
227 42         39 $code_text = undef;
228 44         114 $st = 'text';
229 42         139 goto ANYTEXT;
230             } else {
231 6         67 $code_text .= $_;
232 4         3 $code_text .= "\n";
233 6         148 next;
234             }
235             }
236              
237             TEXT_BEGIN:
238 177 100       741 if (/^(\s*)\Q$line_tag\E(.*)/) {
239 31         47 $text_cb->($1);
240 30 100       43 if ($i < $#lines) {
241 24         98 $code_cb->("$2\n");
242             } else {
243 10         19 $code_cb->($2);
244             }
245 31         97 next;
246             }
247              
248             ANYTEXT:
249 190 100       499 if (/^(.*?)\Q$open_tag\E(.*)/) {
250 43         125 $_ = $2;
251 42         65 $text_cb->($1);
252 43         123 $code_text = '';
253 42         57 $st = 'code';
254 43         203 goto CODE;
255             } else {
256 147         161 $text_cb->($_);
257 148 100       300 $text_cb->("\n") if $i < $#lines;
258 147         305 next;
259             }
260             }
261 61 50 33     174 $text_cb->("<%" . $code_text) if defined $code_text and length $code_text;
262             }
263              
264              
265             sub preprepend {
266 294     296 0 288 my ($self, @tokens) = @_;
267 295   100     497 $self->{prepretokens} ||= [];
268 297 100       388 push @{ $self->prepretokens } => map "$_;\n", @tokens if @tokens;
  229         599  
269 295 100       611 return join '' => @{ $self->prepretokens } if defined wantarray;
  67         295  
270             }
271              
272             sub prepend {
273 98     100 0 110 my ($self, @tokens) = @_;
274 99   50     217 $self->{pretokens} ||= [];
275 98 100       139 push @{ $self->pretokens } => map "$_;", @tokens if @tokens;
  33         135  
276 98 100       171 return join '' => @{ $self->pretokens } if defined wantarray;
  67         329  
277             }
278              
279              
280             sub pre_lines {
281 6     8 0 11 my ($self) = @_;
282 7         13 my $lines = 0;
283 7         16 $lines += @{[ /\n/g ]} for ($self->preprepend, $self->prepend);
  13         78  
284 9         37 return $lines;
285             }
286              
287             sub clean_prepend {
288 13     14 0 10880 my ($self) = shift;
289 13         29 $self->{pretokens} = [];
290             }
291              
292             sub clean_namespace {
293 61     62 0 105 my ($self) = @_;
294 60         104 my $sb = $self->namespace;
295              
296 3     5   15 no strict 'refs';
  5         55  
  3         213  
297 61         85 undef *{$sb . '::' . $_} for keys %{ $sb . '::' };
  60         447  
  178         743  
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