File Coverage

blib/lib/DBIx/POS/Template.pm
Criterion Covered Total %
statement 117 132 88.6
branch 40 66 60.6
condition 14 31 45.1
subroutine 23 26 88.4
pod 3 7 42.8
total 197 262 75.1


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