package My::Builder;
use Module::Build;
our @ISA = qw(Module::Build);
use File::Spec;
use Test::More;
use strict;
sub ACTION_manifest {
my $self = shift;
$self -> depends_on('tests');
$self -> SUPER::ACTION_manifest;
}
sub ACTION_dependencies {
my $self = shift;
$self -> depends_on('code');
require XML::LibXML;
require Pod::Coverage;
my $changed = 0;
my($doc, $root) = $self -> _get_deps_dom;
my $pm_files = $self -> find_pm_files;
my $PL_files = $self -> find_PL_files;
my @files = values %$pm_files, values %$PL_files;
foreach my $file (@files) {
my @dirs = File::Spec -> splitdir($file);
shift @dirs;
my $module = join("::", @dirs);
$module =~ s{\.[^.]+$}{};
next unless $self -> _is_modified_after_deps(File::Spec -> catfile('blib', $file));
#print "Processing $module from $file\n";
my $coverage = Pod::Coverage -> new(
package => $module,
pod_from => File::Spec -> catfile('blib', $file)
);
my $module_el;
unless(($module_el) = ($root -> findnodes('module[@name="' . $module . '"]'))) {
$module_el = $doc -> createElement('module');
$module_el -> setAttribute(name => $module);
$root -> appendChild($module_el);
$changed = 1;
}
my $interface_el;
unless(($interface_el) = ($module_el -> findnodes('interface'))) {
$interface_el = $doc -> createElement('interface');
$module_el -> appendChild($interface_el);
$changed = 1;
}
my @methods = sort($coverage -> uncovered, $coverage -> covered);
my($method_el);
foreach my $method (@methods) {
unless(($method_el) = ($interface_el -> findnodes('method[@name="' . $method . '"]'))) {
$method_el = $doc -> createElement('method');
$method_el -> setAttribute(name => $method);
$interface_el -> appendChild($method_el);
$changed = 1;
}
}
}
my $modules = $root -> findnodes('module');
my %provides = map { $_ -> getAttribute('name') => 1 } $modules -> get_nodelist;
foreach my $module_el ($modules -> get_nodelist) {
my $item = $module_el -> getAttribute('name');
my @dirs = split(/::/, $item);
my $file = File::Spec -> catfile(qw(blib lib), @dirs[0..$#dirs-1], $dirs[$#dirs] . ".pm");
next unless $self -> _is_modified_after_deps($file);
local($/);
# load dependencies from module
open my $fh, "<", $file or next;
my $content = <$fh>;
close $fh;
my @uses = $content =~ m{#?\s*use\s+(.*?);}mg;
@uses = grep { $provides{$_} }
map { s{^base\s+}{} ? eval : m{^(\S+)} }
grep { m{^base} || m{^[A-Z]} }
grep { !m{^#} } @uses;
#print "$item: ", join(" ", @uses), "\n";
foreach my $module ( @uses ) {
my($dep_el) = ($module_el -> findnodes('dependence[@module="' . $module . '"]'));
unless($dep_el) {
next if $module eq $item;
#print "$item does not depend on $module yet\n";
$dep_el = $doc -> createElement('dependence');
$dep_el -> setAttribute(module => $module);
$module_el -> appendChild($dep_el);
$changed = 1;
}
elsif($module eq $item) {
$module_el -> removeChild($dep_el);
$changed = 1;
}
else {
#print "$item already depends on $module\n";
}
}
#print "Uses in $item:\n<", join("><", @uses), ">\n";
}
if($changed) {
open my $fh, ">", "deps.xml" or die "Unable to open deps.xml: $!\n";
my $xml = $doc -> toString(2);
$xml =~ s{\n(\s*\n)+}{\n}mg;
print $fh $xml;
close $fh;
}
}
sub ACTION_tests {
my $self = shift;
$self -> depends_on('dependencies');
my($doc, $root) = $self -> _get_deps_dom;
require Algorithm::Dependency::Ordered;
require Pod::Tests;
eval { require Pod::Coverage; };
my $has_pod_coverage = $@ ? 0 : 1;
my $source = __PACKAGE__::MyXMLSource -> new( qw(dependence module), $root -> findnodes('module') );
my $dep = Algorithm::Dependency::Ordered -> new(
source => $source,
ignore_orphans => 1,
);
my $schedule = $dep->schedule_all;
unless($schedule) {
warn "Unable to create a schedule of modules.\n";
return;
}
$self -> _set_init_deps($dep);
my $counter = "0" x length(2+scalar(@$schedule));
mkdir 't' unless -d 't';
opendir my $dir, 't' or die "Unable to open directory: $!\n";
unlink File::Spec -> catfile('t', $_) for grep { m{^\d+-.+\.t$} } (readdir($dir));
closedir $dir;
my $filename = "${counter}-compile.t";
#print "t/${filename}\n";
open my $fh, ">", File::Spec -> catfile('t', $filename) or die "Unable to open file: $!\n";
my $num_tests = scalar(@$schedule);
my $tlib = File::Spec -> catdir(qw(t lib));
print $fh <<1HERE1;
use lib q{$tlib};
use My::Builder;
use Test::More tests => $num_tests;
BEGIN {
our \@modules = qw(
@{[join("\n ", @$schedule)]}
);
use_ok(\$_) for \@modules;
}
# record test results for later
my \$tester = Test::More -> builder;
my \$builder = My::Builder -> current;
my \@details = \$tester -> details;
my \$test_results = \$builder -> notes('test_results') || { };
for(my \$i = 0; \$i <= \$#modules; \$i++) {
\$test_results -> {\$modules[\$i]} -> {compile_ok} =
\$details[\$i] -> {actual_ok};
}
\$builder -> notes(test_results => \$test_results);
1HERE1
$counter ++;
foreach my $item (@$schedule) {
my $filename = lc $item;
$filename =~ s{::}{-}g;
#print "t/${counter}-${filename}.t\n";
open my $fh, ">", File::Spec -> catfile('t', "${counter}-${filename}.t") or die "Unable to open file: $!\n";
print $fh "use lib q{$tlib};\nuse My::Builder;\n";
$self -> _create_test($fh, $item, $root -> findnodes("module[\@name='$item']"));
$counter ++;
}
#print "t/${counter}-pod.t\n";
$filename = "${counter}-pod.t";
open $fh, ">", File::Spec -> catfile('t', $filename) or die "Unable to open file: $!\n";
print $fh "use lib q{$tlib};\nuse My::Builder;\n";
$self -> _create_pod_test($fh, $schedule);
close $fh;
$counter ++;
#print "t/${counter}-cleanup.t\n";
open $fh, ">", File::Spec -> catfile('t', "${counter}-cleanup.t") or die "Unable to open file: $!\n";
print $fh "use lib q{$tlib};\nuse My::Builder;";
$self -> _create_cleanup($fh);
close $fh;
$counter ++;
}
sub _create_test {
my($self, $fh, $module, @nodes) = @_;
my($source, $dep, $schedule);
$source = __PACKAGE__::MyXMLSource -> new( qw(dependence method), (
map { $_ -> findnodes('interface/method') } @nodes
) ) if @nodes;
$dep = Algorithm::Dependency::Ordered -> new(
source => $source,
ignore_orphans => 1,
) if $source;
$schedule = $dep->schedule_all if $dep;
unless($schedule) {
print $fh qq{# No tests\nuse Test::More skip_all => 'No tests defined';};
return;
}
my $pod_tests_ex = Pod::Tests -> new;
{ no warnings; $pod_tests_ex -> parse_file(File::Spec -> catfile(qw(blib lib), split(/::/, $module)) . ".pm"); }
my @pod_tests;
{ no warnings; @pod_tests = $pod_tests_ex -> tests; }
my %extra_tests;
foreach my $t (@pod_tests) {
$t->{code} =~ s{^\s*#\s*(.*?)\n}{}m;
my $method = $1;
$t -> {code} =~ s{__PACKAGE__}{$module}mg;
$t -> {code} =~ s{__METHOD__}{$method}mg;
do { } while $t -> {code} =~ s{
__OBJECT__
(?: \( ([^)]+) \) )?
}{
defined($1) ? "\$objects{'$1'}" : "\$objects{'_default'}"
}mxe;
$extra_tests{$method} ||= [];
push @{$extra_tests{$method}}, $t;
}
foreach my $method (keys %extra_tests) {
no warnings;
$extra_tests{$method} = join("\n\n", $pod_tests_ex -> build_tests(@{$extra_tests{$method}}));
}
$self -> _register_cleanup($module, $extra_tests{'CLEANUP'}) if $extra_tests{'CLEANUP'};
$self -> _register_init($module, "use $module;\n".$extra_tests{'INIT'}) if $extra_tests{'INIT'};
print $fh "# Testing found ", scalar(@nodes), " nodes\n";
print $fh <<1HERE1;
use Test::More;
use Module::Build;
BEGIN {
eval {
require $module;
};
if(\$\@) {
plan skip_all => 'Unable to load $module';
exit 0;
}
}
plan no_plan;
my \$builder = My::Builder -> current;
1HERE1
my $tests = 0;
my @ids;
my @constructors = map { $_ -> findnodes('interface/constructor') } @nodes;
print $fh <<1HERE1;
my \%objects;
1HERE1
$extra_tests{'BEGIN'} = $self -> _query_init($module) . "\n" . (defined $extra_tests{'BEGIN'} ? $extra_tests{'BEGIN'} : '')
if $self -> _query_init($module);
foreach my $phase (qw(
BEGIN
END
)) {
print $fh "$phase {\n" . $extra_tests{$phase} . "\n}\n"
if(defined $extra_tests{$phase});
}
foreach my $constructor ( @constructors ) {
my $id = $constructor -> getAttribute('id');
$id = '_default' if !defined($id) || $id eq '';
my $method = $constructor -> getAttribute('method');
$method = 'new' if !defined($method) || $method eq '';
# need to get arguments...
push @ids, $id;
my $arguments = $self -> _parse_arguments($constructor);
{ no warnings; print $fh <<1HERE1
\$builder -> begin_tests('$method');
eval {
\$objects{'$id'} = $module -> $method($arguments);
};
ok(!\$\@);
isa_ok(\$objects{'$id'}, '$module');
$extra_tests{$method}
\$builder -> end_tests('$method');
1HERE1
}
}
my %id_seen;
@ids = grep { !$id_seen{$_}++ } @ids;
print $fh "my \@ids = qw(", join(" ", @ids), ");\n\n" if @ids;
my %seen = ( BEGIN => 1, END => 1, CLEANUP => 1, INIT => 1 );
foreach my $method (@$schedule, keys %extra_tests) {
next if $seen{$method}++;
print $fh "\n# method: $method\n\n\$builder -> begin_tests('$method');\n\n";
my($method_el) = ( map { $_ -> findnodes('interface/method[@name="' . $method . '"]') } @nodes );
my @tests = ( );
@tests = $method_el -> findnodes('test') if $method_el;
foreach my $test (@tests) {
my($in_el, $out_el);
($in_el) = ($test -> findnodes('in'));
($out_el) = ($test -> findnodes('out'));
my $in = $self -> _parse_arguments($in_el);
my $out = $self -> _parse_arguments($out_el);
my($object);
if(@ids) {
$object = $test -> getAttribute('object-id');
$object = '_default' if !defined($object) || $object eq '';
print $fh <<1HERE1;
eval {
\@result = ( );
(\@result) = (\$objects{'$object'} -> $method($in));
};
ok(!\$\@);
is_deeply(\\\@result, [ $out ], q($module($object) -> $method));
1HERE1
}
else {
print $fh <<1HERE1;
eval {
\@result = ${module}::${method}($in);
};
ok(!\$\@);
is_deeply(\\\@result, [ $out ], q(${module}::${method}));
1HERE1
}
}
print $fh "\n". $extra_tests{$method}. "\n" if defined $extra_tests{$method} && !ref($extra_tests{$method});
print $fh "\n\$builder -> end_tests('$method');\n";
}
print $fh <<1HERE1;
# record test results for report
\$builder -> record_test_details('$module');
my \$tester = Test::More -> builder;
if(\$tester -> current_test == 0) {
\$tester -> skip_all( 'No tests defined' );
}
1HERE1
}
sub _create_pod_test {
my($self, $fh, $schedule) = @_;
my $num_tests = scalar(@$schedule);
print $fh <<1HERE1;
use Test::More;
my \$build = My::Builder -> current;
my \$pod_coverage = { };
BEGIN {
our(\$has_test_pod, \$has_pod_coverage) = (1,1);
eval {
require Test::Pod;
Test::Pod -> import;
};
\$has_test_pod = 0 if \$\@;
eval {
require Pod::Coverage;
Pod::Coverage -> import;
};
\$has_pod_coverage = 0 if \$\@;
unless(\$has_test_pod || \$has_pod_coverage) {
plan skip_all => 'At least one of Test::Pod and Pod::Coverage are required to run Pod tests';
exit 0;
}
}
plan tests => $num_tests*(\$has_test_pod + \$has_pod_coverage);
1HERE1
foreach my $item (sort @$schedule) {
my $file = File::Spec -> catfile(qw(blib lib), split(/::/, $item)) . ".pm";
print $fh <<1HERE1;
pod_file_ok(
q($file),
q(Testing POD in $item)
) if \$has_test_pod;
if(\$has_pod_coverage) {
my \$coverage = Pod::Coverage -> new(
package => q($item),
pod_from => q($file)
);
ok(\$coverage -> coverage == 1, q(Testing POD coverage in $item));
# store stuff for report later
\$pod_coverage -> {q($item)} = {
why_unrated => \$coverage -> why_unrated,
uncovered => [ \$coverage -> uncovered ],
covered => [ \$coverage -> covered ],
}
}
1HERE1
}
print $fh <<1HERE1;
\$build -> notes(pod_coverage => \$pod_coverage);
1HERE1
}
{
my %init;
my $deps;
my %seen;
sub _register_init {
my($self, $module, $code) = @_;
foreach my $m (@{$deps->{$module} || []}) {
#next if $seen{"$module -> $m"}++;
warn("registering INIT section for $module -> $m\n");
$init{$m} .= $code;
$self -> _register_init($m, $code);
}
}
sub _query_init {
my($self, $module) = @_;
return $init{$module};
}
sub _set_init_deps {
my($self, $dep) = @_;
$deps = $self -> _inv_deps($dep);
}
}
{
my %cleanup;
my @cleanup_modules;
sub _register_cleanup {
my($self, $module, $code) = @_;
$cleanup{$module} = $code;
unshift @cleanup_modules, $module;
}
sub _create_cleanup {
my($self, $fh) = @_;
print $fh <<1HERE1;
use Test::More;
1HERE1
foreach my $module (@cleanup_modules) {
print $fh "\n# Cleanup for $module\n\nuse $module;\n\n";
print $fh $cleanup{$module}, "\n\n";
}
print $fh <<1HERE1;
plan skip_all => 'No tests defined';
1HERE1
}
}
sub ACTION_cover {
my $self = shift;
$self -> depends_on('tests');
system('cover', '-delete');
local $ENV{HARNESS_PERL_SWITCHES} .= " -MDevel::Cover";
$self -> depends_on('test');
system('cover');
}
sub ACTION_report {
my $self = shift;
require Algorithm::Dependency::Ordered;
require Perl::Tidy;
require Pod::Coverage;
require IO::String;
my $pod_coverage = $self -> notes('pod_coverage');
my $test_results = $self -> notes('test_results');
if(!ref $test_results) {
$self -> depends_on('test');
$pod_coverage = $self -> notes('pod_coverage');
$test_results = $self -> notes('test_results');
}
my($doc, $root) = $self -> _get_deps_dom;
#print "doc: $doc; root: $root\n";
my $source = __PACKAGE__::MyXMLSource -> new( qw(dependence module), $root -> findnodes('module') );
#print "source: $source\n";
my $dep = Algorithm::Dependency::Ordered -> new(
source => $source,
ignore_orphans => 1,
);
#print "dep: $dep\n";
my @parallel_modules = $self -> _parallel_deps($dep);
my $schedule = $dep->schedule_all;
#print "schedule: $schedule\n";
my %counts;
my %totals;
my $totals = __PACKAGE__::Counter -> new;
#print "totals: $totals\n";
mkdir 'report' unless -d 'report';
my %overall_tests;
foreach my $package (@$schedule) {
my $filename = File::Spec -> catfile(qw(blib lib), split(/::/, $package)) . ".pm";
my $content;
open my $fh, "<", $filename or die "Unable to read file: $!\n";
{ local($/); $content = <$fh>; }
close $fh;
my $destination;
my $errors = IO::String -> new;
my $counter = __PACKAGE__::Counter -> new;
eval {
local(@ARGV) = ( );
Perl::Tidy::perltidy(
source => \$content,
destination => \$destination,
formatter => $counter,
errorfile => $errors,
);
};
#print "error tidying file: $@\n" if $@;
#print "$package: ", Data::Dumper -> Dump([$counter]);
my $errstr = ${$errors -> string_ref};
#print "Errors:\n$errstr\n" if $errstr ne '';
$totals -> combine($counter);
my @depends = $dep -> source -> item($package) -> depends;
my $outfile = $package;
$outfile =~ s{::}{-}g;
$outfile = lc $outfile;
open $fh, ">", File::Spec -> catfile('report', $outfile . ".html") or next;
print $fh <<EOHTML;
<html>
<head>
<title>CRC for $package</title>
</head>
<body>
<h1>CRC for $package</h1>
EOHTML
if(@depends) {
print $fh "<p>This module depends on the following modules: ";
foreach my $d (@depends) {
my $f = $d;
$f =~ s{::}{-}g;
$f = lc $f;
print $fh qq(<a href="${f}.html">$d</a> );
}
print $fh "</p>\n";
}
else {
print $fh "<p>This module does not depend on any other modules in this project.</p>\n";
}
my $total = 0;
$total += $_ for values %$counter;
print $fh <<EOHTML;
<table border="1">
<tr><td>Line Type</td><td>Count</td></tr>
<tr><td>Code</td><td>@{[$$counter{CODE} || '-']}</td></tr>
<tr><td>Comments</td><td>@{[$$counter{COMMENT} || '-']}</td></tr>
<tr><td>Documentation</td><td>@{[$$counter{POD} || '-']}</td></tr>
<tr><td>Internal Data</td><td>@{[$$counter{HERE} || '-']}</td></tr>
<tr><td>Total</td><td>$total</td></tr>
</table>
EOHTML
my $method_html = '';
my $overall_passed = 0;
my $overall_total = 0;
my %docs;
my $pack_file = File::Spec -> catfile(qw(blib lib), split(/::/, $package));
$pack_file .= ".pm";
my $coverage = $pod_coverage -> {$package};
my @covered = @{$coverage -> {covered} || []};
my @uncovered = @{$coverage -> {uncovered} || []};
@docs{@covered} = ('Y') x scalar(@covered);
@docs{@uncovered} = ('N') x scalar(@uncovered);
my $method_src = __PACKAGE__::MyXMLSource -> new( qw(dependence method), (
$root -> findnodes('module[@name="' . $package . '"]/interface/method')
) );
my $method_dep;
$method_dep = Algorithm::Dependency::Ordered -> new(
source => $method_src,
ignore_orphans => 1,
) if $method_src;
if($method_dep) {
my @parallel_methods = $self -> _parallel_deps($method_dep);
my $filename = $package;
$filename =~ s{::}{-}g;
$filename = lc $filename;
print $fh <<EOHTML;
<h2>Dependency Ranking</h2>
<table border="1">
<tr><td>Rank</td><td>Methods</td></tr>
EOHTML
for(my $rank = 0; $rank <= $#parallel_methods; $rank++) {
print $fh "<tr><td>$rank</td><td>";
foreach my $method (sort @{$parallel_methods[$rank]||[]}) {
my $total = $test_results -> {$package} -> {methods} -> {$method} -> {total};
my $passed = $test_results -> {$package} -> {methods} -> {$method} -> {passed};
my $color = "red";
if(defined $total && defined $passed && $total > 0) {
if($passed == $total) {
$color = "green";
}
elsif(3 * $passed >= 2*$total) {
$color = "gold";
}
elsif(3 * $passed >= $total) {
$color = "orange";
}
}
print $fh qq{<font color="$color">$method</font> };
}
#print $fh join(" ", sort @{$parallel_methods[$rank]||[]});
print $fh "</td></tr>\n";
}
print $fh <<EOHTML;
</table>
EOHTML
}
print $fh "<h2>Modules</h2>\n";
my $methods = [ ];
$methods = $method_dep->schedule_all if $method_dep;
my(@constructors) = $root -> findnodes('module[@name="' . $package . '"]/interface/constructor');
my %seen_methods;
if(@constructors) {
my @cons_methods;
foreach my $c (@constructors) {
my $method = $c -> getAttribute('method') || 'new';
push @cons_methods, $method;
}
my %tcm = map { $_ => undef } @cons_methods;
@constructors = grep { exists $tcm{$_} && ++$tcm{$_} } @{$methods};
push @constructors, grep { exists($tcm{$_}) && !defined($tcm{$_}) } keys %tcm;
my($html, $passed, $total) = $self -> _method_html("Constructors", \@constructors, \%docs, $test_results -> {$package} -> {methods}, $method_dep);
$seen_methods{$_}++ for @constructors;
$method_html .= $html;
$overall_passed += $passed;
$overall_total += $total;
}
my @public_methods = grep { !m{^_} && !$seen_methods{$_} } @$methods;
if(@public_methods) {
my($html, $passed, $total) = $self -> _method_html('Public Methods', \@public_methods, \%docs, $test_results -> {$package} -> {methods}, $method_dep);
$seen_methods{$_}++ for @public_methods;
$method_html .= $html;
$overall_passed += $passed;
$overall_total += $total;
}
my @private_methods = grep { m{^_} && !$seen_methods{$_} } (
@$methods,
keys %{$test_results -> {$package} -> {methods}||{}},
);
if(@private_methods) {
my($html, $passed, $total) = $self -> _method_html('Private Methods', \@private_methods, \%docs, $test_results -> {$package} -> {methods}, $method_dep);
$seen_methods{$_}++ for @private_methods;
$method_html .= $html;
$overall_passed += $passed;
$overall_total += $total;
}
if($overall_total) {
$overall_tests{$package} = [ $overall_passed, $overall_total ];
}
print $fh "<p>Total passed tests: $overall_passed / $overall_total</p>\n";
print $fh <<EOHTML if $method_html;
<table border="1">
$method_html
</table>
EOHTML
print $fh <<EOHTML;
</body>
</html>
EOHTML
close $fh;
}
open my $fh, ">", File::Spec -> catfile(qw(report index.html)) or die "Unable to create CRC index: $!\n";
my $total = 0;
$total += $_ for values %$totals;
my($overall_passed, $overall_total) = (0, 0);
foreach my $package (keys %overall_tests) {
$overall_passed += $overall_tests{$package}[0];
$overall_total += $overall_tests{$package}[1];
}
my $overall_percentage = '-';
if($overall_total) {
$overall_percentage = (int($overall_passed * 10000 / $overall_total) / 100) . '%';
}
else {
$overall_total = '-';
$overall_passed = '-';
}
print $fh <<EOHTML;
<html>
<head>
<title>CRC</title>
</head>
<body>
<h1>CRC Reports</h1>
<table border="1">
<tr><td>Line Type</td><td>Count</td></tr>
<tr><td>Code</td><td>$$totals{CODE}</td></tr>
<tr><td>Comments</td><td>$$totals{COMMENT}</td></tr>
<tr><td>Documentation</td><td>$$totals{POD}</td></tr>
<tr><td>Internal Data</td><td>$$totals{HERE}</td></tr>
<tr><td>Total</td><td>$total</td></tr>
</table>
<p>Passing tests: $overall_passed / $overall_total ($overall_percentage)</p>
<!-- table border=0>
<tr><td><embed src="module_deps.svg" width="290px" height="150px" /></td></tr>
<tr><td align="center"><a href="module_deps.svg">Larger View</a></td></tr>
</table -->
<!-- img src="module_deps.png"/ -->
<h2>Dependency Ranking</h2>
<table border="1">
<tr><td>Rank</td><td>Modules</td></tr>
EOHTML
for(my $rank = 0; $rank <= $#parallel_modules; $rank++) {
print $fh "<tr><td>$rank</td><td>";
foreach my $d (sort @{$parallel_modules[$rank]||[]}) {
my $f = $d;
$f =~ s{::}{-}g;
$f = lc $f;
print $fh qq(<a href="${f}.html">$d</a> );
}
print $fh "</td></tr>\n";
}
print $fh <<EOHTML;
</table>
<h2>Modules</h2>
<table border="1">
<tr><td>Module</td><td>Compiles</td><td>Dependence</td></tr>
EOHTML
foreach my $p (@$schedule) {
my $filename = $p;
$filename =~ s{::}{-}g;
$filename = lc $filename;
print $fh qq(<tr><td><a href="${filename}.html">$p</a></td>\n<td>);
print $fh (defined $test_results -> {$p} -> {compile_ok})
? ($test_results -> {$p} -> {compile_ok} ? 'Y' : 'N')
: '-';
print $fh "</td>\n<td>";
my @depends = $dep -> source -> item($p) -> depends;
foreach my $d (@depends) {
my $f = $d;
$f =~ s{::}{-}g;
$f = lc $f;
print $fh qq(<a href="${f}.html">$d</a> );
}
print $fh "</td></tr>\n";
}
print $fh <<EOHTML;
</table>
</body>
</html>
EOHTML
close $fh;
}
sub _method_html {
my($self, $title, $methods, $docs, $tests, $method_dep) = @_;
my %seen;
my $html = <<1HERE1;
<tr><td colspan="5" align="center"><strong>$title</strong></td></tr>
<tr><td>Method</td><td>Documented</td><td>Tests Failed</td><td>Total Tests</td><td>Dependencies</td></tr>
1HERE1
my($overall_passed, $overall_total) = (0, 0);
foreach my $method (@$methods) {
next if $seen{$method}++;
my $doc = $docs->{$method};
$doc = '-' unless defined $doc;
my $passed_tests = $tests -> {$method} -> {passed};
my $total_tests = $tests -> {$method} -> {total};
$passed_tests = '-' unless defined $passed_tests;
$total_tests = '-' unless defined $total_tests;
$overall_passed += $passed_tests if $passed_tests =~ m{^\s*\d+\s*$};
$overall_total += $total_tests if $total_tests =~ m{^\s*\d+\s*$};
my $diff = ($total_tests =~ m{^\s*\d+\s*$} ? $total_tests : 0)
- ($passed_tests =~ m{^\s*\d+\s*$} ? $passed_tests : 0);
$html .= " <tr><td>$method</td><td>$doc</td><td>$diff</td><td>$total_tests</td><td>";
if(defined $method_dep && defined $method_dep -> source -> item($method)) {
$html .= join(" ", $method_dep -> source -> item($method) -> depends);
}
$html .= "</td></tr>\n";
}
return($html, $overall_passed, $overall_total);
}
sub _parallel_deps {
my($self, $dep) = @_;
my $schedule = $dep -> schedule_all || [];
my %distances;
@distances{@$schedule} = ( 0 ) x scalar(@$schedule);
foreach my $item (@$schedule) {
my @ds = sort { $b <=> $a } map { $distances{$_} || 0 } ($dep -> source -> item($item) -> depends);
#print "ds: ", join(" ", @ds), "\n";
$distances{$item} = $ds[0] + 1 if @ds;
}
my @rows;
foreach my $item (@$schedule) {
$rows[$distances{$item}] ||= [ ];
push @{$rows[$distances{$item}]}, $item;
}
return @rows;
}
sub ACTION_graph {
my $self = shift;
#$self -> depends_on('test');
require Algorithm::Dependency::Ordered;
require Graph::Directed;
my $test_results = $self -> notes('test_results');
#print "test_results: $test_results\n";
if(!ref $test_results) {
$self -> depends_on('test');
$test_results = $self -> notes('test_results');
}
my($doc, $spec_root) = $self -> _get_deps_dom;
my $source = __PACKAGE__::MyXMLSource -> new( qw(dependence module), $spec_root -> findnodes('module') );
my $dep = Algorithm::Dependency::Ordered -> new(
source => $source,
ignore_orphans => 1,
);
my $schedule = $dep -> schedule_all;
my %doms;
my %scores;
foreach my $module (@$schedule) {
my $method_src = __PACKAGE__::MyXMLSource -> new( qw(dependence method), (
$spec_root -> findnodes('module[@name="' . $module . '"]/interface/method')
) );
my $method_dep;
$method_dep = Algorithm::Dependency::Ordered -> new(
source => $method_src,
ignore_orphans => 1,
) if $method_src;
next unless $method_dep;
my $filename = $module;
$filename =~ s{::}{-}g;
$filename = lc $filename;
my $graph = $self -> _make_graph($method_dep);
next unless $graph;
# we want to put in a little window for each method
foreach my $method ($graph -> vertices) {
my $id = "${module}::${method}";
$id =~ s{::}{_}g;
$id = lc $id;
$graph -> set_attribute('id', $method, $id);
my $total = $test_results -> {$module} -> {methods} -> {$method} -> {total};
my $passed = $test_results -> {$module} -> {methods} -> {$method} -> {passed};
my $color = "red";
$scores{$module}{'methods'}++;
if(defined $total && defined $passed && $total > 0) {
if($passed == $total) {
$color = "green";
$scores{$module}{'colors'} += 3;
}
elsif(3 * $passed >= 2*$total) {
$color = "gold";
$scores{$module}{'colors'} += 2;
}
elsif(3 * $passed >= $total) {
$color = "orange";
$scores{$module}{'colors'} += 1;
}
}
$graph -> set_attribute('color', $method, $color);
}
my $svg_dom = XML::LibXML -> createDocument;
$self -> _make_svg($graph, $svg_dom);
my @svgs;
foreach my $method ($graph -> vertices) {
my $id = $graph -> get_attribute('id', $method);
my $g = $svg_dom -> createElement('g');
push @svgs, $g;
$g -> setAttribute(id => "text_$id");
$g -> setAttribute(style => 'visibility:hidden');
#$g -> setAttribute(x => 50);
#$g -> setAttribute(y => $svg_dom -> documentElement -> getAttribute('height') + 20);
my $animate = $svg_dom -> createElement('animate');
$animate -> setAttribute(begin => "vert_${id}.click");
$animate -> setAttribute(attributeName => 'visibility');
$animate -> setAttribute(from => 'hidden');
$animate -> setAttribute(to => 'visible');
$animate -> setAttribute(fill => 'freeze');
$animate -> setAttribute(dur => '0.1s');
$g -> appendChild($animate);
my $rectangle = $svg_dom -> createElement('rect');
$rectangle -> setAttribute(id => "rect_$id");
$rectangle -> setAttribute('stroke-width' => 3);
$rectangle -> setAttribute('stroke' => 'black');
$rectangle -> setAttribute('fill' => 'white');
$rectangle -> setAttribute('rx' => 20);
$animate = $svg_dom -> createElement('animate');
$animate -> setAttribute(begin => "rect_${id}.click");
$animate -> setAttribute(attributeName => 'visibility');
$animate -> setAttribute(from => 'visible');
$animate -> setAttribute(to => 'hidden');
$animate -> setAttribute(fill => 'freeze');
$animate -> setAttribute(dur => '0.1s');
$g -> appendChild($animate);
my @texts = (
qq{method: $method},
qq{tests passed: } . ($test_results -> {$module} -> {methods} -> {$method} -> {passed} || '0'),
qq{ total tests: } . ($test_results -> {$module} -> {methods} -> {$method} -> {total} || '0'),
);
my $max_w = (sort { $b <=> $a } map { length($_) } @texts)[0];
my $svg = $svg_dom -> createElement('svg');
$svg -> setAttribute(x => 50);
$svg -> setAttribute(y => $svg_dom -> documentElement -> getAttribute('height') + 20);
$svg -> setAttribute('width' => 7 * $max_w + 30);
$svg -> setAttribute('height' => 55);
$g -> appendChild($svg);
$svg -> appendChild($rectangle);
$rectangle -> setAttribute(x => 3);
$rectangle -> setAttribute(y => 3);
$rectangle -> setAttribute('width' => $svg -> getAttribute('width') - 6);
$rectangle -> setAttribute('height' => $svg -> getAttribute('height') - 6);
my $text = $svg_dom -> createElement('text');
$text -> setAttribute(x => 15);
$text -> setAttribute(dy => 10);
$svg -> appendChild($text);
foreach my $t (@texts) {
my $tspan = $svg_dom -> createElement('tspan');
$tspan -> appendText($t);
$tspan -> setAttribute(x => 15);
$tspan -> setAttribute(dy => 15);
$text -> appendChild($tspan);
}
}
$doms{$module} = [ $svg_dom -> documentElement, @svgs ];
}
my $graph = $self -> _make_graph($dep);
foreach my $v ($graph -> vertices) {
my $id = $v;
$id =~ s{::}{_}g;
$id = lc $id;
$graph -> set_attribute('id', $v, $id);
my $total = $scores{$v}{'methods'};
my $passed = $scores{$v}{'colors'};
my $color = "red";
if(defined $total && defined $passed && $total > 0) {
if($passed == 3*$total) {
$color = "green";
}
elsif($passed >= 2*$total) {
$color = "gold";
}
elsif($passed >= $total) {
$color = "orange";
}
}
$graph -> set_attribute('color', $v, $color);
}
# put in a little window for each module
my $svg_dom = XML::LibXML -> createDocument;
$svg_dom -> setStandalone(0);
$svg_dom -> createInternalSubset('svg', '-//W3C//DTD SVG 20010904//EN', 'http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd');
$self -> _make_svg($graph, $svg_dom);
my $root = $svg_dom -> documentElement;
$root -> removeAttribute('width');
$root -> removeAttribute('height');
foreach my $module (keys %doms) {
my $g = $svg_dom -> createElement('g');
my $id = $graph -> get_attribute('id', $module);
if(!defined $id) {
$id = lc $id;
$id =~ s{::}{_}g;
}
$g -> setAttribute(id => "svg_$id");
$g -> setAttribute(style => 'visibility:hidden');
my $animate = $svg_dom -> createElement('animate');
$animate -> setAttribute(begin => "vert_${id}.click");
$animate -> setAttribute(attributeName => 'visibility');
$animate -> setAttribute(from => 'hidden');
$animate -> setAttribute(to => 'visible');
$animate -> setAttribute(fill => 'freeze');
$animate -> setAttribute(dur => '0.1s');
$g -> appendChild($animate);
my $rectangle = $svg_dom -> createElement('rect');
$rectangle -> setAttribute(id => "rect_$id");
$rectangle -> setAttribute('stroke-width' => 3);
$rectangle -> setAttribute('stroke' => 'black');
$rectangle -> setAttribute('fill' => 'white');
$rectangle -> setAttribute('rx' => 20);
$animate = $svg_dom -> createElement('animate');
$animate -> setAttribute(begin => "rect_${id}.click");
$animate -> setAttribute(attributeName => 'visibility');
$animate -> setAttribute(from => 'visible');
$animate -> setAttribute(to => 'hidden');
$animate -> setAttribute(fill => 'freeze');
$animate -> setAttribute(dur => '0.1s');
$g -> appendChild($animate);
my $svg = shift @{$doms{$module} || []};
next unless $svg;
$svg_dom -> adoptNode($svg);
#$rectangle -> setAttribute(height => '100%'); # $svg -> getAttribute('height'));
#$rectangle -> setAttribute(width => '100%'); # $svg -> getAttribute('width'));
#$rectangle -> setAttribute(x => 0);
#$rectangle -> setAttribute(y => 0);
$rectangle -> setAttribute(x => 3);
$rectangle -> setAttribute(y => 3);
$rectangle -> setAttribute('width' => $svg -> getAttribute('width') - 6);
$rectangle -> setAttribute('height' => $svg -> getAttribute('height') - 6);
$svg -> insertBefore($rectangle, undef);
$g -> appendChild($svg);
$root -> appendChild($g);
foreach my $d (@{$doms{$module} || []}) {
$svg_dom -> adoptNode($d);
$root -> appendChild($d);
}
}
my $file = File::Spec -> catfile(qw(graph));
$svg_dom -> toFile($file . '.svg', 1);
}
sub _parse_arguments {
my $self = shift;
my $node = shift;
my @arguments;
foreach my $child ( $node -> childNodes ) {
#print "Node: ", $child -> localname, " Type: ", $child -> nodeType, "\n";
my $key = '';
$key = $child -> getAttribute('key') if $child -> can('getAttribute');
$key =~ s{\|}{\\\|}gm;
push @arguments, ($key ne '' ? "q|$key|" : ()), $self -> _parse_argument_node($child);
}
return join(", ", grep { defined && $_ ne '' } @arguments);
}
sub _parse_argument_node {
my $self = shift;
my $child = shift;
my $localname = $child -> localname;
$localname = '' unless defined $localname;
if($localname eq 'nil') {
return 'undef';
}
elsif($localname eq 'regex') {
my $text = $child -> textContent;
$text =~ s{^\s*\n}{}m;
$text =~ s{\n\s*$}{}m;
#$text =~ s{\|}{\\\|}gm;
$text =~ s/([\\|])/\\$1/gm;
return 'qr|'.$text.'|';
}
elsif($localname eq 'string') {
my $text = $child -> textContent;
$text =~ s{^\s*\n}{}m;
$text =~ s{\n\s*$}{}m;
#$text =~ s{\|}{\\\|}gm;
$text =~ s/([\\|])/\\$1/gm;
return 'q|'.$text.'|';
return "q|$text|";
}
elsif($localname eq 'list') {
return "[ " . $self -> _parse_arguments($child) . " ]";
}
elsif($localname eq 'association') {
my @values;
foreach my $pair ($child -> childNodes) {
my $key = $pair -> getAttribute('key');
$key =~ s{\|}{\\\|}gm;
push @values, "q|$key| => " . $self -> _parse_argument_node($pair);
}
return "{ " . join(", ", @values) . " }";
}
}
sub _inv_deps {
my($self, $dep) = @_;
my $schedule = $dep -> schedule_all;
my %v;
foreach my $item (@$schedule) {
foreach my $d ($dep -> source -> item($item) -> depends) {
push @{$v{$d} ||= []}, $item;
}
}
return \%v;
}
sub _make_graph {
my($self, $dep) = @_;
my $graph = Graph::Directed -> new;
my @rows = $self -> _parallel_deps($dep);
my %prev_row;
my %no_edges;
for(my $rank = 0; $rank <= $#rows; $rank++) {
my $row = $rows[$rank];
for(my $file = 0; $file <= $#$row; $file++) {
my $item = $row -> [$file];
$graph -> add_vertex($item);
$graph -> set_attribute('layout_pos1', $item, $rank);
foreach my $d ($dep -> source -> item($item) -> depends) {
$graph -> add_edge($d, $item);
}
}
}
my $inv_dep = $self -> _inv_deps($dep);
foreach my $v (keys %$inv_dep) {
next unless @{$inv_dep->{$v} || []};
my @xs = sort { $a <=> $b } map { $graph -> get_attribute('layout_pos1', $_) } @{$inv_dep->{$v}};
#print "Moving $v from col ", $graph -> get_attribute('layout_pos1', $v), " to col ", $xs[0] - 1, "\n";
$graph -> set_attribute('layout_pos1', $v, $xs[0] - 1);
}
my @sinks = $graph -> sink_vertices;
my %seen;
my %taken;
for(my $pos = 0; $pos <= $#sinks; $pos++) {
$seen{$sinks[$pos]} ++;
$graph -> set_attribute('layout_pos2', $sinks[$pos], $pos);
$graph -> set_attribute('weight', $sinks[$pos], 1);
$taken{$graph -> get_attribute('layout_pos1', $sinks[$pos]) . '.' . $pos} = 1;
}
my @vertices = grep { !$seen{$_} } map { $graph -> neighbors($_) } @sinks;
while(@vertices) {
my $v = shift @vertices;
next if $seen{$v}++;
my @right = grep { $seen{$_} } $graph -> neighbors($v);
push @vertices, grep { !$seen{$_} } $graph -> neighbors($v);
my $minx = $#rows;
my $total_y = 0;
my $total_weight = 0;
my($x, $y);
if(grep { !defined $graph -> get_attribute('layout_pos2', $_) } @right) {
push @vertices, $v;
next;
}
foreach my $r (@right) {
$x = $graph -> get_attribute('layout_pos1', $r);
$y = $graph -> get_attribute('layout_pos2', $r);
$minx = $x if $minx > $x;
$total_y += $y * ($graph -> get_attribute('weight', $r) || 1);
$total_weight += $graph -> get_attribute('weight', $r) || 1;
}
$x = $graph -> get_attribute('layout_pos1', $v);
$y = int(($total_y + $total_weight/2) / ($total_weight || 1));
if($taken{$x . '.' . $y}) {
my $o = 1;
$o ++ while($taken{$x . '.' . ($y+$o)} && $taken{$x . '.' . ($y-$o)});
if(!$taken{$x . '.' . ($y-$o)}) {
$y = $y - $o;
}
elsif(!$taken{$x . '.' . ($y + $o)}) {
$y = $y + $o;
}
else { warn "Oops!: $v - $x, $y\n" }
}
$taken{$x . '.' . $y} = 1;
$graph -> set_attribute('layout_pos1', $v, $x);
$graph -> set_attribute('layout_pos2', $v, $y);
$graph -> set_attribute('weight', $v, $total_weight);
}
@vertices = @{$rows[0] || []};
#@vertices = ( );
%seen = ( );
while(@vertices) {
my $v = shift @vertices;
next if $seen{$v}++;
#if($v =~ m{::}) { print "rechecking location of $v:\n"; }
my $x = $graph -> get_attribute('layout_pos1', $v);
my $y = $graph -> get_attribute('layout_pos2', $v);
next if defined $y;
#push @vertices, grep { $graph -> get_attribute('layout_pos1', $_) >= $x } $graph -> neighbors($v);
#my @successors = grep { $graph -> get_attribute('layout_pos1', $_) < $x } $graph -> neighbors($v);
#next unless @successors;
#my @xs = sort { $b <=> $a } map { $graph -> get_attribute('layout_pos1', $_) } @successors;
#my $new_x = $xs[0] + 1;
#next if $new_x >= $x;
delete $taken{"${x}.${y}"} if defined $y;
$y = 0 if !defined $y;
my $old_y = $y;
my $old_x = $x;
#$x = $new_x;
if($taken{$x . '.' . $y}) {
my $o = 1;
$o ++ while($taken{$x . '.' . ($y+$o)} && $taken{$x . '.' . ($y-$o)});
if(!$taken{$x . '.' . ($y-$o)}) {
$y = $y - $o;
}
elsif(!$taken{$x . '.' . ($y + $o)}) {
$y = $y + $o;
}
else { warn "Oops!: $v - $x, $y\n" }
}
$taken{$x . '.' . $y} = 1;
#print " moving from ($old_x, $old_y) to ($x, $y)\n" if $v =~ m{::};
$graph -> set_attribute('layout_pos1', $v, $x);
$graph -> set_attribute('layout_pos2', $v, $y);
}
my($min_x, $min_y, $max_x, $max_y);
foreach my $v ($graph -> vertices) {
my($x, $y) = (
$graph -> get_attribute('layout_pos1', $v),
$graph -> get_attribute('layout_pos2', $v)
);
#print "$v: $x, $y\n";
$min_x = $x if !defined($min_x) || $x < $min_x;
$min_y = $y if !defined($min_y) || $y < $min_y;
$max_x = $x if !defined($max_x) || $x > $max_x;
$max_y = $y if !defined($max_y) || $y > $max_y;
}
#print "Rectangle: ($min_x, $min_y) -> ($max_x, $max_y)\n";
$graph -> set_attribute('layout_min1', $min_x);
$graph -> set_attribute('layout_min2', $min_y);
$graph -> set_attribute('layout_max1', $max_x);
$graph -> set_attribute('layout_max2', $max_y);
return $graph;
}
sub _make_svg {
my($self, $graph, $svg_dom) = @_;
my $root = $svg_dom -> createElement(
'svg'
);
$root -> setNamespace('http://www.w3.org/1999/xlink', 'xlink', 0);
$root -> setNamespace('http://www.w3.org/2000/svg');
$svg_dom -> setDocumentElement($root);
#my $min_x = $graph -> get_attribute('layout_min1');
$root -> setAttribute(height => ($graph -> get_attribute('layout_max2') - $graph -> get_attribute('layout_min2')) * 25 + 50);
$root -> setAttribute(width => ($graph -> get_attribute('layout_max1') - $graph -> get_attribute('layout_min1')) * 120 + 120);
#$root -> setAttribute(height => 'auto');
#$root -> setAttribute(width => 'auto');
#$root -> setAttribute(viewBox => join(" ",
# $graph -> get_attribute('layout_min1') * 100 + 100,
# $graph -> get_attribute('layout_min2') * 20 - 20,
# $graph -> get_attribute('layout_max1') * 100 + 200,
# $graph -> get_attribute('layout_max2') * 20 + 40,
#));
$root -> setAttribute(version => '1.1');
my @edges = $graph -> edges;
my($u, $v);
while(($u, $v) = splice @edges, 0, 2) {
my $line = $self -> _edge($svg_dom, $graph, $u, $v);
$line -> setAttribute(#'http://www.w3.org/2000/svg',
stroke => 'grey');
$line -> setAttribute(#'http://www.w3.org/2000/svg',
'stroke-width' => '1');
$root -> appendChild($line);
}
#my $defs = $svg_dom -> createElement('defs');
#$root -> appendChild($defs);
my @labels;
my @paths;
my @vertices;
foreach my $v ($graph -> vertices) {
push @paths, $self -> _path($svg_dom, $graph, $v);
my($circle, $label) = $self -> _vertex($svg_dom, $graph, $v);
push @labels, $label;
push @vertices, $circle;
#$root -> appendChild($circle);
}
$root -> appendChild($_) for @labels;
$root -> appendChild($_) for @paths;
$root -> appendChild($_) for @vertices;
}
sub _path {
my($self, $dom, $graph, $v) = @_;
#print "Path for $v:\n";
my $path = $dom -> createElement(
'g'
);
$path -> setAttribute(style => 'visibility:hidden;');
my $id = $graph -> get_attribute('id', $v);
if(!defined $id) {
$id = $v;
$id =~ s{::}{_}g;
$id = lc $id;
}
my $animate = $dom -> createElement('animate');
$animate -> setAttribute(begin => "vert_${id}.mouseover");
$animate -> setAttribute(attributeName => 'visibility');
$animate -> setAttribute(from => 'hidden');
$animate -> setAttribute(to => 'visible');
$animate -> setAttribute(fill => 'freeze');
$animate -> setAttribute(dur => '0.1s');
$path -> appendChild($animate);
$animate = $dom -> createElement('animate');
$animate -> setAttribute(begin => "vert_${id}.mouseout");
$animate -> setAttribute(attributeName => 'visibility');
$animate -> setAttribute(from => 'visible');
$animate -> setAttribute(to => 'hidden');
$animate -> setAttribute(fill => 'freeze');
$animate -> setAttribute(dur => '0.1s');
$path -> appendChild($animate);
$path -> setAttribute(id => "path_$id");
my @edges = $self -> _edges($dom, $graph, $v);
foreach (@edges) {
$_ -> setAttribute(#'http://www.w3.org/2000/svg',
stroke => 'black');
$_ -> setAttribute(#'http://www.w3.org/2000/svg',
'stroke-width' => '2');
}
$path -> appendChild($_) foreach @edges;
return $path;
}
sub _edges {
my($self, $dom, $graph, $v) = @_;
my @edges = ( );
#print " $v\n";
my $x = $graph -> get_attribute('layout_pos1', $v);
my @vertices = grep { $graph -> get_attribute('layout_pos1', $_) < $x } $graph -> neighbors($v);
push @edges, $self -> _edge($dom, $graph, $v, $_) foreach @vertices;
push @edges, $self -> _edges($dom, $graph, $_) foreach @vertices;
return @edges;
}
sub _edge {
my($self, $dom, $graph, $u, $v) = @_;
my($fromx, $fromy, $tox, $toy) = (
$graph -> get_attribute('layout_pos1', $u) - $graph -> get_attribute('layout_min1') + .25,
$graph -> get_attribute('layout_pos2', $u) - $graph -> get_attribute('layout_min2') + 1.5,
$graph -> get_attribute('layout_pos1', $v) - $graph -> get_attribute('layout_min1') + .25,
$graph -> get_attribute('layout_pos2', $v) - $graph -> get_attribute('layout_min2') + 1.5,
);
my $line = $dom -> createElement(
#'http://www.w3.org/2000/svg',
'line',
);
$line -> setAttribute(#'http://www.w3.org/2000/svg',
x1 => $fromx * 100);
$line -> setAttribute(#'http://www.w3.org/2000/svg',
y1 => $fromy * 20);
$line -> setAttribute(#'http://www.w3.org/2000/svg',
x2 => $tox * 100);
$line -> setAttribute(#'http://www.w3.org/2000/svg',
y2 => $toy * 20);
my $width = $graph -> get_attribute('weight', $v); # + $graph -> get_attribute('weight', $v);
$width = int(log($width) / log(2.) + 0.5) if $width > 0;
#$width -= 2;
$width = 1 if $width < 1;
#$line -> setAttribute(style => "stroke-width:$width;");
return $line;
}
sub _vertex {
my($self, $dom, $graph, $vertex) = @_;
my($x, $y, $color, $label, $link_ref) = (
map { $graph -> get_attribute($_, $vertex) } qw(
layout_pos1
layout_pos2
color
label
link
)
);
$x -= $graph -> get_attribute('layout_min1') - .25;
$y -= $graph -> get_attribute('layout_min2') - 1.5;
$label = $vertex unless defined $label;
$color = 'red' unless defined $color;
my $file_ref = lc $label;
$file_ref =~ s{::}{-}g;
my $id = $graph -> get_attribute('id', $vertex);
if(!defined $id) {
$id = lc $label;
$id =~ s{::}{_}g;
}
my $label_ref = "vert_$id";
my $link;
if(defined $link_ref) {
$link = $dom -> createElement(
#'http://www.w3.org/2000/svg',
'a'
);
$link -> setAttribute('xlink:href' => $link_ref);
}
else {
$link = $dom -> createElement(
'g'
);
}
$link -> setAttribute(id => $label_ref);
my $circle = $dom -> createElement(
#'http://www.w3.org/2000/svg',
'circle'
);
$circle -> setAttribute(cx => $x * 100);
$circle -> setAttribute(cy => $y * 20);
$circle -> setAttribute(r => 8);
$circle -> setAttribute(#'http://www.w3.org/2000/svg',
fill => 'black');
$link -> appendChild($circle);
$circle = $dom -> createElement(
#'http://www.w3.org/2000/svg',
'circle'
);
$circle -> setAttribute(cx => $x * 100);
$circle -> setAttribute(cy => $y * 20);
$circle -> setAttribute(r => 7);
$circle -> setAttribute(#'http://www.w3.org/2000/svg',
fill => $color);
$link -> appendChild($circle);
my $text = $dom -> createElement(
#'http://www.w3.org/2000/svg',
'text'
);
$text -> setAttribute(x => $x * 100 + 6);
$text -> setAttribute(y => $y * 20 - 6);
$text -> setAttribute('text-anchor' => 'start');
$text -> setAttribute(fill => 'blue');
#$text -> setAttribute(id => $label_ref);
$text -> appendTextNode( $label );
return( $link, $text);
}
{
my $DOM;
my $M;
sub _get_deps_dom {
require XML::LibXML;
require Pod::Coverage;
my $doc;
my $parser = XML::LibXML -> new;
my $root;
if($DOM) {
$doc = $DOM;
$root = $doc -> documentElement;
return ($doc, $root);
}
if(-e 'deps.xml') {
$M = -M _;
$doc = $parser -> parse_file('deps.xml');
$root = $doc -> documentElement;
foreach my $module ( map { $_ -> getAttribute('name') } $root -> findnodes('module')) {
push @ARGV, $module;
}
}
else {
$M = undef;
$doc = XML::LibXML->createDocument;
$root = $doc -> createElement('specification');
$doc -> setDocumentElement($root);
}
$DOM = $doc;
return($doc, $root);
}
sub _is_modified_after_deps {
return 1 unless defined $M;
my($self, $file) = @_;
return $M > -M $file;
}
}
{
my @result;
my %tests;
sub begin_tests {
my($self, $m) = @_;
my $tester = Test::More -> builder;
$tests{$m} ||= [];
push @{$tests{$m}}, $tester -> current_test;
}
sub end_tests {
my($self, $m) = @_;
my $tester = Test::More -> builder;
my $t = $tests{$m};
my $first = $t -> [$#$t];
my $last = $tester -> current_test - 1;
if(!defined $first || !defined $last || $last < $first) {
pop @$t;
}
else {
while(++$first <= $last) {
push @$t, $first;
}
}
}
sub tests {
my($self, $m) = @_;
return @{$tests{$m}||[]};
}
sub tested_methods { return keys %tests; }
sub record_test_details {
my($self) = shift;
my($module) = shift;
my $tester = Test::More -> builder;
my @details = $tester -> details;
my $results = $self -> notes('test_results');
my $test_results = $results -> {$module} -> {'methods'} ||= {};
foreach my $method ($self -> tested_methods()) {
my @passed = grep { $details[$_] -> {actual_ok} }
$self -> tests($method);
my $total = scalar($self -> tests($method));
$test_results -> {$method} = {
total => $total,
passed => scalar(@passed),
};
}
$self -> notes(test_results => $results);
}
}
{
package __PACKAGE__::Counter;
sub new { bless { } => $_[0] };
sub write_line {
my ( $self, $line_of_tokens ) = @_;
my $line_type = $line_of_tokens->{_line_type};
if($line_of_tokens -> {_line_text} =~ m{^\s*#}) {
$line_type = 'COMMENT';
}
elsif($line_of_tokens -> {_line_text} =~ m{^\s*$}) {
$line_type = 'BLANK';
}
$self -> {$line_type} ++;
}
sub combine {
my ($self, $other) = @_;
foreach my $k (keys %$other) {
$self -> {$k} += $other -> {$k};
}
}
}
{
package __PACKAGE__::MyXMLSource;
use Algorithm::Dependency::Source;
our @ISA = qw(Algorithm::Dependency::Source);
sub new {
my $class = shift;
#my $dom = shift;
my $self = $class -> SUPER::new() or return undef;
$self -> {dep_name} = shift;
$self -> {attr_name} = shift;
return undef unless @_;
$self -> {item_list} = [ @_ ];
return $self;
}
sub _load_item_list {
my $self = shift;
my $item_list = $self -> {item_list} or return undef;
my @ItemList = ( );
my $last_item;
no warnings;
foreach my $item ( @$item_list ) {
push @ItemList, $last_item = Algorithm::Dependency::Item -> new(
$item -> getAttribute('name'),
(map { $_ -> getAttribute($self ->{attr_name}) }
( grep { $_ -> getAttribute('ignore') ne 'yes' } ($item -> findnodes($self -> {dep_name}))))
);
}
return \@ItemList;
}
}
1;
__END__