File Coverage

blib/lib/DBIx/POS/Template.pm
Criterion Covered Total %
statement 122 143 85.3
branch 43 74 58.1
condition 15 34 44.1
subroutine 24 28 85.7
pod 3 8 37.5
total 207 287 72.1


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