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