File: Base.pm

package info (click to toggle)
libsql-translator-perl 0.11011-1
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 15,380 kB
  • sloc: perl: 251,748; sql: 3,805; xml: 233; makefile: 7
file content (314 lines) | stat: -rw-r--r-- 8,552 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
package SQL::Translator::Producer::TT::Base;

=pod

=head1 NAME

SQL::Translator::Producer::TT::Base - TT (Template Toolkit) based Producer base
class.

=cut

use strict;
use warnings;

our @EXPORT_OK;
our $VERSION = '1.59';

use Template;
use Data::Dumper;
use IO::Handle;
use Exporter;
use base qw(Exporter);
@EXPORT_OK = qw(produce);

use SQL::Translator::Utils 'debug';

# Hack to convert the produce call into an object. ALL sub-classes need todo
# this so that the correct class gets created.
sub produce {
    return __PACKAGE__->new( translator => shift )->run;
};

sub new {
    my $proto = shift;
    my $class = ref $proto || $proto;
    my %args  = @_;

    my $me = bless {}, $class;
    $me->{translator} = delete $args{translator} || die "Need a translator.";

    return $me;
}

sub translator { shift->{translator}; }
sub schema     { shift->{translator}->schema(@_); }

# Util args access method.
# No args - Return hashref (the actual hash in Translator) or hash of args.
# 1 arg   - Return that named args value.
# Args    - List of names. Return values of the given arg names in list context
#           or return as hashref in scalar context. Any names given that don't
#           exist in the args are returned as undef.
sub args {
    my $me = shift;

    # No args
    unless (@_) {
        return wantarray
            ? %{ $me->{translator}->producer_args }
            : $me->{translator}->producer_args
        ;
    }

    # 1 arg. Return the value whatever the context.
    return $me->{translator}->producer_args->{$_[0]} if @_ == 1;

    # More args so return values list or hash ref
    my %args = %{ $me->{translator}->producer_args };
    return wantarray ? @args{@_} : { map { ($_=>$args{$_}) } @_ };
}

# Run the produce and return the result.
sub run {
    my $me = shift;
    my $scma = $me->schema;
    my %args = %{$me->args};
    my $tmpl = $me->tt_schema or die "No template!";

    debug "Processing template $tmpl\n";
    my $out;
    my $tt = Template->new(
        #DEBUG    => $me->translator->debug,
        ABSOLUTE => 1,  # Set so we can use from the command line sensibly
        RELATIVE => 1,  # Maybe the cmd line code should set it! Security!
        $me->tt_config, # Hook for sub-classes to add config
        %args,          # Allow any TT opts to be passed in the producer_args
    ) || die "Failed to initialize Template object: ".Template->error;

    $tt->process( $tmpl, {
        $me->tt_default_vars,
        $me->tt_vars,          # Sub-class hook for adding vars
    }, \$out )
    or die "Error processing template '$tmpl': ".$tt->error;

    return $out;
}


# Sub class hooks
#-----------------------------------------------------------------------------

sub tt_config { () };

sub tt_schema {
    my $me = shift;
    my $class = ref $me;

    my $file = $me->args("ttfile");
    return $file if $file;

    no strict 'refs';
    my $ref = *{"$class\:\:DATA"}{IO};
    if ( $ref->opened ) {
        local $/ = undef; # Slurp mode
        return \<$ref>;
    }

    undef;
};

sub tt_default_vars {
    my $me = shift;
    return (
        translator => $me->translator,
        schema     => $me->pre_process_schema($me->translator->schema),
    );
}

sub pre_process_schema { $_[1] }

sub tt_vars   { () };

1;

=pod

=head1 SYNOPSIS

 # Create a producer using a template in the __DATA__ section.
 package SQL::Translator::Producer::Foo;

 use base qw/SQL::Translator::Producer::TT::Base/;

 # Convert produce call into a method call on our new class
 sub produce { return __PACKAGE__->new( translator => shift )->run; };

 # Configure the Template object.
 sub tt_config { ( INTERPOLATE => 1 ); }

 # Extra vars to add to the template
 sub tt_vars { ( foo => "bar" ); }

 # Put template in DATA section (or use file with ttfile producer arg)
 __DATA__
 Schema

 Database: [% schema.database %]
 Foo: $foo
 ...

