1package SQL::Translator::Producer::TT::Table;
2
3=pod
4
5=head1 NAME
6
7SQL::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
44Produces schema output using a given Template Tookit template,
45processing that template for each table in the schema. Optionally
46allows you to write the result for each table to a separate file.
47
48It needs one additional producer_arg of C<tt_table> which is the file
49name of the template to use.  This template will be passed a template
50var of C<table>, which is the current
51L<SQL::Translator::Schema::Table> table we are producing,
52which you can then use to walk the schema via the methods documented
53in that module. You also get C<schema> as a shortcut to the
54L<SQL::Translator::Schema> for the table and C<translator>,
55the L<SQL::Translator> object for this parse in case you want to get
56access to any of the options etc set here.
57
58Here'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
66See F<t/data/template/table.tt> for a more complete example.
67
68You can also set any of the options used to initialize the Template
69object by adding them to your producer_args. See Template Toolkit docs
70for 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
81If you set C<mk_files> and its additional options the producer will
82write a separate file for each table in the schema. This is useful for
83producing things like HTML documentation where every table gets its
84own page (you could also use TTSchema producer to add an index page).
85It's also particularly good for code generation where you want to
86produce a class file per table.
87
88=head1 OPTIONS
89
90=over 4
91
92=item tt_table
93
94File name of the template to run for each table.
95
96=item mk_files
97
98Set to true to output a file for each table in the schema (as well as
99returning the whole lot back to the Translalor and hence STDOUT). The
100file will be named after the table, with the optional C<mk_files_ext>
101added and placed in the directory C<mk_files_base>.
102
103=item mk_files_ext
104
105Extension (without the dot) to add to the filename when using mk_files.
106
107=item mk_files_base = DIR
108
109Dir to build the table files into when using mk_files. Defaults to the
110current directory.
111
112=item mk_file_dir
113
114Set true and if the file needs to written to a directory that doesn't
115exist, it will be created first.
116
117=item on_exists [Default:replace]
118
119What to do if we are running with mk_files and a file already exists
120where we want to write our output. One of "skip", "die", "replace",
121"insert".  The default is die.
122
123B<replace> - Over-write the existing file with the new one, clobbering
124anything already there.
125
126B<skip> - Leave the original file as it was and don't write the new
127version anywhere.
128
129B<die> - Die with an existing file error.
130
131B<insert> - Insert the generated output into the file between a set of
132special comments (defined by the following options.) Any code between
133the comments will be overwritten (ie the results from a previous
134produce) but the rest of the file is left alone (your custom code).
135This is particularly useful for code generation as it allows you to
136generate schema derived code and then add your own custom code
137to the file.  Then when the schema changes you just re-produce to
138insert the new code.
139
140=item insert_comment_start
141
142The comment to look for in the file when on_exists is C<insert>. Default
143is C<SQLF INSERT START>. Must appear on it own line, with only
144whitespace either side, to be recognised.
145
146=item insert_comment_end
147
148The end comment to look for in the file when on_exists is C<insert>.
149Default is C<SQLF INSERT END>. Must appear on it own line, with only
150whitespace either side, to be recognised.
151
152=back
153
154=cut
155
156use strict;
157use warnings;
158
159our ( $DEBUG, @EXPORT_OK );
160our $VERSION = '1.62';
161$DEBUG   = 0 unless defined $DEBUG;
162
163use File::Path;
164use Template;
165use Data::Dumper;
166use Exporter;
167use base qw(Exporter);
168@EXPORT_OK = qw(produce);
169
170use SQL::Translator::Utils 'debug';
171
172my $Translator;
173
174sub 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.
210sub 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.
219sub 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.
260sub 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
2821;
283
284=pod
285
286=head1 AUTHOR
287
288Mark 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
293implicitly 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
304SQL::Translator.
305
306=cut
307