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