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