Changes + Reverts for 0.11000, see Changes file for info
[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 # -------------------------------------------------------------------
175
176 use strict;
177
178 use vars qw[ $DEBUG $VERSION @EXPORT_OK ];
179 $VERSION = '1.59';
180 $DEBUG   = 0 unless defined $DEBUG;
181
182 use File::Path;
183 use Template;
184 use Data::Dumper;
185 use Exporter;
186 use base qw(Exporter);
187 @EXPORT_OK = qw(produce);
188
189 use SQL::Translator::Utils 'debug';
190
191 my $Translator;
192
193 sub produce {
194     $Translator = shift;
195     local $DEBUG   = $Translator->debug;
196     my $scma       = $Translator->schema;
197     my $pargs      = $Translator->producer_args;
198     my $file       = $pargs->{'tt_table'} or die "No template file given!";
199     $pargs->{on_exists} ||= "die";
200
201     debug "Processing template $file\n";
202     my $out;
203     my $tt       = Template->new(
204         DEBUG    => $DEBUG,
205         ABSOLUTE => 1, # Set so we can use from the command line sensibly
206         RELATIVE => 1, # Maybe the cmd line code should set it! Security!
207         %$pargs,        # Allow any TT opts to be passed in the producer_args
208     ) || die "Failed to initialize Template object: ".Template->error;
209
210         for my $tbl ( sort {$a->order <=> $b->order} $scma->get_tables ) {
211                 my $outtmp;
212         $tt->process( $file, {
213             translator => $Translator,
214             schema     => $scma,
215             table      => $tbl,
216         }, \$outtmp ) 
217                 or die "Error processing template '$file' for table '".$tbl->name
218                   ."': ".$tt->error;
219         $out .= $outtmp;
220
221         # Write out the file...
222                 write_file(  table_file($tbl), $outtmp ) if $pargs->{mk_files};
223     }
224
225     return $out;
226 };
227
228 # Work out the filename for a given table.
229 sub table_file {
230     my ($tbl) = shift;
231     my $pargs = $Translator->producer_args;
232     my $root  = $pargs->{mk_files_base};
233     my $ext   = $pargs->{mk_file_ext};
234     return "$root/$tbl.$ext";
235 }
236
237 # Write the src given to the file given, handling the on_exists arg.
238 sub write_file {
239         my ($file, $src) = @_;
240     my $pargs = $Translator->producer_args;
241     my $root = $pargs->{mk_files_base};
242
243     if ( -e $file ) {
244         if ( $pargs->{on_exists} eq "skip" ) {
245             warn "Skipping existing $file\n";
246             return 1;
247         }
248         elsif ( $pargs->{on_exists} eq "die" ) {
249             die "File $file already exists.\n";
250         }
251         elsif ( $pargs->{on_exists} eq "replace" ) {
252             warn "Replacing $file.\n";
253         }
254         elsif ( $pargs->{on_exists} eq "insert" ) {
255             warn "Inserting into $file.\n";
256             $src = insert_code($file, $src);
257         }
258         else {
259             die "Unknown on_exists action: $pargs->{on_exists}\n";
260         }
261     }
262     else {
263         if ( my $interactive = -t STDIN && -t STDOUT ) {
264             warn "Creating $file.\n";
265         }
266     }
267
268     my ($dir) = $file =~ m!^(.*)/!; # Want greedy, eveything before the last /
269         if ( $dir and not -d $dir and $pargs->{mk_file_dir} ) { mkpath($dir); }
270
271     debug "Writing to $file\n";
272         open( FILE, ">$file") or die "Error opening file $file : $!\n";
273         print FILE $src;
274         close(FILE);
275 }
276
277 # Reads file and inserts code between the insert comments and returns the new
278 # source.
279 sub insert_code {
280     my ($file, $src) = @_;
281     my $pargs = $Translator->producer_args;
282     my $cstart = $pargs->{insert_comment_start} || "SQLF_INSERT_START";
283     my $cend   = $pargs->{insert_comment_end}   || "SQLF_INSERT_END";
284
285     # Slurp in the origional file
286     open ( FILE, "<", "$file") or die "Error opening file $file : $!\n";
287     local $/ = undef;
288     my $orig = <FILE>;
289     close(FILE);
290
291     # Insert the new code between the insert comments
292     unless (
293         $orig =~ s/^\s*?$cstart\s*?\n.*?^\s*?$cend\s*?\n/\n$cstart\n$src\n$cend\n/ms
294     ) {
295         warn "No insert done\n";
296     }
297
298     return $orig;
299 }
300
301 1;
302
303 # -------------------------------------------------------------------
304
305 =pod
306
307 =head1 AUTHOR
308
309 Mark Addison E<lt>grommit@users.sourceforge.netE<gt>.
310
311 =head1 TODO
312
313 - Some tests for the various on exists options (they have been tested
314 implicitley through use in a project but need some proper tests).
315
316 - More docs on code generation strategies.
317
318 - Better hooks for filename generation.
319
320 - Integrate with L<TT::Base> and L<TTSchema>.
321
322 =head1 SEE ALSO
323
324 SQL::Translator.
325
326 =cut