fd08eb2ed93a2d9be1e2788084a0c58df7ab4516
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Producer / TT / Table.pm
1 package SQL::Translator::Producer::TT::Table;
2
3 # -------------------------------------------------------------------
4 # Copyright (C) 2002-2009 SQLFairy Authors
5 #
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License as
8 # published by the Free Software Foundation; version 2.
9 #
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 # General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
18 # 02111-1307  USA
19 # -------------------------------------------------------------------
20
21 =pod
22
23 =head1 NAME
24
25 SQL::Translator::Producer::TT::Table -
26     Produces output using the Template Toolkit from a SQL schema, per table.
27
28 =head1 SYNOPSIS
29
30   # Normal STDOUT version
31   #
32   my $translator     = SQL::Translator->new(
33       from           => 'MySQL',
34       filename       => 'foo_schema.sql',
35       to             => 'TT::Table',
36       producer_args  => {
37           tt_table     => 'foo_table.tt',
38       },
39   );
40   print $translator->translate;
41
42   # To generate a file per table
43   #
44   my $translator     = SQL::Translator->new(
45       from           => 'MySQL',
46       filename       => 'foo_schema.sql',
47       to             => 'TT::Table',
48       producer_args  => {
49           tt_table       => 'foo_table.tt.html',
50           mk_files      => 1,
51           mk_files_base => "./doc/tables",
52           mk_file_ext   => ".html",
53           on_exists     => "replace",
54       },
55   );
56   #
57   # ./doc/tables/ now contains the templated tables as $tablename.html
58   #
59
60 =head1 DESCRIPTION
61
62 Produces schema output using a given Template Tookit template,
63 processing that template for each table in the schema. Optionally
64 allows you to write the result for each table to a separate file.
65
66 It needs one additional producer_arg of C<tt_table> which is the file
67 name of the template to use.  This template will be passed a template
68 var of C<table>, which is the current
69 L<SQL::Translator::Producer::Table> table we are producing, which you
70 can then use to walk the schema via the methods documented in that
71 module. You also get L<schema> as a shortcut to the
72 L<SQL::Translator::Producer::Schema> for the table and C<translator>,
73 the L<SQL::Translator> object for this parse in case you want to get
74 access to any of the options etc set here.
75
76 Here's a brief example of what the template could look like:
77
78   [% table.name %]
79   ================
80   [% FOREACH field = table.get_fields %]
81       [% field.name %]   [% field.data_type %]([% field.size %])
82   [% END -%]
83
84 See F<t/data/template/table.tt> for a more complete example.
85
86 You can also set any of the options used to initiallize the Template
87 object by adding them to your producer_args. See Template Toolkit docs
88 for details of the options.
89
90   $translator          = SQL::Translator->new(
91       to               => 'TT',
92       producer_args    => {
93           ttfile       => 'foo_template.tt',
94           INCLUDE_PATH => '/foo/templates/tt',
95           INTERPOLATE  => 1,
96       },
97   );
98
99 If you set C<mk_files> and its additional options the producer will
100 write a separate file for each table in the schema. This is useful for
101 producing things like HTML documentation where every table gets its
102 own page (you could also use TTSchema producer to add an index page).
103 Its also particulary good for code generation where you want to
104 produce a class file per table.
105
106 =head1 OPTIONS
107
108 =over 4
109
110 =item tt_table
111
112 File name of the template to run for each table.
113
114 =item mk_files
115
116 Set to true to output a file for each table in the schema (as well as
117 returning the whole lot back to the Translalor and hence STDOUT). The
118 file will be named after the table, with the optional C<mk_files_ext>
119 added and placed in the directory C<mk_files_base>.
120
121 =item mk_files_ext
122
123 Extension (without the dot) to add to the filename when using mk_files.
124
125 =item mk_files_base = DIR
126
127 Dir to build the table files into when using mk_files. Defaults to the
128 current directory.
129
130 =item mk_file_dir
131
132 Set true and if the file needs to written to a directory that doesn't
133 exist, it will be created first.
134
135 =item on_exists [Default:replace]
136
137 What to do if we are running with mk_files and a file already exists
138 where we want to write our output. One of "skip", "die", "replace",
139 "insert".  The default is die.
140
141 B<replace> - Over-write the existing file with the new one, clobbering
142 anything already there.
143
144 B<skip> - Leave the origional file as it was and don't write the new
145 version anywhere.
146
147 B<die> - Die with an existing file error.
148
149 B<insert> - Insert the generated output into the file bewteen a set of
150 special comments (defined by the following options.) Any code between
151 the comments will be overwritten (ie the results from a previous
152 produce) but the rest of the file is left alone (your custom code).
153 This is particularly useful for code generation as it allows you to
154 generate schema derived code and then add your own custom code
155 to the file.  Then when the schema changes you just re-produce to
156 insert the new code.
157
158 =item insert_comment_start
159
160 The comment to look for in the file when on_exists is C<insert>. Default
161 is C<SQLF INSERT START>. Must appear on it own line, with only
162 whitespace either side, to be recognised.
163
164 =item insert_comment_end
165
166 The end comment to look for in the file when on_exists is C<insert>.
167 Default is C<SQLF INSERT END>. Must appear on it own line, with only
168 whitespace either side, to be recognised.
169
170 =back
171
172 =cut
173
174 use strict;
175
176 use vars qw[ $DEBUG $VERSION @EXPORT_OK ];
177 $VERSION = '1.59';
178 $DEBUG   = 0 unless defined $DEBUG;
179
180 use File::Path;
181 use Template;
182 use Data::Dumper;
183 use Exporter;
184 use base qw(Exporter);
185 @EXPORT_OK = qw(produce);
186
187 use SQL::Translator::Utils 'debug';
188
189 my $Translator;
190
191 sub produce {
192     $Translator = shift;
193     local $DEBUG   = $Translator->debug;
194     my $scma       = $Translator->schema;
195     my $pargs      = $Translator->producer_args;
196     my $file       = $pargs->{'tt_table'} or die "No template file given!";
197     $pargs->{on_exists} ||= "die";
198
199     debug "Processing template $file\n";
200     my $out;
201     my $tt       = Template->new(
202         DEBUG    => $DEBUG,
203         ABSOLUTE => 1, # Set so we can use from the command line sensibly
204         RELATIVE => 1, # Maybe the cmd line code should set it! Security!
205         %$pargs,        # Allow any TT opts to be passed in the producer_args
206     ) || die "Failed to initialize Template object: ".Template->error;
207
208    for my $tbl ( sort {$a->order <=> $b->order} $scma->get_tables ) {
209       my $outtmp;
210         $tt->process( $file, {
211             translator => $Translator,
212             schema     => $scma,
213             table      => $tbl,
214         }, \$outtmp )
215       or die "Error processing template '$file' for table '".$tbl->name
216              ."': ".$tt->error;
217         $out .= $outtmp;
218
219         # Write out the file...
220       write_file(  table_file($tbl), $outtmp ) if $pargs->{mk_files};
221     }
222
223     return $out;
224 };
225
226 # Work out the filename for a given table.
227 sub table_file {
228     my ($tbl) = shift;
229     my $pargs = $Translator->producer_args;
230     my $root  = $pargs->{mk_files_base};
231     my $ext   = $pargs->{mk_file_ext};
232     return "$root/$tbl.$ext";
233 }
234
235 # Write the src given to the file given, handling the on_exists arg.
236 sub write_file {
237    my ($file, $src) = @_;
238     my $pargs = $Translator->producer_args;
239     my $root = $pargs->{mk_files_base};
240
241     if ( -e $file ) {
242         if ( $pargs->{on_exists} eq "skip" ) {
243             warn "Skipping existing $file\n";
244             return 1;
245         }
246         elsif ( $pargs->{on_exists} eq "die" ) {
247             die "File $file already exists.\n";
248         }
249         elsif ( $pargs->{on_exists} eq "replace" ) {
250             warn "Replacing $file.\n";
251         }
252         elsif ( $pargs->{on_exists} eq "insert" ) {
253             warn "Inserting into $file.\n";
254             $src = insert_code($file, $src);
255         }
256         else {
257             die "Unknown on_exists action: $pargs->{on_exists}\n";
258         }
259     }
260     else {
261         if ( my $interactive = -t STDIN && -t STDOUT ) {
262             warn "Creating $file.\n";
263         }
264     }
265
266     my ($dir) = $file =~ m!^(.*)/!; # Want greedy, eveything before the last /
267    if ( $dir and not -d $dir and $pargs->{mk_file_dir} ) { mkpath($dir); }
268
269     debug "Writing to $file\n";
270    open( FILE, ">$file") or die "Error opening file $file : $!\n";
271    print FILE $src;
272    close(FILE);
273 }
274
275 # Reads file and inserts code between the insert comments and returns the new
276 # source.
277 sub insert_code {
278     my ($file, $src) = @_;
279     my $pargs = $Translator->producer_args;
280     my $cstart = $pargs->{insert_comment_start} || "SQLF_INSERT_START";
281     my $cend   = $pargs->{insert_comment_end}   || "SQLF_INSERT_END";
282
283     # Slurp in the origional file
284     open ( FILE, "<", "$file") or die "Error opening file $file : $!\n";
285     local $/ = undef;
286     my $orig = <FILE>;
287     close(FILE);
288
289     # Insert the new code between the insert comments
290     unless (
291         $orig =~ s/^\s*?$cstart\s*?\n.*?^\s*?$cend\s*?\n/\n$cstart\n$src\n$cend\n/ms
292     ) {
293         warn "No insert done\n";
294     }
295
296     return $orig;
297 }
298
299 1;
300
301 =pod
302
303 =head1 AUTHOR
304
305 Mark Addison E<lt>grommit@users.sourceforge.netE<gt>.
306
307 =head1 TODO
308
309 - Some tests for the various on exists options (they have been tested
310 implicitley through use in a project but need some proper tests).
311
312 - More docs on code generation strategies.
313
314 - Better hooks for filename generation.
315
316 - Integrate with L<TT::Base> and L<TTSchema>.
317
318 =head1 SEE ALSO
319
320 SQL::Translator.
321
322 =cut