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