c49cb52d728985a2c813e7ad911960e3ee499812
[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-4 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 $VERSION @EXPORT_OK ];
181 $VERSION = sprintf "%d.%02d", q$Revision$ =~ /(\d+)\.(\d+)/;
182 $DEBUG   = 0 unless defined $DEBUG;
183
184 use File::Path;
185 use Template;
186 use Data::Dumper;
187 use Exporter;
188 use base qw(Exporter);
189 @EXPORT_OK = qw(produce);
190
191 use SQL::Translator::Utils 'debug';
192
193 my $Translator;
194
195 sub produce {
196     $Translator = shift;
197     local $DEBUG   = $Translator->debug;
198     my $scma       = $Translator->schema;
199     my $pargs      = $Translator->producer_args;
200     my $file       = $pargs->{'tt_table'} or die "No template file given!";
201     $pargs->{on_exists} ||= "die";
202
203     debug "Processing template $file\n";
204     my $out;
205     my $tt       = Template->new(
206         DEBUG    => $DEBUG,
207         ABSOLUTE => 1, # Set so we can use from the command line sensibly
208         RELATIVE => 1, # Maybe the cmd line code should set it! Security!
209         %$pargs,        # Allow any TT opts to be passed in the producer_args
210     ) || die "Failed to initialize Template object: ".Template->error;
211
212         for my $tbl ( sort {$a->order <=> $b->order} $scma->get_tables ) {
213                 my $outtmp;
214         $tt->process( $file, {
215             translator => $Translator,
216             schema     => $scma,
217             table      => $tbl,
218         }, \$outtmp ) 
219                 or die "Error processing template '$file' for table '".$tbl->name
220                   ."': ".$tt->error;
221         $out .= $outtmp;
222
223         # Write out the file...
224                 write_file(  table_file($tbl), $outtmp ) if $pargs->{mk_files};
225     }
226
227     return $out;
228 };
229
230 # Work out the filename for a given table.
231 sub table_file {
232     my ($tbl) = shift;
233     my $pargs = $Translator->producer_args;
234     my $root  = $pargs->{mk_files_base};
235     my $ext   = $pargs->{mk_file_ext};
236     return "$root/$tbl.$ext";
237 }
238
239 # Write the src given to the file given, handling the on_exists arg.
240 sub write_file {
241         my ($file, $src) = @_;
242     my $pargs = $Translator->producer_args;
243     my $root = $pargs->{mk_files_base};
244
245     if ( -e $file ) {
246         if ( $pargs->{on_exists} eq "skip" ) {
247             warn "Skipping existing $file\n";
248             return 1;
249         }
250         elsif ( $pargs->{on_exists} eq "die" ) {
251             die "File $file already exists.\n";
252         }
253         elsif ( $pargs->{on_exists} eq "replace" ) {
254             warn "Replacing $file.\n";
255         }
256         elsif ( $pargs->{on_exists} eq "insert" ) {
257             warn "Inserting into $file.\n";
258             $src = insert_code($file, $src);
259         }
260         else {
261             die "Unknown on_exists action: $pargs->{on_exists}\n";
262         }
263     }
264     else {
265         warn "Creating $file.\n";
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