File Coverage

blib/lib/DBIx/POS/Template.pm
Criterion Covered Total %
statement 116 131 88.5
branch 40 66 60.6
condition 13 28 46.4
subroutine 23 26 88.4
pod 3 7 42.8
total 195 258 75.5


line stmt bran cond sub pod time code
1             package DBIx::POS::Template;
2 2     2   28289 use strict;
  2         3  
  2         46  
3 2     2   7 use warnings;
  2         2  
  2         45  
4 2     2   6 use base qw{Pod::Parser};
  2         5  
  2         167  
5 2     2   817 use Hash::Merge qw(merge);
  2         3427  
  2         1307  
6              
7             our $VERSION = '0.021';
8              
9             # Hold data for our pending statement
10             my $info = {};
11              
12             # SQL instance
13             my %sql;
14              
15             # What command we're looking at
16             my $cmd;
17              
18             my $enc; # PODs enc
19              
20             # Text::Template->new(%TT, %tt)
21             our %TT = (
22             DELIMITERS => ['{%', '%}'],
23             #~ BROKEN => sub { die @_;},
24             );
25              
26             my %tt = (); # instance scope: Text::Template->new(..., tt=>{%TT, %tt}, )
27             my $tt = {}; # new scope: Text::Template->new(..., tt=>{%TT, %$tt}, )
28             my %template = (); # instance scope: Text::Template->new(..., template=>\%template, )
29             my $template = {}; # new scope: Text::Template->new(..., template=>$template, )
30             my $scope; # 'new' | 'instance'
31              
32             # separate object
33             sub new {
34 1     1 1 684 my ($class, $file, %arg) = @_;
35 1         1 $scope = 'new';
36 1         3 my %back = %sql;
37            
38 1   50     17 $tt = $arg{TT} || $arg{tt} || {};
39 1   50     3 $template = $arg{template} || {};
40 1         3 %sql = ();
41            
42 1         2 $class->_process( $file,);
43 1         5 my $new = { %sql };
44            
45 1         4 %sql = %back;
46              
47 1         4 bless $new, $class;
48             }
49              
50             # Taken directly from Class::Singleton
51             #~ sub instance000 {
52             #~ my $class = shift;
53             #~ $scope = 'instance';
54             #~ # get a reference to the _instance variable in the $class package
55             #~ no strict 'refs';
56             #~ my $instance = \${ "$class\::_instance" };
57              
58             #~ defined $$instance
59             #~ ? $$instance
60             #~ : ($$instance = $class->_instance(@_));
61             #~ }
62              
63             my $instance;
64             sub instance {
65 2     2 1 401 my ($class, $file, %arg) = @_;
66 2         4 $scope = 'instance';
67             # merge prev tt opts
68 2   33     8 my $tt = $arg{TT} || $arg{tt};
69 2 50       9 @tt{ keys %$tt } = values %$tt
70             if $tt;
71             #~ @template{ keys %{$arg{template}} } = values %{$arg{template}} хэш сложный!
72            
73 2         7 %template = %{merge($arg{template}, \%template)}
74 2 50 33     7 if $arg{template} && %{$arg{template}};
  2         12  
75            
76 2         2523 $class->_process( $file,);
77 2   100     25 $instance ||= bless \%sql, $class;
78             }
79              
80             sub _process {# pos file
81 3     3   4 my ($class, $file,) = @_;
82 3 50       6 return unless $file;
83 3 50       9 $file .='.pm'
84             if $file =~ s/::/\//g;
85             #~ warn "Processing file [$file]";
86 3         4 $enc = undef;
87 3         679 $class->SUPER::new->parse_from_file($file);
88             }
89              
90             sub template {
91 3     3 1 981 my ($self, $key, %arg) = @_;
92             die "No such item by key [$key] on this POS, please check processed file(s)"
93 3 50       9 unless $self->{$key};
94 3         789 $self->{$key}->template(%arg);
95             }
96              
97             ########### Parser ################
98              
99             # Handle =whatever commands
100             sub command {
101 23     23 0 30 my ($self, $command, $paragraph, $line) = @_;
102              
103             # Get rid of all trailing whitespace
104 23         47 $paragraph =~ s/\s+$//ms;
105              
106             # There may be a short description right after the command
107 23 100       38 if ($command eq 'desc') {
108 3   50     8 $info->{desc} = $paragraph || "";
109             }
110              
111             # The name comes right after the command
112 23 100       27 if ($command eq 'name') {
113 5         7 $self->end_input;
114 5         9 $info->{name} = $paragraph;
115             }
116              
117             # The noreturn comes right after the command
118 23 50       26 if ($command eq 'noreturn') {
119 0         0 $info->{noreturn} = 1;
120             }
121            
122 23 100       25 if ($command eq 'encoding') {
123 3         4 $enc = $paragraph;
124             }
125              
126             # Remember what command we're in
127 23         625 $cmd = $command;
128             }
129              
130             sub end_input {
131 8     8 0 8 my ($self) = @_;
132             # If there's stuff to try and construct from
133 8 100       6 if (%{$info}) {
  8         27  
134             # If we have the necessary bits
135             #~ if (scalar (grep {m/^(?:name|short|desc|sql)$/} keys %{$info}) == 3) {
136 5 50 33     22 if (defined($info->{name}) && defined($info->{sql})) {
137             # Grab the entire content for the %sql hash
138 5 100       30 $sql{$info->{name}} = DBIx::POS::Statement->new (
    100          
139             $info,
140             tt => {%TT, $scope eq 'new' ? %$tt : %tt},
141             template => $scope eq 'new' ? $template : \%template,
142             enc=>$enc,
143             );
144             # Start with a new empty hashref
145 5         69 $info = {};
146             } else {# Something's missing
147 0         0 warn "Malformed entry: ", %$info;# . Dump (\%sql, $info);
148             }
149             }
150             }
151              
152              
153             # Handle the blocks of text between commands
154             sub textblock {
155 1     1 0 2 my ($parser, $paragraph, $line) = @_;
156              
157             # Collapse trailing whitespace to a \n
158 1         4 $paragraph =~ s/\s+$/\n/ms;
159              
160 1 50       5 if ($cmd eq 'desc') {
    50          
    0          
161 0         0 $info->{desc} .= $paragraph;
162             }
163              
164             elsif ($cmd eq 'param') {
165 1         30 $info->{param} .= $paragraph;
166             }
167              
168             elsif ($cmd eq 'sql') {
169 0         0 $info->{sql} .= $paragraph;
170             }
171             }
172              
173             # We handle verbatim sections the same way
174             sub verbatim {
175 6     6 0 6 my ($parser, $paragraph, $line) = @_;
176              
177             # Collapse trailing whitespace to a \n
178 6         25 $paragraph =~ s/\s+$/\n/ms;
179              
180 6 50       19 if ($cmd eq 'desc') {
    50          
    50          
181 0         0 $info->{desc} .= $paragraph;
182             }
183              
184             elsif ($cmd eq 'param') {
185 0         0 $info->{param} .= $paragraph;
186             }
187              
188             elsif ($cmd eq 'sql') {
189 6         170 $info->{sql} .= $paragraph;
190             }
191             }
192              
193             1;
194             #=============================================
195             package DBIx::POS::Statement;
196             #=============================================
197 2     2   1170 use Text::Template;
  2         4555  
  2         87  
