| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package App::PrereqGrapher; | 
| 2 |  |  |  |  |  |  | $App::PrereqGrapher::VERSION = '0.14'; | 
| 3 |  |  |  |  |  |  | # | 
| 4 |  |  |  |  |  |  | # ABSTRACT: generate dependency graph using Perl::PrereqScanner | 
| 5 |  |  |  |  |  |  | # | 
| 6 |  |  |  |  |  |  |  | 
| 7 | 1 |  |  | 1 |  | 18119 | use 5.006; | 
|  | 1 |  |  |  |  | 3 |  | 
| 8 | 1 |  |  | 1 |  | 3 | use strict; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 16 |  | 
| 9 | 1 |  |  | 1 |  | 11 | use warnings; | 
|  | 1 |  |  |  |  | 5 |  | 
|  | 1 |  |  |  |  | 21 |  | 
| 10 |  |  |  |  |  |  |  | 
| 11 | 1 |  |  | 1 |  | 4 | use Carp; | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 52 |  | 
| 12 | 1 |  |  | 1 |  | 509 | use Moo; | 
|  | 1 |  |  |  |  | 11126 |  | 
|  | 1 |  |  |  |  | 6 |  | 
| 13 | 1 |  |  | 1 |  | 1737 | use Perl::PrereqScanner; | 
|  | 1 |  |  |  |  | 579545 |  | 
|  | 1 |  |  |  |  | 49 |  | 
| 14 | 1 |  |  | 1 |  | 1014 | use Getopt::Long qw/:config no_ignore_case/; | 
|  | 1 |  |  |  |  | 8304 |  | 
|  | 1 |  |  |  |  | 6 |  | 
| 15 | 1 |  |  | 1 |  | 898 | use Graph::Easy; | 
|  | 1 |  |  |  |  | 82574 |  | 
|  | 1 |  |  |  |  | 52 |  | 
| 16 | 1 |  |  | 1 |  | 474 | use Module::Path qw(module_path); | 
|  | 1 |  |  |  |  | 455 |  | 
|  | 1 |  |  |  |  | 68 |  | 
| 17 | 1 |  |  | 1 |  | 2136 | use Module::CoreList; | 
|  | 1 |  |  |  |  | 32286 |  | 
|  | 1 |  |  |  |  | 12 |  | 
| 18 |  |  |  |  |  |  |  | 
| 19 |  |  |  |  |  |  | my %formats = | 
| 20 |  |  |  |  |  |  | ( | 
| 21 |  |  |  |  |  |  | 'dot'  => sub { $_[0]->as_graphviz; }, | 
| 22 |  |  |  |  |  |  | 'svg'  => sub { $_[0]->as_svg; }, | 
| 23 |  |  |  |  |  |  | 'gml'  => sub { $_[0]->as_graphml; }, | 
| 24 |  |  |  |  |  |  | 'vcg'  => sub { $_[0]->as_vcg; }, | 
| 25 |  |  |  |  |  |  | 'html' => sub { $_[0]->as_html_file; }, | 
| 26 |  |  |  |  |  |  | ); | 
| 27 |  |  |  |  |  |  |  | 
| 28 |  |  |  |  |  |  | has format => ( | 
| 29 |  |  |  |  |  |  | is      => 'ro', | 
| 30 |  |  |  |  |  |  | isa     => sub { croak "valid formats: ", join(", ", keys %formats), "\n"  unless exists $formats{$_[0]}; }, | 
| 31 |  |  |  |  |  |  | default => sub { return 'dot'; }, | 
| 32 |  |  |  |  |  |  | ); | 
| 33 |  |  |  |  |  |  |  | 
| 34 |  |  |  |  |  |  | has output_file => ( | 
| 35 |  |  |  |  |  |  | is  => 'ro', | 
| 36 |  |  |  |  |  |  | ); | 
| 37 |  |  |  |  |  |  |  | 
| 38 |  |  |  |  |  |  | has no_core => ( | 
| 39 |  |  |  |  |  |  | is => 'ro', | 
| 40 |  |  |  |  |  |  | ); | 
| 41 |  |  |  |  |  |  |  | 
| 42 |  |  |  |  |  |  | has no_recurse_core => ( | 
| 43 |  |  |  |  |  |  | is => 'ro', | 
| 44 |  |  |  |  |  |  | ); | 
| 45 |  |  |  |  |  |  |  | 
| 46 |  |  |  |  |  |  | has verbose => ( | 
| 47 |  |  |  |  |  |  | is => 'ro', | 
| 48 |  |  |  |  |  |  | ); | 
| 49 |  |  |  |  |  |  |  | 
| 50 |  |  |  |  |  |  | has depth => ( | 
| 51 |  |  |  |  |  |  | is => 'ro', | 
| 52 |  |  |  |  |  |  | isa => sub { croak "depth must be an integer\n" unless $_[0] =~ /^\d+$/; }, | 
| 53 |  |  |  |  |  |  | ); | 
| 54 |  |  |  |  |  |  |  | 
| 55 |  |  |  |  |  |  | has timeout => ( | 
| 56 |  |  |  |  |  |  | is => 'ro', | 
| 57 |  |  |  |  |  |  | isa => sub { croak "timeout must be an integer\n" unless $_[0] =~ /^\d+$/; }, | 
| 58 |  |  |  |  |  |  | ); | 
| 59 |  |  |  |  |  |  |  | 
| 60 |  |  |  |  |  |  | sub new_with_options | 
| 61 |  |  |  |  |  |  | { | 
| 62 | 0 |  |  | 0 | 0 | 0 | my $class    = shift; | 
| 63 | 0 |  |  |  |  | 0 | my %options  = $class->parse_options(); | 
| 64 | 0 |  |  |  |  | 0 | my $instance = $class->new(%options, @_); | 
| 65 |  |  |  |  |  |  |  | 
| 66 | 0 |  |  |  |  | 0 | return $instance; | 
| 67 |  |  |  |  |  |  | } | 
| 68 |  |  |  |  |  |  |  | 
| 69 |  |  |  |  |  |  | sub parse_options | 
| 70 |  |  |  |  |  |  | { | 
| 71 | 0 |  |  | 0 | 0 | 0 | my $class = shift; | 
| 72 | 0 |  |  |  |  | 0 | my %options; | 
| 73 |  |  |  |  |  |  | my %format; | 
| 74 |  |  |  |  |  |  |  | 
| 75 |  |  |  |  |  |  | GetOptions( | 
| 76 |  |  |  |  |  |  | 'h|help'              => \$options{'help'}, | 
| 77 |  |  |  |  |  |  | 'd|depth=i'           => \$options{'depth'}, | 
| 78 |  |  |  |  |  |  | 't|timeout=i'         => \$options{'timeout'}, | 
| 79 |  |  |  |  |  |  | 'o|output-file=s'     => \$options{'output_file'}, | 
| 80 |  |  |  |  |  |  | 'nc|no-core'          => \$options{'no_core'}, | 
| 81 |  |  |  |  |  |  | 'nrc|no-recurse-core' => \$options{'no_recurse_core'}, | 
| 82 |  |  |  |  |  |  | 'v|verbose'           => \$options{'verbose'}, | 
| 83 |  |  |  |  |  |  | 'dot'                 => \$format{'dot'}, | 
| 84 |  |  |  |  |  |  | 'svg'                 => \$format{'svg'}, | 
| 85 |  |  |  |  |  |  | 'gml'                 => \$format{'gml'}, | 
| 86 |  |  |  |  |  |  | 'vcg'                 => \$format{'vcg'}, | 
| 87 | 0 | 0 |  |  |  | 0 | 'html'                => \$format{'html'}, | 
| 88 |  |  |  |  |  |  | ) || croak "Can't get options."; | 
| 89 | 0 | 0 |  |  |  | 0 | usage() if $options{'help'}; | 
| 90 |  |  |  |  |  |  |  | 
| 91 | 0 |  |  |  |  | 0 | foreach my $format (keys %formats) { | 
| 92 | 0 | 0 |  |  |  | 0 | delete $format{$format} unless $format{$format}; | 
| 93 |  |  |  |  |  |  | } | 
| 94 | 0 | 0 |  |  |  | 0 | if (keys %format > 1) { | 
| 95 | 0 |  |  |  |  | 0 | print "FORMAT: ", join(', ', keys %format), "\n"; | 
| 96 | 0 |  |  |  |  | 0 | croak "you can only specify at most ONE output format (default is 'dot')"; | 
| 97 |  |  |  |  |  |  | } | 
| 98 | 0 | 0 |  |  |  | 0 | if ($format{svg}) { | 
| 99 | 0 |  |  |  |  | 0 | eval { | 
| 100 | 0 |  |  |  |  | 0 | require Graph::Easy::As_svg; | 
| 101 |  |  |  |  |  |  | }; | 
| 102 | 0 | 0 |  |  |  | 0 | if ($@) { | 
| 103 | 0 |  |  |  |  | 0 | croak "You don't have Graph::Easy::As_svg installed, ", | 
| 104 |  |  |  |  |  |  | "so I can't generate SVG"; | 
| 105 |  |  |  |  |  |  | } | 
| 106 |  |  |  |  |  |  | } | 
| 107 | 0 | 0 |  |  |  | 0 | $format{dot} = 1 unless keys %format == 1; | 
| 108 |  |  |  |  |  |  |  | 
| 109 | 0 | 0 | 0 |  |  | 0 | if ($options{no_core} && $options{no_recurse_core}) { | 
| 110 | 0 |  |  |  |  | 0 | croak "doesn't make sense to specify no-core and no-recurse-core together"; | 
| 111 |  |  |  |  |  |  | } | 
| 112 |  |  |  |  |  |  |  | 
| 113 | 0 |  |  |  |  | 0 | for (keys %options) { | 
| 114 | 0 | 0 |  |  |  | 0 | delete $options{$_} unless defined $options{$_}; | 
| 115 |  |  |  |  |  |  | } | 
| 116 | 0 |  |  |  |  | 0 | $options{format} = (keys %format)[0]; | 
| 117 |  |  |  |  |  |  |  | 
| 118 | 0 |  |  |  |  | 0 | return %options; | 
| 119 |  |  |  |  |  |  | } | 
| 120 |  |  |  |  |  |  |  | 
| 121 |  |  |  |  |  |  | sub generate_graph | 
| 122 |  |  |  |  |  |  | { | 
| 123 | 2 |  |  | 2 | 1 | 55 | my ($self, @inputs) = @_; | 
| 124 | 2 |  |  |  |  | 5 | my (@queue, %seen, $scanner, $graph, $module); | 
| 125 | 0 |  |  |  |  | 0 | my ($prereqs, $depsref); | 
| 126 | 0 |  |  |  |  | 0 | my ($path, $filename, $fh); | 
| 127 | 2 |  |  |  |  | 3 | my $module_count = 0; | 
| 128 | 2 |  |  |  |  | 4 | my %depth; | 
| 129 |  |  |  |  |  |  |  | 
| 130 | 2 |  |  |  |  | 28 | $scanner = Perl::PrereqScanner->new; | 
| 131 | 2 |  |  |  |  | 53450 | $graph   = Graph::Easy->new(); | 
| 132 | 2 |  |  |  |  | 159 | $graph->timeout($self->timeout); | 
| 133 |  |  |  |  |  |  |  | 
| 134 | 2 |  |  |  |  | 11 | @depth{@inputs} = map { 0 } @inputs; | 
|  | 2 |  |  |  |  | 8 |  | 
| 135 |  |  |  |  |  |  |  | 
| 136 | 2 |  |  |  |  | 6 | push(@queue, @inputs); | 
| 137 | 2 |  |  |  |  | 10 | while (@queue > 0) { | 
| 138 | 63 |  |  |  |  | 127 | $module = shift @queue; | 
| 139 | 63 | 100 |  |  |  | 152 | next if $seen{$module}; | 
| 140 | 23 |  |  |  |  | 50 | $seen{$module} = 1; | 
| 141 |  |  |  |  |  |  |  | 
| 142 | 23 | 100 |  |  |  | 136 | if (defined($path = module_path($module))) { | 
|  |  | 50 |  |  |  |  |  | 
| 143 |  |  |  |  |  |  | } elsif (-f $module) { | 
| 144 | 0 |  |  |  |  | 0 | $path = $module; | 
| 145 |  |  |  |  |  |  | } else { | 
| 146 | 1 | 50 |  |  |  | 1310 | carp "can't find $module - keeping calm and carrying on.\n" if $self->verbose; | 
| 147 | 1 |  |  |  |  | 5 | next; | 
| 148 |  |  |  |  |  |  | } | 
| 149 |  |  |  |  |  |  |  | 
| 150 |  |  |  |  |  |  | # Huge files (eg currently Perl::Tidy) will cause PPI to barf | 
| 151 |  |  |  |  |  |  | # So we need to catch those, keep calm, and carry on | 
| 152 | 22 |  |  |  |  | 15638 | eval { $prereqs = $scanner->scan_file($path); }; | 
|  | 22 |  |  |  |  | 131 |  | 
| 153 | 22 | 50 |  |  |  | 7776176 | next if $@; | 
| 154 | 22 |  |  |  |  | 49 | ++$module_count; | 
| 155 | 22 |  |  |  |  | 157 | $depsref = $prereqs->as_string_hash(); | 
| 156 | 22 |  |  |  |  | 1149 | foreach my $dep (keys %{ $depsref }) { | 
|  | 22 |  |  |  |  | 87 |  | 
| 157 | 81 | 100 | 66 |  |  | 808 | if (!exists($depth{$dep}) || $depth{$dep} > $depth{$module} + 1) { | 
| 158 | 31 |  |  |  |  | 65 | $depth{$dep} = $depth{$module} + 1; | 
| 159 |  |  |  |  |  |  | } | 
| 160 | 81 | 50 | 33 |  |  | 370 | if ($self->no_core && is_core($dep)) { | 
|  |  | 100 |  |  |  |  |  | 
| 161 |  |  |  |  |  |  | # don't include core modules | 
| 162 |  |  |  |  |  |  | } elsif ($dep eq 'perl') { | 
| 163 | 8 |  |  |  |  | 45 | $graph->add_edge($module, "perl $depsref->{perl}"); | 
| 164 |  |  |  |  |  |  | } else { | 
| 165 | 73 |  |  |  |  | 260 | $graph->add_edge($module, $dep); | 
| 166 | 73 | 100 | 100 |  |  | 5802 | next if $self->depth && $depth{$dep} >= $self->depth; | 
| 167 | 61 | 50 | 33 |  |  | 284 | push(@queue, $dep) unless $self->no_recurse_core && is_core($dep); | 
| 168 |  |  |  |  |  |  | } | 
| 169 |  |  |  |  |  |  | } | 
| 170 |  |  |  |  |  |  | } | 
| 171 |  |  |  |  |  |  |  | 
| 172 | 2 |  | 33 |  |  | 12 | $filename = $self->output_file || 'dependencies.'.$self->format; | 
| 173 | 2 | 50 |  |  |  | 264 | open($fh, '>', $filename) || | 
| 174 |  |  |  |  |  |  | croak "Failed to write $filename: $!\n"; | 
| 175 | 2 |  |  |  |  | 14 | print $fh $formats{$self->format}->($graph); | 
| 176 | 2 |  |  |  |  | 123035 | close($fh); | 
| 177 | 2 | 50 |  |  |  | 81 | print STDERR "$module_count modules processed. Graph written to $filename\n" if $self->verbose; | 
| 178 |  |  |  |  |  |  | } | 
| 179 |  |  |  |  |  |  |  | 
| 180 |  |  |  |  |  |  | sub is_core | 
| 181 |  |  |  |  |  |  | { | 
| 182 | 0 |  |  | 0 | 0 |  | my $module   = shift; | 
| 183 | 0 | 0 |  |  |  |  | my $version  = @_ > 0 ? shift : $^V; | 
| 184 |  |  |  |  |  |  |  | 
| 185 | 0 | 0 |  |  |  |  | return 0 unless defined(my $first_release = Module::CoreList::first_release($module)); | 
| 186 | 0 | 0 |  |  |  |  | return 0 unless $version >= $first_release; | 
| 187 | 0 | 0 |  |  |  |  | return 1 if !defined(my $final_release = Module::CoreList::removed_from($module)); | 
| 188 | 0 |  |  |  |  |  | return $version <= $final_release; | 
| 189 |  |  |  |  |  |  | } | 
| 190 |  |  |  |  |  |  |  | 
| 191 |  |  |  |  |  |  | sub usage | 
| 192 |  |  |  |  |  |  | { | 
| 193 | 0 |  |  | 0 | 0 |  | require Pod::Usage; | 
| 194 | 0 |  |  |  |  |  | Pod::Usage::pod2usage(); | 
| 195 | 0 |  |  |  |  |  | exit; | 
| 196 |  |  |  |  |  |  | } | 
| 197 |  |  |  |  |  |  |  | 
| 198 |  |  |  |  |  |  | 1; | 
| 199 |  |  |  |  |  |  |  | 
| 200 |  |  |  |  |  |  | =head1 NAME | 
| 201 |  |  |  |  |  |  |  | 
| 202 |  |  |  |  |  |  | App::PrereqGrapher - generate dependency graph using Perl::PrereqScanner | 
| 203 |  |  |  |  |  |  |  | 
| 204 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 205 |  |  |  |  |  |  |  | 
| 206 |  |  |  |  |  |  | use App::PrereqGrapher; | 
| 207 |  |  |  |  |  |  |  | 
| 208 |  |  |  |  |  |  | my %options = ( | 
| 209 |  |  |  |  |  |  | format => 'dot', | 
| 210 |  |  |  |  |  |  | no_core => 0, | 
| 211 |  |  |  |  |  |  | no_recurse_core => 1, | 
| 212 |  |  |  |  |  |  | output_file => 'prereqs.dot', | 
| 213 |  |  |  |  |  |  | verbose => 0, | 
| 214 |  |  |  |  |  |  | ); | 
| 215 |  |  |  |  |  |  | my $grapher = App::PrereqGrapher->new( %options ); | 
| 216 |  |  |  |  |  |  |  | 
| 217 |  |  |  |  |  |  | $grapher->generate_graph('Module::Path'); | 
| 218 |  |  |  |  |  |  |  | 
| 219 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 220 |  |  |  |  |  |  |  | 
| 221 |  |  |  |  |  |  | App::PrereqGrapher builds a directed graph of the prereqs or dependencies for | 
| 222 |  |  |  |  |  |  | a file or module. It uses Perl::PrereqScanner to find the dependencies for the seed, | 
| 223 |  |  |  |  |  |  | and then repeatedly calls Perl::PrereqScanner on those dependencies, and so on, | 
| 224 |  |  |  |  |  |  | until all dependencies have been found. | 
| 225 |  |  |  |  |  |  |  | 
| 226 |  |  |  |  |  |  | It then saves the resulting graph to a file, using one of the five supported formats. | 
| 227 |  |  |  |  |  |  | The default format is 'dot', the format used by the GraphViz graph drawing toolkit. | 
| 228 |  |  |  |  |  |  |  | 
| 229 |  |  |  |  |  |  | If your code contains lines like: | 
| 230 |  |  |  |  |  |  |  | 
| 231 |  |  |  |  |  |  | require 5.006; | 
| 232 |  |  |  |  |  |  | use 5.006; | 
| 233 |  |  |  |  |  |  |  | 
| 234 |  |  |  |  |  |  | Then you'll end up with a dependency labelled B<perl 5.006>; | 
| 235 |  |  |  |  |  |  | this way you can see where you're dependent on modules which | 
| 236 |  |  |  |  |  |  | require different minimum versions of perl. | 
| 237 |  |  |  |  |  |  |  | 
| 238 |  |  |  |  |  |  | =head1 METHODS | 
| 239 |  |  |  |  |  |  |  | 
| 240 |  |  |  |  |  |  | =head2 new | 
| 241 |  |  |  |  |  |  |  | 
| 242 |  |  |  |  |  |  | The constructor understands the following options: | 
| 243 |  |  |  |  |  |  |  | 
| 244 |  |  |  |  |  |  | =over 4 | 
| 245 |  |  |  |  |  |  |  | 
| 246 |  |  |  |  |  |  | =item format | 
| 247 |  |  |  |  |  |  |  | 
| 248 |  |  |  |  |  |  | Select the output format, which must be one of: dot, svg, vcg, gml, or html. | 
| 249 |  |  |  |  |  |  | See L</"OUTPUT FORMATS"> for more about the supported output formats. | 
| 250 |  |  |  |  |  |  | If not specified, the default format is 'dot'. | 
| 251 |  |  |  |  |  |  |  | 
| 252 |  |  |  |  |  |  | =item output_file | 
| 253 |  |  |  |  |  |  |  | 
| 254 |  |  |  |  |  |  | Specifies the name of the file to write the dependency graph into, | 
| 255 |  |  |  |  |  |  | including the extension. If not specified, the filename will be C<dependencies>, | 
| 256 |  |  |  |  |  |  | with the extension set according to the format. | 
| 257 |  |  |  |  |  |  |  | 
| 258 |  |  |  |  |  |  | =item depth | 
| 259 |  |  |  |  |  |  |  | 
| 260 |  |  |  |  |  |  | Only generate the graph to the specified depth. | 
| 261 |  |  |  |  |  |  | If the complete dependency graph is very large, this option may help you get | 
| 262 |  |  |  |  |  |  | an overview. | 
| 263 |  |  |  |  |  |  |  | 
| 264 |  |  |  |  |  |  | =item no_core | 
| 265 |  |  |  |  |  |  |  | 
| 266 |  |  |  |  |  |  | Don't include any modules which are core (included with perl) for the version | 
| 267 |  |  |  |  |  |  | of perl being used. | 
| 268 |  |  |  |  |  |  |  | 
| 269 |  |  |  |  |  |  | =item no_recurse_core | 
| 270 |  |  |  |  |  |  |  | 
| 271 |  |  |  |  |  |  | When a core module is used, include it in the dependency graph, | 
| 272 |  |  |  |  |  |  | but don't show any of I<its> dependencies. | 
| 273 |  |  |  |  |  |  |  | 
| 274 |  |  |  |  |  |  | =item timeout | 
| 275 |  |  |  |  |  |  |  | 
| 276 |  |  |  |  |  |  | Specifies the timeout for Graph::Easy when its laying out the graph. | 
| 277 |  |  |  |  |  |  | This is mainly relevant for formats like SVG and HTML, where the graph | 
| 278 |  |  |  |  |  |  | layout is done in Perl. Defaults to 5 seconds. | 
| 279 |  |  |  |  |  |  |  | 
| 280 |  |  |  |  |  |  | =item verbose | 
| 281 |  |  |  |  |  |  |  | 
| 282 |  |  |  |  |  |  | Display verbose logging as the grapher runs. | 
| 283 |  |  |  |  |  |  | Currently this will just tell you if a module was use'd or require'd, | 
| 284 |  |  |  |  |  |  | but couldn't be found locally. | 
| 285 |  |  |  |  |  |  |  | 
| 286 |  |  |  |  |  |  | =back | 
| 287 |  |  |  |  |  |  |  | 
| 288 |  |  |  |  |  |  | =head2 generate_graph | 
| 289 |  |  |  |  |  |  |  | 
| 290 |  |  |  |  |  |  | Takes one or more seed items. Each item may be a module or the path to a perl file. | 
| 291 |  |  |  |  |  |  |  | 
| 292 |  |  |  |  |  |  | $grapher->generate_graph('Module::Path', 'Module::Version'); | 
| 293 |  |  |  |  |  |  |  | 
| 294 |  |  |  |  |  |  | It will first try and interpret each item as a module, but if it can't find a module | 
| 295 |  |  |  |  |  |  | with the given name, it will try and interpret it as a file path. | 
| 296 |  |  |  |  |  |  | This means that if you have a file called C<strict> for example, then you won't be | 
| 297 |  |  |  |  |  |  | able to run: | 
| 298 |  |  |  |  |  |  |  | 
| 299 |  |  |  |  |  |  | $grapher->generate_graph('strict'); | 
| 300 |  |  |  |  |  |  |  | 
| 301 |  |  |  |  |  |  | as it will be interpreted as the module of that name. Put an explicit path to stop this: | 
| 302 |  |  |  |  |  |  |  | 
| 303 |  |  |  |  |  |  | $grapher->generate_graph('./strict'); | 
| 304 |  |  |  |  |  |  |  | 
| 305 |  |  |  |  |  |  | =head1 OUTPUT FORMATS | 
| 306 |  |  |  |  |  |  |  | 
| 307 |  |  |  |  |  |  | =over 4 | 
| 308 |  |  |  |  |  |  |  | 
| 309 |  |  |  |  |  |  | =item dot | 
| 310 |  |  |  |  |  |  |  | 
| 311 |  |  |  |  |  |  | The format used by GraphViz and related tools. | 
| 312 |  |  |  |  |  |  |  | 
| 313 |  |  |  |  |  |  | =item svg | 
| 314 |  |  |  |  |  |  |  | 
| 315 |  |  |  |  |  |  | Scalable Vector Graphics (SVG) a W3C standard. | 
| 316 |  |  |  |  |  |  | You have to install L<Graph::Easy::As_svg> if you want to use this format. | 
| 317 |  |  |  |  |  |  |  | 
| 318 |  |  |  |  |  |  | =item vcg | 
| 319 |  |  |  |  |  |  |  | 
| 320 |  |  |  |  |  |  | The VCG or GDL format. | 
| 321 |  |  |  |  |  |  |  | 
| 322 |  |  |  |  |  |  | =item gml | 
| 323 |  |  |  |  |  |  |  | 
| 324 |  |  |  |  |  |  | Graph Markup Language, aka GraphML. | 
| 325 |  |  |  |  |  |  |  | 
| 326 |  |  |  |  |  |  | =item html | 
| 327 |  |  |  |  |  |  |  | 
| 328 |  |  |  |  |  |  | Generate an HTML format with embedded CSS. I haven't been able to get this to work, | 
| 329 |  |  |  |  |  |  | but it's one of the formats supported by L<Graph::Easy>. | 
| 330 |  |  |  |  |  |  |  | 
| 331 |  |  |  |  |  |  | =back | 
| 332 |  |  |  |  |  |  |  | 
| 333 |  |  |  |  |  |  | =head1 KNOWN BUGS | 
| 334 |  |  |  |  |  |  |  | 
| 335 |  |  |  |  |  |  | L<Perl::PrereqScanner> uses L<PPI> to parse each item. | 
| 336 |  |  |  |  |  |  | PPI has a hard-coded limit for the size of file it's prepared to parse | 
| 337 |  |  |  |  |  |  | (currently just over 1M). | 
| 338 |  |  |  |  |  |  | This means that very large files will be ignored; | 
| 339 |  |  |  |  |  |  | for example Perl::Tidy cannot be graphed, | 
| 340 |  |  |  |  |  |  | and if you try and graph a file that use's Perl::Tidy, | 
| 341 |  |  |  |  |  |  | then it just won't appear in the graph. | 
| 342 |  |  |  |  |  |  |  | 
| 343 |  |  |  |  |  |  | If a class isn't defined in its own file, | 
| 344 |  |  |  |  |  |  | then App::PrereqGrapher won't find it; | 
| 345 |  |  |  |  |  |  | for example Tie::StdHash is defined inside Tie::Hash. | 
| 346 |  |  |  |  |  |  | By default these are silently ignored, | 
| 347 |  |  |  |  |  |  | but if you use the B<-verbose> option you'll get the following warning: | 
| 348 |  |  |  |  |  |  |  | 
| 349 |  |  |  |  |  |  | can't find Tie::StdHash - keeping calm and carrying on. | 
| 350 |  |  |  |  |  |  |  | 
| 351 |  |  |  |  |  |  | Perl::PrereqScanner parses code and makes no attempt to | 
| 352 |  |  |  |  |  |  | determine whether any of it would actually run on your platform. | 
| 353 |  |  |  |  |  |  | For example, one module might decide at run-time whether to C<require> | 
| 354 |  |  |  |  |  |  | Foo::Bar or Foo::Baz, and might never use Foo::Baz on your OS. | 
| 355 |  |  |  |  |  |  | But Perl::PrereqScanner will see both of Foo::Bar and Foo::Baz | 
| 356 |  |  |  |  |  |  | as pre-reqs. | 
| 357 |  |  |  |  |  |  |  | 
| 358 |  |  |  |  |  |  | =head1 TODO | 
| 359 |  |  |  |  |  |  |  | 
| 360 |  |  |  |  |  |  | =over 4 | 
| 361 |  |  |  |  |  |  |  | 
| 362 |  |  |  |  |  |  | =item * | 
| 363 |  |  |  |  |  |  |  | 
| 364 |  |  |  |  |  |  | Have an option to control what depth we should recurse to? | 
| 365 |  |  |  |  |  |  | You might only be interested in the dependencies of your code, | 
| 366 |  |  |  |  |  |  | and their first level of dependencies. | 
| 367 |  |  |  |  |  |  |  | 
| 368 |  |  |  |  |  |  | =item * | 
| 369 |  |  |  |  |  |  |  | 
| 370 |  |  |  |  |  |  | Show some indication that we're running. It can take a long time to run | 
| 371 |  |  |  |  |  |  | if your ultimate dependency graph is very large. | 
| 372 |  |  |  |  |  |  |  | 
| 373 |  |  |  |  |  |  | =back | 
| 374 |  |  |  |  |  |  |  | 
| 375 |  |  |  |  |  |  | =head1 SEE ALSO | 
| 376 |  |  |  |  |  |  |  | 
| 377 |  |  |  |  |  |  | The distribution for this module contains a command-line script, | 
| 378 |  |  |  |  |  |  | L<prereq-grapher>. It has its own documentation. | 
| 379 |  |  |  |  |  |  |  | 
| 380 |  |  |  |  |  |  | This module uses L<Perl::PrereqScanner> to parse the source code, | 
| 381 |  |  |  |  |  |  | and L<Graph::Easy> to generate and save the dependency graph. | 
| 382 |  |  |  |  |  |  |  | 
| 383 |  |  |  |  |  |  | If you want to generate SVG graphs, you need to have L<Graph::Easy::As_svg> installed. | 
| 384 |  |  |  |  |  |  |  | 
| 385 |  |  |  |  |  |  | L<http://neilb.org/reviews/dependencies.html>: a review of CPAN modules that can be used | 
| 386 |  |  |  |  |  |  | to get dependency information. | 
| 387 |  |  |  |  |  |  |  | 
| 388 |  |  |  |  |  |  | =head1 REPOSITORY | 
| 389 |  |  |  |  |  |  |  | 
| 390 |  |  |  |  |  |  | L<https://github.com/neilb/App-PrereqGrapher> | 
| 391 |  |  |  |  |  |  |  | 
| 392 |  |  |  |  |  |  | =head1 AUTHOR | 
| 393 |  |  |  |  |  |  |  | 
| 394 |  |  |  |  |  |  | Neil Bowers E<lt>neilb@cpan.orgE<gt> | 
| 395 |  |  |  |  |  |  |  | 
| 396 |  |  |  |  |  |  | =head1 COPYRIGHT AND LICENSE | 
| 397 |  |  |  |  |  |  |  | 
| 398 |  |  |  |  |  |  | This software is copyright (c) 2012 by Neil Bowers <neilb@cpan.org>. | 
| 399 |  |  |  |  |  |  |  | 
| 400 |  |  |  |  |  |  | This is free software; you can redistribute it and/or modify it under | 
| 401 |  |  |  |  |  |  | the same terms as the Perl 5 programming language system itself. | 
| 402 |  |  |  |  |  |  |  | 
| 403 |  |  |  |  |  |  | =cut | 
| 404 |  |  |  |  |  |  |  |