From: Mark Addison Date: Thu, 1 Apr 2004 19:02:39 +0000 (+0000) Subject: Added TT::Table producer. X-Git-Tag: v0.06~97 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=0be5b227cf41450dfbd47a634ace6efdd68b1d24;p=dbsrgits%2FSQL-Translator.git Added TT::Table producer. --- diff --git a/MANIFEST b/MANIFEST index db157bc..b0eed19 100644 --- a/MANIFEST +++ b/MANIFEST @@ -45,6 +45,7 @@ lib/SQL/Translator/Producer/SQLite.pm lib/SQL/Translator/Producer/Storable.pm lib/SQL/Translator/Producer/Sybase.pm lib/SQL/Translator/Producer/TTSchema.pm +lib/SQL/Translator/Producer/TT/Table.pm lib/SQL/Translator/Producer/XML.pm lib/SQL/Translator/Producer/XML/SQLFairy.pm lib/SQL/Translator/Producer/YAML.pm @@ -83,6 +84,7 @@ t/25storable.t t/26sybase.t t/27sqlite-parser.t t/29html.t +t/33tt-table-producer.t t/data/Excel/t.xls t/data/mysql/Apache-Session-MySQL.sql t/data/mysql/BGEP-RE-create.sql @@ -95,5 +97,6 @@ t/data/sybase/create.sql t/data/template/basic.tt t/data/xml/schema-basic.xml t/data/xml/schema-basic-attribs.xml +t/data/template/table.tt META.yml Module meta-data (added by MakeMaker) SIGNATURE Public-key signature (added by MakeMaker) diff --git a/lib/SQL/Translator/Producer/TT/Table.pm b/lib/SQL/Translator/Producer/TT/Table.pm new file mode 100644 index 0000000..7ff5d57 --- /dev/null +++ b/lib/SQL/Translator/Producer/TT/Table.pm @@ -0,0 +1,323 @@ +package SQL::Translator::Producer::TT::Table; + +# ------------------------------------------------------------------- +# $Id: Table.pm,v 1.1 2004-04-01 19:02:39 grommit Exp $ +# ------------------------------------------------------------------- +# Copyright (C) 2002-4 SQLFairy Authors +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +# 02111-1307 USA +# ------------------------------------------------------------------- + +=pod + +=head1 NAME + +SQL::Translator::Producer::TT::Table - + Produces output using the Template Toolkit from a SQL schema, per table. + +=head1 SYNOPSIS + + # Normal STDOUT version + # + my $translator = SQL::Translator->new( + from => 'MySQL', + filename => 'foo_schema.sql', + to => 'TT::Table', + producer_args => { + tt_table => 'foo_table.tt', + }, + ); + print $translator->translate; + + # To generate a file per table + # + my $translator = SQL::Translator->new( + from => 'MySQL', + filename => 'foo_schema.sql', + to => 'TT::Table', + producer_args => { + tt_table => 'foo_table.tt.html', + mk_files => 1, + mk_files_base => "./doc/tables", + mk_file_ext => ".html", + on_exists => "replace", + }, + ); + # + # ./doc/tables/ now contains the templated tables as $tablename.html + # + +=head1 DESCRIPTION + +Produces schema output using a given Template Tookit template, processing that +template for each table in the schema. Optionally allows you to write the result +for each table to a seperate file. + +It needs one additional producer_arg of C which is the file +name of the template to use. This template will be passed a template var of +C, which is the current C table we are +producing, which you can then use to walk the schema via the methods documented +in that module. You also get C as a shortcut to the +C for the table and C, the +C object for this parse in case you want to get access to any +of the options etc set here. + +Here's a brief example of what the template could look like: + + [% table.name %] + ================ + [% FOREACH field = table.get_fields %] + [% field.name %] [% field.data_type %]([% field.size %]) + [% END -%] + +See F for a more complete example. + +You can also set any of the options used to initiallize the Template object by +adding them to your producer_args. See Template Toolkit docs for details of +the options. + + $translator = SQL::Translator->new( + to => 'TT', + producer_args => { + ttfile => 'foo_template.tt', + INCLUDE_PATH => '/foo/templates/tt', + INTERPOLATE => 1, + }, + ); + +If you set C and its additional options the producer will write a +seperate file for each table in the schema. This is usefull for producing +things like HTML documentation where every table gets its own page (you could +also use TTSchema producer to add an index page). Its also particulary good +for code generation where you want to produce a class file per table. + +=head1 OPTIONS + +=over 4 + +=item tt_table + +File name of the template to run for each table. + +=item mk_files + +Set to true to output a file for each table in the schema (as well as returning +the whole lot back to the Translalor and hence STDOUT). The file will be named +after the table, with the optional C added and placed in the +directory C. + +=item mk_files_ext + +Extension (without the dot) to add to the filename when using mk_files. + +=item mk_files_base = DIR + +Dir to build the table files into when using mk_files. Defaults to the current +directory. + +=item mk_file_dir + +Set true and if the file needs to written to a directory that doesn't exist, +it will be created first. + +=item on_exists [Default:replace] + +What to do if we are running with mk_files and a file already exists where we +want to write our output. One of "skip", "die", "replace", "insert". +The default is die. + +B - Over-write the existing file with the new one, clobbering anything +already there. + +B - Leave the origional file as it was and don't write the new version +anywhere. + +B - Die with an existing file error. + +B - Insert the generated output into the file bewteen a set of special +comments (defined by the following options.) Any code between the comments will +be overwritten (ie the results from a previous produce) but the rest of the file +is left alone (your custom code). +This is particularly usefull for code generation as it allows you to +generate schema derived code and then add your own custom code using it to file. +Then when the scheam changes you just re-produce and insert the new code. + +=item insert_comment_start + +The comment to look for in the file when on_exists is insert. Default is +C. Must appear on it own line, with only whitespace either +side, to be recognised. + +=item insert_comment_end + +The end comment to look for in the file when on_exists is insert. Default is +C. Must appear on it own line, with only whitespace either +side, to be recognised. + +=back + +=cut + +# ------------------------------------------------------------------- + +use strict; + +use vars qw[ $DEBUG $VERSION @EXPORT_OK ]; +$VERSION = sprintf "%d.%02d", q$Revision: 1.1 $ =~ /(\d+)\.(\d+)/; +$DEBUG = 0 unless defined $DEBUG; + +use File::Path; +use Template; +use Data::Dumper; +use Exporter; +use base qw(Exporter); +@EXPORT_OK = qw(produce); + +use SQL::Translator::Utils 'debug'; + +my $Translator; + +sub produce { + $Translator = shift; + local $DEBUG = $Translator->debug; + my $scma = $Translator->schema; + my $pargs = $Translator->producer_args; + my $file = $pargs->{'tt_table'} or die "No template file given!"; + $pargs->{on_exists} ||= "die"; + + debug "Processing template $file\n"; + my $out; + my $tt = Template->new( + DEBUG => $DEBUG, + ABSOLUTE => 1, # Set so we can use from the command line sensibly + RELATIVE => 1, # Maybe the cmd line code should set it! Security! + %$pargs, # Allow any TT opts to be passed in the producer_args + ) || die "Failed to initialize Template object: ".Template->error; + + for my $tbl ( sort {$a->order <=> $b->order} $scma->get_tables ) { + my $outtmp; + $tt->process( $file, { + translator => $Translator, + schema => $scma, + table => $tbl, + }, \$outtmp ) + or die "Error processing template '$file' for table '".$tbl->name + ."': ".$tt->error; + $out .= $outtmp; + + # Write out the file... + write_file( table_file($tbl), $outtmp ) if $pargs->{mk_files}; + } + + return $out; +}; + +# Work out the filename for a given table. +sub table_file { + my ($tbl) = shift; + my $pargs = $Translator->producer_args; + my $root = $pargs->{mk_files_base}; + my $ext = $pargs->{mk_file_ext}; + return "$root/$tbl.$ext"; +} + +# Write the src given to the file given, handling the on_exists arg. +sub write_file { + my ($file, $src) = @_; + my $pargs = $Translator->producer_args; + my $root = $pargs->{mk_files_base}; + + if ( -e $file ) { + if ( $pargs->{on_exists} eq "skip" ) { + warn "Skipping existing $file\n"; + return 1; + } + elsif ( $pargs->{on_exists} eq "die" ) { + die "File $file already exists.\n"; + } + elsif ( $pargs->{on_exists} eq "replace" ) { + warn "Replacing $file.\n"; + } + elsif ( $pargs->{on_exists} eq "insert" ) { + warn "Inserting into $file.\n"; + $src = insert_code($file, $src); + } + else { + die "Unknown on_exists action: $pargs->{on_exists}\n"; + } + } + else { + warn "Creating $file.\n"; + } + + my ($dir) = $file =~ m!^(.*)/!; # Want greedy, eveything before the last / + if ( $dir and not -d $dir and $pargs->{mk_file_dir} ) { mkpath($dir); } + + debug "Writing to $file\n"; + open( FILE, ">$file") or die "Error opening file $file : $!\n"; + print FILE $src; + close(FILE); +} + +# Reads file and inserts code between the insert comments and returns the new +# source. +sub insert_code { + my ($file, $src) = @_; + my $pargs = $Translator->producer_args; + my $cstart = $pargs->{insert_comment_start} || "SQLF_INSERT_START"; + my $cend = $pargs->{insert_comment_end} || "SQLF_INSERT_END"; + + # Slurp in the origional file + open ( FILE, "<", "$file") or die "Error opening file $file : $!\n"; + local $/ = undef; + my $orig = ; + close(FILE); + + # Insert the new code between the insert comments + unless ( + $orig =~ s/^\s*?$cstart\s*?\n.*?^\s*?$cend\s*?\n/\n$cstart\n$src\n$cend\n/ms + ) { + warn "No insert done\n"; + } + + return $orig; +} + +1; + +# ------------------------------------------------------------------- + +=pod + +=head1 AUTHOR + +Mark Addison Egrommit@users.sourceforge.netE. + +=head1 TODO + ++ Some tests for the various on exists options (they have been tested +implicitley through use in a project but need some proper tests). + ++ More docs code generation strategies. + ++ Better hooks for filename generation. + ++ Better integration with TTSchema. + +=head1 SEE ALSO + +SQL::Translator. + +=cut diff --git a/t/33tt-table-producer.t b/t/33tt-table-producer.t new file mode 100644 index 0000000..226679e --- /dev/null +++ b/t/33tt-table-producer.t @@ -0,0 +1,98 @@ +#!/usr/bin/perl -w +# vim:filetype=perl + +# Before `make install' is performed this script should be runnable with +# `make test'. After `make install' it should work as `perl test.pl' + +use strict; +use Test::More; +use Test::Exception; +use Test::SQL::Translator qw(maybe_plan); + +use Data::Dumper; +use vars '%opt'; +BEGIN { map { $opt{$_}=1 if s/^-// } @ARGV; } +use constant DEBUG => (exists $opt{d} ? 1 : 0); + +use FindBin qw/$Bin/; +use File::Temp qw/tempdir/; + +# Testing 1,2,3,4... +#============================================================================= + +BEGIN { + maybe_plan(8, 'Template', 'Test::Differences') +} +use Test::Differences; + +use SQL::Translator; +use SQL::Translator::Producer::TT::Table; + +# Setup a tmp directory we can output files to. +my $tdir = tempdir( CLEANUP => 1 ); + +# Parse the test XML schema +my $obj; +$obj = SQL::Translator->new( + debug => DEBUG, #$opt{d}, + show_warnings => 1, + add_drop_table => 1, + from => "SQLite", + filename => "$Bin/data/sqlite/create.sql", + to => "TT-Table", + producer_args => { + tt_table => "$Bin/data/template/table.tt", + mk_files => 1, + mk_files_base => "$tdir", + mk_file_ext => "txt", + on_exists => "replace", + }, +); +my $out; +lives_ok { $out = $obj->translate; } "Translate ran"; +ok $out ne "" ,"Produced something!"; +warn $obj->error unless $out; + +# Normal output looks ok +local $/ = undef; # slurp +eq_or_diff $out, ,"Output looks right"; + +# File output +my @files = glob("$tdir/*.txt"); +ok( @files == 2, "Wrote 2 files." ); +is( $files[0], "$tdir/person.txt" , "Wrote person.txt" ); +is( $files[1], "$tdir/pet.txt" , "Wrote pet.txt" ); + +open(FILE, "$tdir/person.txt") || die "Couldn't open $tdir/person.txt : $!"; +eq_or_diff , qq{Table: person + Primary Key: person_id + Foriegn Keys: + Data Fields: name, age, weight, iq, description + +} +, "person.txt looks right"; + +open(FILE, "$tdir/pet.txt") || die "Couldn't open $tdir/pet.txt : $!"; +eq_or_diff , qq{Table: pet + Primary Key: pet_id, person_id + Foriegn Keys: + Data Fields: name, age + +} +, "person.txt looks right"; + + +print $out if DEBUG; +#print "Debug:", Dumper($obj) if DEBUG; + +__DATA__ +Table: person + Primary Key: person_id + Foriegn Keys: + Data Fields: name, age, weight, iq, description + +Table: pet + Primary Key: pet_id, person_id + Foriegn Keys: + Data Fields: name, age + diff --git a/t/data/template/table.tt b/t/data/template/table.tt new file mode 100644 index 0000000..be59a80 --- /dev/null +++ b/t/data/template/table.tt @@ -0,0 +1,5 @@ +Table: [% table %] + Primary Key: [% table.pkey_fields.join(", ") %] + Foriegn Keys: [% table.key_fields.join(", ") %] + Data Fields: [% table.data_fields.join(", ") %] +