=head1 DESCRIPTION

A base class producer designed to be sub-classed to create new TT based
producers cheaply - by simply giving the template to use and sprinkling in some
extra template variables and config.

You can find an introduction to this module in L<SQL::Translator::Manual>.

The 1st thing the module does is convert the produce sub routine call we get
from SQL::Translator into a method call on an object, which we can then
sub-class. This is done with the following code which needs to appear in B<all>
sub classes.

 # Convert produce call into an object method call
 sub produce { return __PACKAGE__->new( translator => shift )->run; };

See L<PRODUCER OBJECT> below for details.

The upshot of this is we can make new template producers by sub classing this
base class, adding the above snippet and a template.
The module also provides a number of hooks into the templating process,
see L<SUB CLASS HOOKS> for details.

See the L<SYNOPSIS> above for an example of creating a simple producer using
a single template stored in the producers DATA section.

=head1 SUB CLASS HOOKS

Sub-classes can override these methods to control the templating by giving
the template source, adding variables and giving config to the Tempate object.

=head2 tt_config

 sub tt_config { ( INTERPOLATE => 1 ); }

Return hash of Template config to add to that given to the L<Template> C<new>
method.

=head2 tt_schema

 sub tt_schema { "foo.tt"; }
 sub tt_schema { local $/ = undef; \<DATA>; }

The template to use, return a file name or a scalar ref of TT
source, or an L<IO::Handle>. See L<Template> for details, as the return from
this is passed on to it's C<produce> method.

The default implimentation uses the producer arg C<ttfile> as a filename to read
the template from. If the arg isn't there it will look for a C<__DATA__> section
in the class, reading it as template source if found. Returns undef if both
these fail, causing the produce call to fail with a 'no template!' error.

=head2 tt_vars

 sub tt_vars { ( foo => "bar" ); }

Return hash of template vars to use in the template. Nothing added here
by default, but see L<tt_default_vars> for the variables you get for free.

=head2 tt_default_vars

Return a hash-ref of the default vars given to the template.
You wouldn't normally over-ride this, just inherit the default implimentation,
to get the C<translator> & C<schema> variables, then over-ride L<tt_vars> to add
your own.

The current default variables are:

=over 4

=item schema

The schema to template.

=item translator

The L<SQL::Translator> object.

=back

=head2 pre_process_schema

WARNING: This method is Experimental so may change!

Called with the L<SQL::Translator::Schema> object and should return one (it
doesn't have to be the same one) that will become the C<schema> varibale used
in the template.

Gets called from tt_default_vars.

=head1 PRODUCER OBJECT

The rest of the methods in the class set up a sub-classable producer object.
You normally just inherit them.

=head2 new

 my $tt_producer = TT::Base->new( translator => $translator );

Construct a new TT Producer object. Takes a single, named arg of the
L<SQL::Translator> object running the translation. Dies if this is not given.

=head2 translator

Return the L<SQL::Translator> object.

=head2 schema

Return the L<SQL::Translator::Schema> we are translating. This is equivilent
to C<< $tt_producer->translator->schema >>.

=head2 run

Called to actually produce the output, calling the sub class hooks. Returns the
produced text.

=head2 args

Util wrapper method around C<< TT::Base->translator->producer_args >> for
(mostley) readonly access to the producer args. How it works depends on the
number of arguments you give it and the context.

 No args - Return hashref (the actual hash in Translator) or hash of args.
 1 arg   - Return value of the arg with the passed name.
 2+ args - List of names. In list context returns values of the given arg
           names, returns as a hashref in scalar context. Any names given
           that don't exist in the args are returned as undef.

This is still a bit messy but is a handy way to access the producer args when
you use your own to drive the templating.

=head1 SEE ALSO

L<perl>,
L<SQL::Translator>,
L<Template>.

=head1 TODO

- Add support for a sqlf template repository, set as an INCLUDE_PATH,
so that sub-classes can easily include file based templates using relative
paths.

- Pass in template vars from the producer args and command line.

- Merge in TT::Table.

- Hooks to pre-process the schema and post-process the output.

=head1 AUTHOR

Mark Addison E<lt>grommit@users.sourceforge.netE<gt>.

=cut