RE: [CDBI] ERD's
"Andrew O'Brien" <[email protected]>
| Newsgroups | gmane.comp.lang.perl.modules.class-dbi |
|---|---|
| Message-ID | <[email protected]> |
Hi Daniel, > Does anyone know of scripts/module that can create pretty > ERD's based on > my Class::DBI. Have a look at: GraphViz GraphViz::DBI Class::DBI::Loader::GraphViz If you've got a supported DB with detectable/programmable foreign keys then they work reasonably well. The output isn't the prettiest thing you've ever seen but it works. I have the extra problem that relationships in my particular case (mysql w/o foreign key constraints and some strangely inconsistent fk naming on columns) need to come from Class::DBI modules rather than from the DB. I whipped up a quick wrapper script then generates & executes another perl script with the has_a relationships hard coded. There are probably better ways to do this but it worked for me :) Comes with command line help but a sample execution: ./db_diagram.pl --module_base Oriel::Something --path ~/src/lib/Oriel/Something --dsn 'dbi:mysql:something' --user andrewo --pass password --outfile diagram.ps It can also optionally munge the (occasionally large-scale) [e]ps output file to force it to A3 as we have an A3 printer here. Works well enough but assumes you're in a unix environment with the psnup command. If this is something that people find useful I can probably fix it up/extend it further. Hope it helps! Andrew _______________________________________________ ClassDBI mailing list ClassDBI-Ra3b/[email protected] http://lists.digitalcraftsmen.net/mailman/listinfo/classdbi
db_diagram.pl
(application/octet-stream, 4.1 KB)
#!/usr/bin/perl -w
use strict;
use File::Temp qw/tempfile/;
use Getopt::Long;
# defaults
my %opts = ( outfile => '/tmp/db_diagram.ps',
help => 0,
keep => 0,
a3ps => 1,
);
sub usage {
die "
Usage: $0 --module_base My::Namespace --path path_to_pm_files --dsn dbi_dsn_string [options]
Options:
--path dir Path in which your pm files live for this module
base. May be given more than once.
--outfile filename Output file; type is determined by extension.
Default is $opts{outfile}. See Graphviz for
supported output formats.
--[no]keep Whether to keep temporary script file. Default is
to clean up after ourselves.
--dsn string DBI dsn for connecting to your database
--user string DB connection username
--pass string DB connection password
--[no]a3ps Convert ps output to A3 (if outfile is a .ps file)
This requires the psnup command.
--help This help
";
}
GetOptions( \%opts,
'module_base|base|b=s',
'outfile=s',
'path=s@',
'dsn=s',
'user=s',
'pass=s',
'keep!',
'help|h+'
) or usage();
usage() if $opts{help};
usage() unless $opts{module_base};
usage() unless $opts{dsn};
die "Output file [$opts{outfile}] already exists, not overwriting\n" if -e $opts{outfile};
# we need a non-conflicting namespace so just get the last part of the
# module namespace. This may cause issues if you've got a
# module_basname of just one level - untested.
($opts{namespace} = $opts{module_base}) =~ s/^.*:://;
# Quick hack to find all module files
my @files;
{
my %f;
foreach my $dir (@{$opts{path}}) {
$f{$_}++ foreach glob("$dir/*.pm");
}
@files = keys %f;
}
my ($format) = ( $opts{outfile} =~ m/\.([^\.]+)$/ );
my $output_func = "as_$format";
# generate stringified args to Loader
my $loader_args = join( ",\n",
map { qq{$_ => '$opts{$_}'} }
grep { defined $opts{$_} }
qw(namespace dsn user pass) );
#my ($fh,$filename) = tempfile( 'bit_gen_diagram_XXXXX', DIR => '/tmp', UNLINK => 1 );
# do our own unlinking as we can't exec a file while its still being
# written to ...
my ($fh,$filename) = tempfile( 'db_gen_diagram_XXXXX', DIR => '/tmp' );
END { unlink $filename if ($filename && !$opts{keep}) };
chmod 0755, $filename;
print $fh '#!/usr/bin/perl -w
use strict;
use GraphViz;
use GraphViz::DBI;
use Class::DBI::Loader::GraphViz;
my $loader = Class::DBI::Loader->new(
' . $loader_args . '
);
';
# now scan all .pm files in our namespace and import all has_a
# relationships. __PACKAGE__ will need to be translated to be an
# absolute during this process.
#
# We also need to convert any all-caps module name in our namespace to
# ucfirst()
foreach my $f (@files) {
my ($name) = ($f =~ m,/([^/]+).pm$,);
if ( open( IN, "<$f" ) ) {
print $fh $_ foreach
map { s/__PACKAGE__/$opts{namespace}::$name/;
s/$opts{module_base}/$opts{namespace}/g;
s/(?<=$opts{namespace}::)([A-Z]+)/ucfirst(lc($1))/eg;
$_;
} grep( /^\s*(?:__PACKAGE__|$opts{module_base})->has_a.*$opts{module_base}/, <IN>);
close IN;
} else {
warn "Couldn't process file [$f]: $!\n";
}
}
print $fh '
my GraphViz $g = $loader->graph_tables;
$g->{RANK_DIR} = 1;
if (open(OUT,">'.$opts{outfile}.'")) {
print OUT $g->'.$output_func.';
close OUT;
}
exit;
';
close $fh;
system($filename);
if ($format =~ m/ps$/ && $opts{a3ps}) {
# now do all the resizing tricks to get this into A3 landscape
# First, get the boundingbox
open(IN,"<$opts{outfile}") or die "Cannot open $opts{outfile}: $!";
my ($width, $height);
while (<IN>) {
if (m/^\%\%BoundingBox: (\d+) (\d+) (\d+) (\d+)/) {
# cheat and don't take the offsets into account. ie don't do X2-X1, just use X2
$width = $3;
$height = $4;
last;
}
}
close IN;
die "Couldn't find width and height of image file!\n" unless $width && $height;
my $newfile = $opts{outfile};
$newfile =~ s/\.(\w+)$/_a3.$1/;
my $cmd = "/usr/bin/psnup -l -W $width -H $height -p a3 $opts{outfile} $newfile";
print "Running $cmd\n";
system($cmd);
}
exit;