198 2     2   10 use Hash::Merge qw(merge);
  2         3  
  2         86  
199 2     2   923 use Encode;
  2         14246  
  2         147  
200              
201 2     2   1739 use overload '""' => sub { shift->template };
  2     13   1753  
  2         15  
  13         4835  
202              
203             sub new {
204 5     5   6 my $proto = shift;
205 5   33     21 my $class = ref $proto || $proto;
206 5         5 my $self = shift;
207 5         11 my %arg = @_;
208 5   33     14 $self->{_TT} = $arg{TT} || $arg{tt} ;
209 5         5 $self->{_template_default} = $arg{template};
210 5         6 $self->{_enc} = $arg{enc};
211 5         5 bless ($self, $class);
212 5 50       30 if (my $enc = $self->{_enc}) {
213 5         10 my @enc = qw(name desc param short sql);
214 5         14 @$self{ @enc } = map Encode::decode($enc, $self->{$_}), @enc;
215             }
216 5         309 $self->_eval_param();
217 5         13 return $self;
218             }
219              
220             sub desc {
221 0     0   0 my $self = shift;
222 0 0       0 $self->{desc} = shift if (@_);
223 0         0 return $self->{desc};
224             }
225              
226             sub name {
227 1     1   1121 my $self = shift;
228 1 50       3 $self->{name} = shift if (@_);
229 1         4 return $self->{name};
230             }
231              
232             sub noreturn {
233 0     0   0 my $self = shift;
234 0 0       0 $self->{noreturn} = shift if (@_);
235 0         0 return $self->{noreturn};
236             }
237              
238             sub param {# ->param() | ->param('foo') | ->param('foo'=>'bar', ....)
239 7     7   1370 my $self = shift;
240 7 100       19 return unless defined $self->{param};
241             #~ $self->{param} ||= {};
242 6 50       11 return $self->{param} unless ref $self->{param} eq 'HASH';
243 6 100       14 return $self->{param} unless @_;
244 5 100       22 return $self->{param}{ shift() } if @_ == 1;
245 1         4 my %arg = @_;
246 1   50     3 @{$self->{param} ||= {}}{ keys %arg } = values %arg;
  1         5  
247             }
248              
249             sub _eval_param {
250 5     5   6 my $self = shift;
251 5 100       11 return unless $self->{param};
252 1         50 my $param = eval $self->{param};
253 1 50       5 die "Malformed perl code param [$self->{param}]: $@" if $@;
254 1         2 $self->{param} = $param;
255             }
256              
257              
258             sub short {
259 0     0   0 my $self = shift;
260 0 0       0 $self->{short} = shift if (@_);
261 0         0 return $self->{short};
262             }
263              
264             sub sql {
265 6     6   641 my $self = shift;
266 6 50       11 $self->{sql} = shift if (@_);
267 6         13 return $self->{sql};
268             }
269              
270             sub template {
271 23     23   2173 my ($self, %arg) = @_;
272             #~ return $self->{sql}
273             #~ unless scalar(%arg) || scalar(%{$self->{_template_default}});
274             $self->{_template} ||= Text::Template->new(
275             TYPE => 'STRING',
276             SOURCE => $self->sql,
277 23   66     56 %{$self->{_TT}},
  5         28  
278             );
279             #~ $self->{_template}->fill_in(HASH=>{%{$self->{_template_default}}, %arg},);#BROKEN_ARG=>\'error!', BROKEN => sub { die @_;},
280             return $self->{_template}->fill_in(HASH=>$self->{_template_default})
281 23 100       414 unless %arg;
282             return $self->{_template}->fill_in(HASH=>\%arg)
283 7 50       3 unless %{$self->{_template_default}};
  7         14  
284 7         21 $self->{_template}->fill_in(HASH=>merge(\%arg, $self->{_template_default}));
285             }
286              
287              
288             =pod
289              
290             =encoding utf8
291              
292             =head1 DBIx::POS::Template
293              
294             Доброго всем
295              
296             ¡ ¡ ¡ ALL GLORY TO GLORIA ! ! !
297              
298             =head1 VERSION
299              
300             0.021
301              
302             =head1 NAME
303              
304             DBIx::POS::Template - is a fork of L. Define a dictionary of SQL statements in a POD dialect (POS) plus expand template sql with embedded Perl using L.
305              
306             =head1 SYNOPSIS
307              
308             use DBIx::POS::Template;
309              
310             # separate object
311             my $pos = DBIx::POS::Template->new(__FILE__, ...);
312             # or singleton DBIx::POS::Template->instance($file, ...);
313            
314             my $sql = $pos->{test1}->template(where => "bar = ?");
315             # or $pos->template('test1', where => "bar = ?")
316            
317             =pod
318              
319             =name test1
320              
321             =desc test the DBIx::POS::Template module
322              
323             =param
324            
325             # Some arbitrary parameters as perl code (eval)
326             {
327             cache=>1, # will be prepare_cached
328             }
329              
330             =sql
331              
332             select * from foo
333             {% $where %}
334             ;
335              
336             =cut
337              
338             Or:
339              
340             package POS::Foo;
341             use DBIx::POS::Template;
342             use Hash::Merge qw(merge);
343            
344             my $default = {foo=>'bar',};
345            
346             sub new {
347             my $proto = shift;
348             DBIx::POS::Template->new(__FILE__, template => merge({@_}, $default));
349             # or DBIx::POS::Template->instance(...
350             }
351              
352             =head1 DESCRIPTION
353              
354             DBIx::POS::Template is subclass Pod::Parser to define a POD dialect for writing a SQL dictionary(s) with templating.
355              
356             By separating the SQL code from its normal context of execution, it
357             encourages you to do other things with it---for instance, it is easy
358             to create a script that can do performance testing of certain SQL
359             statements in isolation, or to create generic command-line wrapper
360             around your SQL statements.
361              
362             By giving a framework for documenting the SQL, it encourages
363             documentation of the intent and/or implementation of the SQL code. It
364             also provides all of that information in a format from which other
365             documentation could be generated---say, a chunk of DocBook for
366             incorporation into a guide to programming the application.
367              
368             This class whould work as separate objects per pod-file or as singleton for all processed files with one dictionary of them.
369              
370             =head1 METHODS
371              
372             =head2 new($file, )
373              
374             Create separate object and process $file POS with options names:
375              
376             =over 4
377              
378             =item * TT | tt
379              
380             Optional hashref will passing to L->new() for each parsed statement. By default only defined the key:
381              
382             ..., TT => {DELIMITERS => ['{%', '%}'],},
383              
384             For B this hashref will be merged with previous instance invokes.
385              
386             =item * template
387              
388             ..., template => {foo=>1,},
389              
390             Optional hashref of default values for each statement template. For B this hashref will be merged with previous instance invokes.
391              
392             =back
393              
394             =head2 instance($file, )
395              
396             Return singleton dictionary object, parsed $file keys will collapse/override with previous instances files. Same options as C. B and B