Added TT::Table producer.
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Producer / TT / Table.pm
1 package SQL::Translator::Producer::TT::Table;
2
3 # -------------------------------------------------------------------
4 # $Id: Table.pm,v 1.1 2004-04-01 19:02:39 grommit Exp $
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, processing that
65 template for each table in the schema. Optionally allows you to write the result
66 for each table to a seperate 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 var of
70 C<table>, which is the current C<SQL::Translator::Producer::Table> table we are
71 producing, which you can then use to walk the schema via the methods documented
72 in that module. You also get C<schema> as a shortcut to the
73 C<SQL::Translator::Producer::Schema> for the table and C<translator>, the
74 C<SQL::Translator> object for this parse in case you want to get access to any
75 of the options etc set here.
76
77 Here's a brief example of what the template could look like:
78
79   [% table.name %]
80   ================
81   [% FOREACH field = table.get_fields %]
82       [% field.name %]   [% field.data_type %]([% field.size %])
83   [% END -%]
84
85 See F<t/data/template/table.tt> for a more complete example.
86
87 You can also set any of the options used to initiallize the Template object by
88 adding them to your producer_args. See Template Toolkit docs for details of
89 the options.
90
91   $translator          = SQL::Translator->new(
92       to               => 'TT',
93       producer_args    => {
94           ttfile       => 'foo_template.tt',
95           INCLUDE_PATH => '/foo/templates/tt',
96           INTERPOLATE  => 1,
97       },
98   );
99
100 If you set C<mk_files> and its additional options the producer will write a
101 seperate file for each table in the schema. This is usefull for producing
102 things like HTML documentation where every table gets its own page (you could
103 also use TTSchema producer to add an index page). Its also particulary good
104 for code generation where you want to 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 returning
117 the whole lot back to the Translalor and hence STDOUT). The file will be named
118 after the table, with the optional C<mk_files_ext> added and placed in the
119 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 current
128 directory.
129
130 =item mk_file_dir
131
132 Set true and if the file needs to written to a directory that doesn't exist,
133 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 where we
138 want to write our output. One of "skip", "die", "replace", "insert".
139 The default is die.
140
141 B<replace> - Over-write the existing file with the new one, clobbering anything
142 already there.
143
144 B<skip> - Leave the origional file as it was and don't write the new version
145 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 special
150 comments (defined by the following options.) Any code between the comments will
151 be overwritten (ie the results from a previous produce) but the rest of the file
152 is left alone (your custom code).
153 This is particularly usefull for code generation as it allows you to
154 generate schema derived code and then add your own custom code using it to file.
155 Then when the scheam changes you just re-produce and insert the new code.
156
157 =item insert_comment_start
158
159 The comment to look for in the file when on_exists is insert. Default is
160 C<SQLF INSERT START>. Must appear on it own line, with only whitespace either
161 side, to be recognised.
162
163 =item insert_comment_end
164
165 The end comment to look for in the file when on_exists is insert. Default is
166 C<SQLF INSERT END>. Must appear on it own line, with only whitespace either
167 side, to be recognised.
168
169 =back
170
171 =cut
172
173 # -------------------------------------------------------------------
174
175 use strict;
176
177 use vars qw[ $DEBUG $VERSION @EXPORT_OK ];
178 $VERSION = sprintf "%d.%02d", q$Revision: 1.1 $ =~ /(\d+)\.(\d+)/;
179 $DEBUG   = 0 unless defined $DEBUG;
180
181 use File::Path;
182 use Template;
183 use Data::Dumper;
184 use Exporter;
185 use base qw(Exporter);
186 @EXPORT_OK = qw(produce);
187
188 use SQL::Translator::Utils 'debug';
189
190 my $Translator;
191
192 sub produce {
193     $Translator = shift;
194     local $DEBUG   = $Translator->debug;
195     my $scma       = $Translator->schema;
196     my $pargs      = $Translator->producer_args;
197     my $file       = $pargs->{'tt_table'} or die "No template file given!";
198     $pargs->{on_exists} ||= "die";
199
200     debug "Processing template $file\n";
201     my $out;
202     my $tt       = Template->new(
203         DEBUG    => $DEBUG,
204         ABSOLUTE => 1, # Set so we can use from the command line sensibly
205         RELATIVE => 1, # Maybe the cmd line code should set it! Security!
206         %$pargs,        # Allow any TT opts to be passed in the producer_args
207     ) || die "Failed to initialize Template object: ".Template->error;
208
209         for my $tbl ( sort {$a->order <=> $b->order} $scma->get_tables ) {
210                 my $outtmp;
211         $tt->process( $file, {
212             translator => $Translator,
213             schema     => $scma,
214             table      => $tbl,
215         }, \$outtmp ) 
216                 or die "Error processing template '$file' for table '".$tbl->name
217                   ."': ".$tt->error;
218         $out .= $outtmp;
219
220         # Write out the file...
221                 write_file(  table_file($tbl), $outtmp ) if $pargs->{mk_files};
222     }
223
224     return $out;
225 };
226
227 # Work out the filename for a given table.
228 sub table_file {
229     my ($tbl) = shift;
230     my $pargs = $Translator->producer_args;
231     my $root  = $pargs->{mk_files_base};
232     my $ext   = $pargs->{mk_file_ext};
233     return "$root/$tbl.$ext";
234 }
235
236 # Write the src given to the file given, handling the on_exists arg.
237 sub write_file {
238         my ($file, $src) = @_;
239     my $pargs = $Translator->producer_args;
240     my $root = $pargs->{mk_files_base};
241
242     if ( -e $file ) {
243         if ( $pargs->{on_exists} eq "skip" ) {
244             warn "Skipping existing $file\n";
245             return 1;
246         }
247         elsif ( $pargs->{on_exists} eq "die" ) {
248             die "File $file already exists.\n";
249         }
250         elsif ( $pargs->{on_exists} eq "replace" ) {
251             warn "Replacing $file.\n";
252         }
253         elsif ( $pargs->{on_exists} eq "insert" ) {
254             warn "Inserting into $file.\n";
255             $src = insert_code($file, $src);
256         }
257         else {
258             die "Unknown on_exists action: $pargs->{on_exists}\n";
259         }
260     }
261     else {
262         warn "Creating $file.\n";
263     }
264
265     my ($dir) = $file =~ m!^(.*)/!; # Want greedy, eveything before the last /
266         if ( $dir and not -d $dir and $pargs->{mk_file_dir} ) { mkpath($dir); }
267
268     debug "Writing to $file\n";
269         open( FILE, ">$file") or die "Error opening file $file : $!\n";
270         print FILE $src;
271         close(FILE);
272 }
273
274 # Reads file and inserts code between the insert comments and returns the new
275 # source.
276 sub insert_code {
277     my ($file, $src) = @_;
278     my $pargs = $Translator->producer_args;
279     my $cstart = $pargs->{insert_comment_start} || "SQLF_INSERT_START";
280     my $cend   = $pargs->{insert_comment_end}   || "SQLF_INSERT_END";
281
282     # Slurp in the origional file
283     open ( FILE, "<", "$file") or die "Error opening file $file : $!\n";
284     local $/ = undef;
285     my $orig = <FILE>;
286     close(FILE);
287
288     # Insert the new code between the insert comments
289     unless (
290         $orig =~ s/^\s*?$cstart\s*?\n.*?^\s*?$cend\s*?\n/\n$cstart\n$src\n$cend\n/ms
291     ) {
292         warn "No insert done\n";
293     }
294
295     return $orig;
296 }
297
298 1;
299
300 # -------------------------------------------------------------------
301
302 =pod
303
304 =head1 AUTHOR
305
306 Mark Addison E<lt>grommit@users.sourceforge.netE<gt>.
307
308 =head1 TODO
309
310 + Some tests for the various on exists options (they have been tested
311 implicitley through use in a project but need some proper tests).
312
313 + More docs code generation strategies.
314
315 + Better hooks for filename generation.
316
317 + Better integration with TTSchema.
318
319 =head1 SEE ALSO
320
321 SQL::Translator.
322
323 =cut