The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/env perl
# usage: util/importgraph.pl [--layout=neato]
# produces importgraph.ps in the current directory
# it expects to run from the top pugs directory with src present
# arrows point from importer to importee

use strict;
use warnings;
use File::Find;
use GraphViz;
use Getopt::Long;

my $layout = "neato";
my $overlap = "false";
my @ignore;
GetOptions(
  "layout=s" => \$layout,
  "overlap=s" => \$overlap,
  "ignore=s" => \@ignore,
) or die "invalid command line";

my $g = GraphViz->new(
  layout => $layout,
  directed => 1,
  rankdir => 1,
  overlap => $overlap,
);

my %modules;
find sub {
  return unless /\.hs$/;

  my ($module_name, $exports, %imports);

  open my $fh, '<', $_
    or die "couldn't open $File::Find::name: $!\n";

  while(<$fh>) {
    if (/^module \s+ ([\w.]+) (\s* \()?/x) {
      $module_name = $1;
      $exports = defined $2;
    } elsif (/^import \s+ (qualified \s+)? ([\w.]+) (\s* \()?/x) {
      $imports{$2} = [defined($1), defined($3)];
    } elsif (/^import|^module/) {
      warn "Unrecognised import|module: $_";
    }
  }

  close $fh;

  unless (defined $module_name) {
    warn "couldn't find a module in $File::Find::name\n";
    return;
  }

  $modules{$module_name} = [\%imports, $exports];
}, 'src';

#delete $modules{$_} for @ignore;
for my $mod (values %modules) {
  for my $ignore (@ignore) {
    delete $mod->[0]->{$ignore};
  }
}

# setup some predefined clusters
sub setup_mod {
  my $type = shift;
  my $name = shift;
  my $regexp = join '|', map {
    UNIVERSAL::isa($_, 'Regexp') ? $_ : "^\Q$_\E\$" } @_;
  $regexp = qr/$regexp/;
  for my $mod (keys %modules) {
    next unless $mod =~ /$regexp/;
    $modules{$mod}->[$type] = $name;
    print "Adding $mod to $name\n";
  }
}
sub setup_cluster { setup_mod(2, @_) }
sub setup_rank { setup_mod(3, @_) }

setup_cluster('Pugs.Rule', qr/^Pugs\.Rule\b/);
setup_cluster('Pugs.AST', qr/^Pugs\.AST\b/);
setup_cluster('IMC', qr/^IMC\b/);
setup_cluster('RRegex', qr/^RRegex\b/);
setup_cluster('Emit', qr/^Emit\b/);
setup_rank('parser', qr/^Pugs\.Parser\.\w+$/);
setup_rank('parser_program', 'Pugs.Parser.Program');
setup_rank('prim', qr/^Pugs\.Prim\.\w+$/);
setup_rank('prim_lifts', 'Pugs.Prim.Lifts');
setup_rank('embed', qr/^Pugs\.Embed\.\w+$/);
setup_rank('codegens', 'Pugs.Compile.Haskell', 'Pugs.Compile.Pugs',
  qr/^Pugs\.CodeGen\.\w+$/);

my ($nodes, $edges) = (0, 0);
while (my ($name, $module) = each %modules) {
  my $cluster = $module->[2];
  my $rank = $module->[3];
  $g->add_node($name, color => ($module->[1] ? 'green' : 'black'),
    (defined $cluster ? (cluster => $cluster) : ()),
    (defined $rank ? (rank => $rank) : ()),
    );
  $nodes++;

  while(my ($k, $edge) = each %{$module->[0]}) {
    next unless exists $modules{$k}; # only pugs modules
    my $color = $edge->[1] ? 'green' : $edge->[0] ? "blue" : "black";
    $g->add_edge($name, $k, color => $color);
    $edges++;
  }
}
print "$nodes nodes and $edges edges\n";

#$g->as_canon("importgraph.dot");
#$g->as_png("importgraph.png");
$g->as_ps("importgraph.ps");