GraphViz2-2.67/0000755000175000017500000000000014266245444013225 5ustar osboxesosboxesGraphViz2-2.67/META.json0000644000175000017500000000401314266245444014644 0ustar osboxesosboxes{ "abstract" : "A wrapper for AT&T's Graphviz", "author" : [ "Ron Savage " ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 7.44, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "GraphViz2", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "develop" : { "requires" : { "Pod::Markdown" : "0", "Test::Pod" : "1.48" }, "suggests" : { "Config::Tiny" : "2.16", "Date::Simple" : "3.03", "File::HomeDir" : "0.99", "HTML::TreeBuilder" : "4.2", "HTTP::Tiny" : "0.012", "Text::Xslate" : "1.2" } }, "runtime" : { "requires" : { "Data::Section::Simple" : "0.02", "File::Which" : "1.21", "Graph" : "0.9716", "IPC::Run3" : "0.048", "Moo" : "2.001001", "Types::Standard" : "1.000005", "perl" : "5.008008" } }, "test" : { "requires" : { "Test::More" : "0.88", "Test::Snapshot" : "0.06" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/graphviz-perl/GraphViz2/issues" }, "license" : [ "http://dev.perl.org/licenses/" ], "repository" : { "type" : "git", "url" : "https://github.com/graphviz-perl/GraphViz2.git", "web" : "https://github.com/graphviz-perl/GraphViz2" } }, "version" : "2.67", "x_serialization_backend" : "JSON::PP version 4.04" } GraphViz2-2.67/MANIFEST.SKIP0000644000175000017500000000131613750334153015115 0ustar osboxesosboxes# Avoid version control files. ,v$ \B\.cvsignore$ \B\.git\b \B\.gitignore\b \B\.svn\b \bCVS\b \bRCS\b # Avoid Makemaker generated and utility files. \bblib \bblibdirs$ \bpm_to_blib$ \bMakefile$ \bMakeMaker-\d # Avoid Module::Build generated and utility files. \b_build \bBuild$ \bBuild.bat$ # Avoid Devel::Cover generated files \bcover_db # Avoid temp and backup files. ~$ \#$ \.# \.bak$ \.old$ \.rej$ \.tmp$ # Avoid OS-specific files/dirs # Mac OSX metadata \B\.DS_Store # Mac OSX SMB mount metadata files \B\._ # Avoid UltraEdit files. \.prj$ \.pui$ ^MYMETA\. ^GraphViz2-.* \.swp$ ^lib/GraphViz2/Config\.pm ^lib/GraphViz2/Filer\.pm ^lib/GraphViz2/Utils\.pm ^config/ ^data/ ^htdocs/ ^scripts/ ^\.github/ GraphViz2-2.67/t/0000755000175000017500000000000014266245444013470 5ustar osboxesosboxesGraphViz2-2.67/t/gen.macro.3.t0000644000175000017500000000231613741671654015674 0ustar osboxesosboxes# Annotation: Demonstrates cluster subgraphs via a macro. use strict; use warnings; use File::Spec; use GraphViz2; sub macro { my ($graph, $name, $node_1, $node_2) = @_; $graph->push_subgraph( name => $name, graph => {label => $name}, node => {color => 'magenta', shape => 'diamond'}, ); $graph->add_node(name => $node_1, shape => 'hexagon'); $graph->add_node(name => $node_2, color => 'orange'); $graph->add_edge(from => $node_1, to => $node_2); $graph->pop_subgraph; } my $id = '3'; my $graph = GraphViz2->new( edge => {color => 'grey'}, global => {directed => 1}, graph => {label => "Macro demo $id - Cluster sub-graphs", rankdir => 'TB'}, ); macro($graph, 'cluster 1', 'Chadstone', 'Waverley'); macro($graph, 'cluster 2', 'Hughesdale', 'Notting Hill'); if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec -> catfile('html', "macro.$id.$format"); $graph -> run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; $graph->run(format => 'dot'); # leave here as a canary so test "run" Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/gen.record.1.t0000644000175000017500000000214013741677760016046 0ustar osboxesosboxes# Annotation: Nested records using strings as labels. use strict; use warnings; use File::Spec; use GraphViz2; my $id = '1'; my $graph = GraphViz2->new( edge => {color => 'grey'}, global => {directed => 1}, graph => {label => "Record demo $id - Nested records using strings as labels"}, node => {shape => 'record'}, ); $graph->add_node(name => 'struct1::x', label => ' left| mid dle| right'); $graph->add_node(name => 'struct2', label => ' one| two'); $graph->add_node( name => 'struct3', label => "hello\\nworld |{ b |{c| d|e}| f}| g | h", ); $graph->add_edge(from => 'struct1::x:f1', to => 'struct2:f0', color => 'blue'); $graph->add_edge(from => 'struct1::x:f2', to => 'struct3:here', color => 'red'); if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec->catfile('html', "record.$id.$format"); $graph->run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/gen.record.4.t0000644000175000017500000000214013741720434016034 0ustar osboxesosboxes# Annotation: Set record-style node labels and shapes in various ways. use strict; use warnings; use File::Spec; use GraphViz2; my $id = '4'; my $graph = GraphViz2->new( global => {directed => 1}, graph => {label => "Record demo $id - Set record shapes in various ways"}, node => {color => 'magenta'}, ); $graph->add_node(name => 'One', label => []); $graph->add_node(name => 'Two', label => ['Left', 'Right']); $graph->add_node(name => 'Three', color => 'black', label => ['Good', 'Bad'], shape => 'record'); $graph->add_node(name => 'Four', label => [ { text => '{Big', }, { text => 'Small}', }, ]); $graph->add_node(name => 'Five', label => [ { text => '{Yin', }, { text => 'Yang}', }, ], shape => 'record'); if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec -> catfile('html', "record.$id.$format"); $graph -> run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/gen.record.3.t0000644000175000017500000000366113747337472016060 0ustar osboxesosboxes# Annotation: Deeply nested records using nested array-refs. use strict; use warnings; use File::Spec; use GraphViz2; my $id = '3'; my $graph = GraphViz2->new( global => {directed => 1, combine_node_and_port => 0, record_shape => 'record'}, graph => { label => "Record demo $id - Deeply nested records " . "using nested array-refs" }, ); $graph->add_node(name => 'Alphabet', label => [ { port => 'port_a', text => 'a:port_a' }, [ { port => 'port_b', text => 'b:port_b' }, 'c', [ { port => 'port_d', text => 'd:port_d' }, 'e', 'f', [ 'g', { port => 'port_h', text => 'h:port_h' }, 'i', 'j', [ 'k', 'l', 'm', { port => 'port_n', text => 'n:port_n' }, 'o', 'p', ], 'q', 'r', { port => 'port_s', text => 's:port_s' }, 't', ], 'u', 'v', { port => 'port_w', text => 'w:port_w' }, ], 'x', { port => 'port_y', text => 'y:port_y' }, ], 'z', ]); $graph -> add_edge( from => 'Alphabet', tailport => 'port_a', to => 'Alphabet', headport => 'port_n', color => 'maroon', ); $graph -> add_edge( from => 'Alphabet', tailport => 'port_b', to => 'Alphabet', headport => 'port_s', color => 'blue', ); $graph -> add_edge( from => 'Alphabet', tailport => 'port_d', to => 'Alphabet', headport => 'port_w', color => 'red', ); $graph -> add_edge( from => 'Alphabet', tailport => 'port_y', to => 'Alphabet', headport => 'port_h', color => 'green', ); if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec -> catfile('html', "record.$id.$format"); $graph -> run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/gen.sub.graph.frames.t0000644000175000017500000000426313741721767017603 0ustar osboxesosboxes# Annotation: Demonstrates clusters with and without frames. use strict; use warnings; use File::Spec; use GraphViz2; my $graph = GraphViz2->new( edge => {color => 'grey', penwidth => 3}, global => {directed => 1}, graph => {label => 'Demo of 3 subgraphs (2 being clusters), and 1 frame', rankdir => 'TB'}, node => {shape => 'oval'}, ); $graph->add_node(name => 'One', color => 'red', shape => 'circle'); $graph->add_node(name => 'Two', color => 'green', shape => 'doublecircle'); $graph->add_edge(from => 'One', to => 'Two', color => 'maroon'); $graph->push_subgraph( graph => {label => 'Child the First'}, name => 'cluster First subgraph', node => {color => 'magenta', shape => 'diamond'}, subgraph => {pencolor => 'white'}, # Required because name =~ /^cluster/. ); $graph->add_node(name => 'Three'); # Default color and shape. $graph->add_node(name => 'Four', color => 'orange', shape => 'rectangle'); $graph->add_edge(from => 'Three', to => 'Four'); $graph->pop_subgraph; $graph->push_subgraph( graph => {label => 'Child the Second'}, name => 'cluster Second subgraph', node => {color => 'magenta', shape => 'diamond'}, subgraph => {pencolor => 'purple'}, # Required because name =~ /^cluster/. ); $graph->add_node(name => 'Five', color => 'blue'); # Default shape. $graph->add_node(name => 'Six', color => 'orange', shape => 'rectangle'); $graph->add_edge(from => 'Five', to => 'Six'); $graph->pop_subgraph; $graph->push_subgraph( name => 'Third subgraph', graph => {label => 'Child the Third'}, node => {color => 'magenta', shape => 'diamond'}, ); $graph->add_node(name => 'Seven', color => 'blue', shape => 'doubleoctagon'); $graph->add_node(name => 'Eight', color => 'orange', shape => 'rectangle'); $graph->add_edge(from => 'Seven', to => 'Eight'); $graph->pop_subgraph; if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec -> catfile('html', "sub.graph.frames.$format"); $graph -> run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/gen.macro.4.t0000644000175000017500000000234213741672506015671 0ustar osboxesosboxes# Annotation: Demonstrates linked cluster subgraphs via a macro. use strict; use warnings; use File::Spec; use GraphViz2; sub macro { my ($graph, $name, $node_1, $node_2) = @_; $graph->push_subgraph( name => $name, graph => {label => $name}, node => {color => 'magenta', shape => 'diamond'}, ); $graph->add_node(name => $node_1, shape => 'hexagon'); $graph->add_node(name => $node_2, color => 'orange'); $graph->add_edge(from => $node_1, to => $node_2); $graph->pop_subgraph; } my $id = '4'; my $graph = GraphViz2->new( edge => {color => 'grey'}, global => {directed => 1}, graph => {label => "Macro demo $id - Linked cluster sub-graphs", rankdir => 'TB'}, ); macro($graph, 'cluster 1', 'Chadstone', 'Waverley'); macro($graph, 'cluster 2', 'Hughesdale', 'Notting Hill'); $graph -> add_edge(from => 'Chadstone', to => 'Notting Hill', minlen => 2); if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec -> catfile('html', "macro.$id.$format"); $graph -> run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/gen.rank.sub.graph.1.t0000644000175000017500000000317213741675573017421 0ustar osboxesosboxes# Annotation: Demonstrates subgraphs used to rank nodes horizontally (succinct version of code). use strict; use warnings; use File::Spec; use GraphViz2; my $graph = GraphViz2->new( edge => {color => 'grey'}, global => {directed => 1}, graph => {rankdir => 'TB'}, node => {shape => 'oval'}, subgraph => {rank => 'same'}, ); $graph->add_node(name => 'Carnegie', shape => 'circle'); $graph->add_node(name => 'Chadstone', shape => 'circle', color => 'red'); $graph->add_node(name => 'Malvern', shape => 'box', color => 'green'); $graph->add_node(name => 'Murrumbeena', shape => 'doublecircle', color => 'orange'); $graph->add_node(name => 'Oakleigh', color => 'blue'); $graph->add_edge(from => 'Chadstone', to => 'Oakleigh', arrowhead => 'odot'); $graph->add_edge(from => 'Malvern', to => 'Carnegie', arrowsize => 2); $graph->add_edge(from => 'Malvern', to => 'Oakleigh', color => 'brown'); # a and b are arbitrary values. All that's happening is that all nodes # in @{$rank{a} } will be in the same horizontal line, and likewise for b. my %rank = (a => [qw(Malvern Prahran)], b => [qw(Oakleigh Murrumbeena)]); for my $key (sort keys %rank) { $graph->push_subgraph; $graph->add_node(name => $_) for @{$rank{$key} }; $graph->pop_subgraph; } if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec->catfile('html', "rank.sub.graph.1.$format"); $graph->run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/calc.input0000644000175000017500000000257313730646160015455 0ustar osboxesosboxes%right '=' %left '-' '+' %left '*' '/' %left NEG %right '^' %% input: #empty | input line { push(@{$_[1]},$_[2]); $_[1] } ; line: '\n' { ++$_[0]->YYData->{LINE}; $_[1] } | exp '\n' { ++$_[0]->YYData->{LINE}; $_[1] } | error '\n' { ++$_[0]->YYData->{LINE}; $_[0]->YYErrok } ; exp: NUM | VAR { $_[0]->YYData->{VARS}{$_[1]} } | VAR '=' exp { $_[0]->YYData->{VARS}{$_[1]}=$_[3] } | exp '+' exp { $_[1] + $_[3] } | exp '-' exp { $_[1] - $_[3] } | exp '*' exp { $_[1] * $_[3] } | exp '/' exp { $_[1] / $_[3] } | '-' exp %prec NEG { -$_[2] } | exp '^' exp { $_[1] ** $_[3] } | '(' exp ')' { $_[2] } ; %% sub Error { my($parser)=shift; push(@{$parser->YYData->{ERRLINES}}, $parser->YYData->{LINE}); } sub Lexer { my($parser)=shift; exists($parser->YYData->{LINE}) or $parser->YYData->{LINE}=1; $parser->YYData->{INPUT} or return('',undef); $parser->YYData->{INPUT}=~s/^[ \t]//; for ($parser->YYData->{INPUT}) { s/^([0-9]+(?:\.[0-9]+)?)// and return('NUM',$1); s/^([A-Za-z][A-Za-z0-9_]*)// and return('VAR',$1); s/^(.)//s and return($1,$1); } } GraphViz2-2.67/t/gen.sub.graph.t0000644000175000017500000000314413741722337016316 0ustar osboxesosboxes# Annotation: Demonstrates a subgraph (with a frame because the subgroup is called cluster_*). use strict; use warnings; use File::Spec; use GraphViz2; my $graph = GraphViz2->new( edge => {color => 'grey'}, global => {directed => 1}, graph => {label => 'sub.graph.pl', rankdir => 'TB'}, node => {shape => 'oval'}, ); $graph->add_node(name => 'Carnegie', shape => 'circle'); $graph->add_node(name => 'Murrumbeena', shape => 'doublecircle', color => 'green'); $graph->add_node(name => 'Oakleigh', color => 'blue'); $graph->add_edge(from => 'Murrumbeena', to => 'Carnegie', arrowsize => 2); $graph->add_edge(from => 'Murrumbeena', to => 'Oakleigh', color => 'brown'); $graph->push_subgraph( name => 'cluster_1', graph => {label => 'Child'}, node => {color => 'magenta', shape => 'diamond'}, ); $graph->add_node(name => 'Chadstone', shape => 'hexagon'); $graph->add_node(name => 'Waverley', color => 'orange'); $graph->add_edge(from => 'Chadstone', to => 'Waverley'); $graph->pop_subgraph; $graph->default_node(color => 'cyan'); $graph->add_node(name => 'Malvern'); $graph->add_node(name => 'Prahran', shape => 'trapezium'); $graph->add_edge(from => 'Malvern', to => 'Prahran'); $graph->add_edge(from => 'Malvern', to => 'Murrumbeena'); if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec -> catfile('html', "sub.graph.$format"); $graph -> run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/gen.html.labels.1.t0000644000175000017500000000335313741670241016767 0ustar osboxesosboxes# Annotation: Demonstrates a HTML label without a table. use strict; use warnings; use File::Spec; use GraphViz2; my $id = 1; my $graph = GraphViz2->new( edge => {color => 'grey'}, global => {directed => 1}, graph => { label => "HTML label demo # $id - Using \\< ... \\>", rankdir => 'TB', }, node => {shape => 'oval'}, ); $graph -> default_node(shape => 'circle', style => 'filled'); $graph -> default_edge(arrowsize => 4); $graph -> add_node(name => 'Carnegie', shape => 'circle'); $graph -> add_node(name => 'Carnegie', color => 'red'); $graph -> default_node(style => 'rounded'); $graph -> add_node( name => 'Murrumbeena', shape => 'doublecircle', color => 'green', label => 'Victoria
Australia>', ); $graph -> add_node( name => 'Oakleigh', shape => 'record', color => 'blue', label => ['West Oakleigh', 'East Oakleigh'], ); $graph -> add_edge( from => 'Murrumbeena', to => 'Carnegie', arrowsize => 2, label => 'Train
Stroll>', ); $graph -> default_edge(arrowsize => 1); $graph -> add_edge( from => 'Murrumbeena', to => 'Oakleigh:port1', color => 'brown', label => 'Promenade
Saunter>', ); $graph -> add_edge( from => 'Murrumbeena', to => 'Oakleigh:port2', color => 'green', label => 'Run
Sprint>', ); if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec -> catfile('html', "html.labels.$id.$format"); $graph -> run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/gen.parse.yacc.t0000644000175000017500000000152713743740444016461 0ustar osboxesosboxes# Annotation: Demonstrates graphing a byacc-style grammar. use strict; use warnings; use File::Spec; use GraphViz2; use GraphViz2::Parse::Yacc; my $graph = GraphViz2->new( edge => {color => 'grey'}, global => {directed => 1, combine_node_and_port => 0}, graph => {concentrate => 1, rankdir => 'TB'}, node => {color => 'blue', shape => 'oval'}, ); my $g = GraphViz2::Parse::Yacc->new(graph => $graph); $g->create(file_name => File::Spec->catfile('t', 'calc3.output')); if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec -> catfile('html', "parse.yacc.$format"); $graph -> run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/gen.sub.sub.graph.t0000644000175000017500000000364713741723203017107 0ustar osboxesosboxes# Annotation: Demonstrates a subsubgraph. use strict; use warnings; use File::Spec; use GraphViz2; my $graph = GraphViz2->new( edge => {color => 'grey'}, global => {directed => 1}, graph => {label => 'sub.sub.graph.pl', rankdir => 'TB'}, node => {shape => 'oval'}, ); $graph->add_node(name => 'Carnegie', shape => 'circle'); $graph->add_node(name => 'Murrumbeena', shape => 'doublecircle', color => 'green'); $graph->add_node(name => 'Oakleigh', color => 'blue'); $graph->add_edge(from => 'Murrumbeena', to => 'Carnegie', arrowsize => 2); $graph->add_edge(from => 'Murrumbeena', to => 'Oakleigh', color => 'brown'); $graph->push_subgraph( name => 'cluster_subgraph_1', graph => {label => 'Child'}, node => {color => 'magenta', shape => 'diamond'}, ); $graph->add_node(name => 'Chadstone', shape => 'hexagon'); $graph->add_node(name => 'Waverley', color => 'orange'); $graph->add_edge(from => 'Chadstone', to => 'Waverley'); $graph->push_subgraph( name => 'cluster_2', graph => {label => 'Grandchild'}, node => {color => 'blue3', shape => 'triangle'}, ); $graph->add_node(name => 'Glen Waverley', shape => 'pentagon'); $graph->add_node(name => 'Mount Waverley', color => 'darkslategrey'); $graph->add_edge(from => 'Glen Waverley', to => 'Mount Waverley'); $graph->pop_subgraph; $graph->pop_subgraph; $graph->default_node(color => 'cyan'); $graph->add_node(name => 'Malvern'); $graph->add_node(name => 'Prahran', shape => 'trapezium'); $graph->add_edge(from => 'Malvern', to => 'Prahran'); $graph->add_edge(from => 'Malvern', to => 'Murrumbeena'); if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec -> catfile('html', "sub.sub.graph.$format"); $graph -> run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/gen.Heawood.t0000644000175000017500000000251513741667073016021 0ustar osboxesosboxes# Annotation: Demonstrates the transitive 6-net, also known as Heawood's graph. # # Reverse-engineered from graphs/directed/Heawood.gv from the Graphviz distro for V 2.26.3. use strict; use warnings; use File::Spec; use GraphViz2; my $graph = GraphViz2 -> new( global => {name => 'Heawood'}, graph => {rankdir => 'TB'}, ); $graph->default_edge(color => 'black'); $graph->default_node( fontname => "Arial", shape => "circle", width => "0.50000", height => "0.50000", color => "black", ); $graph->add_edge(from => $_, to => ($_ + 1) ) for 0 .. 12; $graph -> add_edge(from => 13, to => 0); $graph -> add_edge(from => 0, to => 5, len => 2.5); $graph -> add_edge(from => 2, to => 7, len => 2.5); $graph -> add_edge(from => 4, to => 9, len => 2.5); $graph -> add_edge(from => 6, to => 11, len => 2.5); $graph -> add_edge(from => 8, to => 13, len => 2.5); $graph -> add_edge(from => 10, to => 1, len => 2.5); $graph -> add_edge(from => 12, to => 3, len => 2.5); if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec->catfile('html', "Heawood.$format"); $graph->run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/calc3.y0000644000175000017500000000376513730646160014655 0ustar osboxesosboxes%pure-parser %parse-param { int regs[26] } %parse-param { int *base } %lex-param { int *base } %{ # include # include %} %start list %token DIGIT LETTER %left '|' %left '&' %left '+' '-' %left '*' '/' '%' %left UMINUS /* supplies precedence for unary minus */ %% /* beginning of rules section */ list : /* empty */ | list stat '\n' | list error '\n' { yyerrok ; } ; stat : expr { printf("%d\n",$1);} | LETTER '=' expr { regs[$1] = $3; } ; expr : '(' expr ')' { $$ = $2; } | expr '+' expr { $$ = $1 + $3; } | expr '-' expr { $$ = $1 - $3; } | expr '*' expr { $$ = $1 * $3; } | expr '/' expr { $$ = $1 / $3; } | expr '%' expr { $$ = $1 % $3; } | expr '&' expr { $$ = $1 & $3; } | expr '|' expr { $$ = $1 | $3; } | '-' expr %prec UMINUS { $$ = - $2; } | LETTER { $$ = regs[$1]; } | number ; number: DIGIT { $$ = $1; (*base) = ($1==0) ? 8 : 10; } | number DIGIT { $$ = (*base) * $1 + $2; } ; %% /* start of programs */ #ifdef YYBYACC extern int YYLEX_DECL(); static void YYERROR_DECL(); #endif int main (void) { int regs[26]; int base = 10; while(!feof(stdin)) { yyparse(regs, &base); } return 0; } static void YYERROR_DECL() { fprintf(stderr, "%s\n", s); } int YYLEX_DECL() { /* lexical analysis routine */ /* returns LETTER for a lower case letter, yylval = 0 through 25 */ /* return DIGIT for a digit, yylval = 0 through 9 */ /* all other characters are returned immediately */ int c; while( (c=getchar()) == ' ' ) { /* skip blanks */ } /* c is now nonblank */ if( islower( c )) { *yylval = (c - 'a'); return ( LETTER ); } if( isdigit( c )) { *yylval = (c - '0') % (*base); return ( DIGIT ); } return( c ); } GraphViz2-2.67/t/gen.utf8.1.t0000644000175000017500000000267513741732370015460 0ustar osboxesosboxes# Annotation: Demonstrates utf8 chars in labels. use strict; use utf8; use warnings; use warnings qw(FATAL utf8); # Fatalize encoding glitches. use File::Spec; use GraphViz2; my $graph = GraphViz2->new( edge => {color => 'grey'}, global => {directed => 1}, graph => {rankdir => 'TB'}, node => {shape => 'oval'}, ); $graph->add_node(name => 'Zero', label => 'The Orient Express'); $graph->add_node(name => 'One', label => 'Reichwaldstraße'); $graph->add_node(name => 'Two', label => 'Böhme'); $graph->add_node(name => 'Three', label => 'ʎ ʏ ʐ ʑ ʒ ʓ ʙ ʚ'); $graph->add_node(name => 'Four', label => 'Πηληϊάδεω Ἀχιλῆος'); $graph->add_node(name => 'Five', label => 'ΔΔΔΔΔΔΔΔΔΔΔΔΔΔΔΔΔ'); $graph->add_edge(from => 'Zero', to => 'One'); $graph->add_edge(from => 'Zero', to => 'Three'); $graph->add_edge(from => 'One', to => 'Two'); $graph->add_edge(from => 'Three', to => 'Four'); $graph->add_edge(from => 'Two', to => 'Five', label => 'Label has a ☃'); $graph->add_edge(from => 'Four', to => 'Five', label => 'Label has a ✔'); if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec -> catfile('html', "utf8.1.$format"); $graph->run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/snapshots/0000755000175000017500000000000014266245444015512 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_map_4_t/0000755000175000017500000000000014266245444017666 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_map_4_t/dot_file0000644000175000017500000000034313740725234021372 0ustar osboxesosboxes'digraph mainmap { URL = "gen.map.4.1.html"; graph [ rankdir="TB" ] node [ shape="oval" ] edge [ color="grey" ] "source" [ URL="gen.map.4.2.html" ] "destination" "source" -> "destination" [ URL="gen.map.4.3.html" ] } ' GraphViz2-2.67/t/snapshots/gen_sub_graph_frames_t/0000755000175000017500000000000014266245444022175 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_sub_graph_frames_t/dot_file0000644000175000017500000000167513743144324023710 0ustar osboxesosboxes'digraph Perl { graph [ label="Demo of 3 subgraphs (2 being clusters), and 1 frame" rankdir="TB" ] node [ shape="oval" ] edge [ color="grey" penwidth="3" ] "One" [ color="red" shape="circle" ] "Two" [ color="green" shape="doublecircle" ] "One" -> "Two" [ color="maroon" ] subgraph "cluster First subgraph" { graph [ label="Child the First" ] node [ color="magenta" shape="diamond" ] pencolor="white" "Three" "Four" [ color="orange" shape="rectangle" ] "Three" -> "Four" } subgraph "cluster Second subgraph" { graph [ label="Child the Second" ] node [ color="magenta" shape="diamond" ] pencolor="purple" "Five" [ color="blue" ] "Six" [ color="orange" shape="rectangle" ] "Five" -> "Six" } subgraph "Third subgraph" { graph [ label="Child the Third" ] node [ color="magenta" shape="diamond" ] "Seven" [ color="blue" shape="doubleoctagon" ] "Eight" [ color="orange" shape="rectangle" ] "Seven" -> "Eight" } } ' GraphViz2-2.67/t/snapshots/gen_plaintext_t/0000755000175000017500000000000014266245444020676 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_plaintext_t/dot_file0000644000175000017500000000006713741674750022414 0ustar osboxesosboxes'graph Perl { "Murrumbeena" [ shape="plaintext" ] } ' GraphViz2-2.67/t/snapshots/gen_html_labels_3_t/0000755000175000017500000000000014266245444021376 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_html_labels_3_t/dot_file0000644000175000017500000000077713736760027023121 0ustar osboxesosboxes'digraph Perl { graph [ label="HTML label demo # 3 - Fixing newlines" rankdir="TB" ] node [ shape="oval" ] edge [ color="grey" ] "One" [ label=Blue
> ] "Two" [ label=Green
> ] "Three" [ color="red" label=<
Three
Red
> ] "Four" [ color="magenta" label=<
Four
magenta
> ] } ' GraphViz2-2.67/t/snapshots/gen_sub_graph_t/0000755000175000017500000000000014266245444020640 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_sub_graph_t/dot_file0000644000175000017500000000120213741745721022343 0ustar osboxesosboxes'digraph Perl { graph [ label="sub.graph.pl" rankdir="TB" ] node [ shape="oval" ] edge [ color="grey" ] "Carnegie" [ shape="circle" ] "Murrumbeena" [ color="green" shape="doublecircle" ] "Oakleigh" [ color="blue" ] "Murrumbeena" -> "Carnegie" [ arrowsize="2" ] "Murrumbeena" -> "Oakleigh" [ color="brown" ] subgraph "cluster_1" { graph [ label="Child" ] node [ color="magenta" shape="diamond" ] "Chadstone" [ shape="hexagon" ] "Waverley" [ color="orange" ] "Chadstone" -> "Waverley" } node [ color="cyan" shape="oval" ] "Malvern" "Prahran" [ shape="trapezium" ] "Malvern" -> "Prahran" "Malvern" -> "Murrumbeena" } ' GraphViz2-2.67/t/snapshots/methods_t/0000755000175000017500000000000014266245444017500 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/methods_t/mve_edges_0_10000644000175000017500000000046113776426405022024 0ustar osboxesosboxes{ 'a' => { 'b' => [ { 'attributes' => { 'label' => 'a b x' }, 'from_port' => '', 'to_port' => '' }, { 'attributes' => { 'label' => 'a b y' }, 'from_port' => '', 'to_port' => '' } ] } } GraphViz2-2.67/t/snapshots/methods_t/mve_edges_1_00000644000175000017500000000025113773672367022030 0ustar osboxesosboxes{ 'a' => { 'b' => [ { 'attributes' => { 'label' => 'a b' }, 'from_port' => '', 'to_port' => '' } ] } } GraphViz2-2.67/t/snapshots/methods_t/mve_nodes_1_10000644000175000017500000000022414014557440022031 0ustar osboxesosboxes{ 'a' => { 'attributes' => { 'label' => 'a w' } }, 'b' => { 'attributes' => {} }, 'v' => { 'attributes' => {} } } GraphViz2-2.67/t/snapshots/methods_t/mve_nodes_0_10000644000175000017500000000022214014557440022026 0ustar osboxesosboxes{ 'a' => { 'attributes' => { 'label' => 'a' } }, 'b' => { 'attributes' => {} }, 'v' => { 'attributes' => {} } } GraphViz2-2.67/t/snapshots/methods_t/mve_edges_0_00000644000175000017500000000025113773672367022027 0ustar osboxesosboxes{ 'a' => { 'b' => [ { 'attributes' => { 'label' => 'a b' }, 'from_port' => '', 'to_port' => '' } ] } } GraphViz2-2.67/t/snapshots/methods_t/undir_dot_input0000644000175000017500000000003613776424562022635 0ustar osboxesosboxes'graph Perl { "x" -- "y" } ' GraphViz2-2.67/t/snapshots/methods_t/mve_edges_1_10000644000175000017500000000046113776426405022025 0ustar osboxesosboxes{ 'a' => { 'b' => [ { 'attributes' => { 'label' => 'a b x' }, 'from_port' => '', 'to_port' => '' }, { 'attributes' => { 'label' => 'a b y' }, 'from_port' => '', 'to_port' => '' } ] } } GraphViz2-2.67/t/snapshots/methods_t/mve_nodes_1_00000644000175000017500000000022414014557440022030 0ustar osboxesosboxes{ 'a' => { 'attributes' => { 'label' => 'a w' } }, 'b' => { 'attributes' => {} }, 'v' => { 'attributes' => {} } } GraphViz2-2.67/t/snapshots/methods_t/mve_nodes_0_00000644000175000017500000000022214014557440022025 0ustar osboxesosboxes{ 'a' => { 'attributes' => { 'label' => 'a' } }, 'b' => { 'attributes' => {} }, 'v' => { 'attributes' => {} } } GraphViz2-2.67/t/snapshots/gen_html_labels_1_t/0000755000175000017500000000000014266245444021374 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_html_labels_1_t/dot_file0000644000175000017500000000152713736760027023111 0ustar osboxesosboxes'digraph Perl { graph [ label="HTML label demo # 1 - Using \\< ... \\>" rankdir="TB" ] node [ shape="oval" ] edge [ color="grey" ] node [ shape="circle" style="filled" ] edge [ arrowsize="4" color="grey" ] "Carnegie" [ shape="circle" ] "Carnegie" [ color="red" shape="circle" ] node [ shape="circle" style="rounded" ] "Murrumbeena" [ color="green" label=Victoria
Australia> shape="doublecircle" ] "Oakleigh" [ color="blue" label=" West Oakleigh| East Oakleigh" shape="record" ] "Murrumbeena" -> "Carnegie" [ arrowsize="2" label=Train
Stroll> ] edge [ arrowsize="1" color="grey" ] "Murrumbeena" -> "Oakleigh":port1 [ color="brown" label=Promenade
Saunter> ] "Murrumbeena" -> "Oakleigh":port2 [ color="green" label=Run
Sprint> ] } ' GraphViz2-2.67/t/snapshots/gen_sub_sub_graph_t/0000755000175000017500000000000014266245444021511 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_sub_sub_graph_t/dot_file0000644000175000017500000000156513741745721023230 0ustar osboxesosboxes'digraph Perl { graph [ label="sub.sub.graph.pl" rankdir="TB" ] node [ shape="oval" ] edge [ color="grey" ] "Carnegie" [ shape="circle" ] "Murrumbeena" [ color="green" shape="doublecircle" ] "Oakleigh" [ color="blue" ] "Murrumbeena" -> "Carnegie" [ arrowsize="2" ] "Murrumbeena" -> "Oakleigh" [ color="brown" ] subgraph "cluster_subgraph_1" { graph [ label="Child" ] node [ color="magenta" shape="diamond" ] "Chadstone" [ shape="hexagon" ] "Waverley" [ color="orange" ] "Chadstone" -> "Waverley" subgraph "cluster_2" { graph [ label="Grandchild" ] node [ color="blue3" shape="triangle" ] "Glen Waverley" [ shape="pentagon" ] "Mount Waverley" [ color="darkslategrey" ] "Glen Waverley" -> "Mount Waverley" } } node [ color="cyan" shape="oval" ] "Malvern" "Prahran" [ shape="trapezium" ] "Malvern" -> "Prahran" "Malvern" -> "Murrumbeena" } ' GraphViz2-2.67/t/snapshots/gen_parse_yacc_t/0000755000175000017500000000000014266245444020777 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_parse_yacc_t/dot_file0000644000175000017500000000210613776427735022520 0ustar osboxesosboxes'digraph Perl { graph [ concentrate="1" rankdir="TB" ] node [ color="blue" shape="oval" ] edge [ color="grey" ] "$accept" [ label=" $accept|{ list\\ $end\\l}" shape="Mrecord" ] "$accept" -> "list":"port1" "expr" [ label=" expr|{ \'(\'\\ expr\\ \')\'\\l| expr\\ \'+\'\\ expr\\l| expr\\ \'-\'\\ expr\\l| expr\\ \'*\'\\ expr\\l| expr\\ \'/\'\\ expr\\l| expr\\ \'%\'\\ expr\\l| expr\\ \'&\'\\ expr\\l| expr\\ \'\\|\'\\ expr\\l| \'-\'\\ expr\\l| LETTER\\l| number\\l}" shape="Mrecord" ] "expr" -> "expr":"port1" "expr" -> "number":"port1" "list" [ label=" list|{ (empty)\\l| list\\ stat\\ \'\\\\n\'\\l| list\\ error\\ \'\\\\n\'\\l}" shape="Mrecord" ] "list" -> "list":"port1" "list" -> "stat":"port1" "number" [ label=" number|{ DIGIT\\l| number\\ DIGIT\\l}" shape="Mrecord" ] "number" -> "number":"port1" "stat" [ label=" stat|{ expr\\l| LETTER\\ \'=\'\\ expr\\l}" shape="Mrecord" ] "stat" -> "expr":"port1" } ' GraphViz2-2.67/t/snapshots/gen_map_3_t/0000755000175000017500000000000014266245444017665 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_map_3_t/dot_file0000644000175000017500000000034313740725234021371 0ustar osboxesosboxes'digraph mainmap { URL = "gen.map.3.1.html"; graph [ rankdir="TB" ] node [ shape="oval" ] edge [ color="grey" ] "source" [ URL="gen.map.3.2.html" ] "destination" "source" -> "destination" [ URL="gen.map.3.3.html" ] } ' GraphViz2-2.67/t/snapshots/gen_macro_4_t/0000755000175000017500000000000014266245444020212 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_macro_4_t/dot_file0000644000175000017500000000106213741745721021721 0ustar osboxesosboxes'digraph Perl { graph [ label="Macro demo 4 - Linked cluster sub-graphs" rankdir="TB" ] edge [ color="grey" ] subgraph "cluster 1" { graph [ label="cluster 1" ] node [ color="magenta" shape="diamond" ] "Chadstone" [ shape="hexagon" ] "Waverley" [ color="orange" ] "Chadstone" -> "Waverley" } subgraph "cluster 2" { graph [ label="cluster 2" ] node [ color="magenta" shape="diamond" ] "Hughesdale" [ shape="hexagon" ] "Notting Hill" [ color="orange" ] "Hughesdale" -> "Notting Hill" } "Chadstone" -> "Notting Hill" [ minlen="2" ] } ' GraphViz2-2.67/t/snapshots/gen_parse_yapp_t/0000755000175000017500000000000014266245444021031 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_parse_yapp_t/dot_file0000644000175000017500000000154013776427736022554 0ustar osboxesosboxes'digraph Perl { graph [ concentrate="1" rankdir="TB" ] node [ color="blue" shape="oval" ] edge [ color="grey" ] "$start" [ label=" $start|{ input\\ $end\\l}" shape="Mrecord" ] "$start" -> "input":"port1" "exp" [ label=" exp|{ NUM\\l| VAR\\l| VAR\\ \'=\'\\ exp\\l| exp\\ \'+\'\\ exp\\l| exp\\ \'-\'\\ exp\\l| exp\\ \'*\'\\ exp\\l| exp\\ \'/\'\\ exp\\l| \'-\'\\ exp\\l| exp\\ \'^\'\\ exp\\l| \'(\'\\ exp\\ \')\'\\l}" shape="Mrecord" ] "exp" -> "exp":"port1" "input" [ label=" input|{ (empty)\\l| input\\ line\\l}" shape="Mrecord" ] "input" -> "input":"port1" "input" -> "line":"port1" "line" [ label=" line|{ \'\\\\n\'\\l| exp\\ \'\\\\n\'\\l| error\\ \'\\\\n\'\\l}" shape="Mrecord" ] "line" -> "exp":"port1" } ' GraphViz2-2.67/t/snapshots/record_old_t/0000755000175000017500000000000014266245444020151 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/record_old_t/dot_file0000644000175000017500000000062613743121220021645 0ustar osboxesosboxes'digraph Perl { graph [ label="Record demo 2 - Nested records using an arrayref of hashrefs as labels" ] node [ shape="record" ] "struct1" [ label=" left| mid dle| right" ] "struct2" [ label=" one| two" ] "struct3" [ label="hello\\nworld|{b|{c| d|e}|f}|g|h" shape="Mrecord" ] "struct1":f1 -> "struct2":f0 [ color="blue" ] "struct1":f2 -> "struct3":here [ color="red" ] } ' GraphViz2-2.67/t/snapshots/gen_cluster_t/0000755000175000017500000000000014266245444020347 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_cluster_t/dot_file0000644000175000017500000000130513741745721022056 0ustar osboxesosboxes'digraph Perl { graph [ clusterrank="local" compound="1" rankdir="TB" ] node [ shape="oval" ] edge [ color="grey" ] subgraph "cluster_Europe" { graph [ bgcolor="grey" label="Europe" ] "London" [ color="blue" ] "Paris" [ color="green" label="City of\\nlurve" ] "London" -> "Paris" "Paris" -> "London" } "New York" [ color="yellow" ] "London" -> "New York" [ label="Far" ] subgraph "cluster_Australia" { graph [ bgcolor="grey" label="Australia" ] "Victoria" [ color="blue" ] "New South Wales" [ color="green" ] "Tasmania" [ color="red" ] "Victoria" -> "New South Wales" "Victoria" -> "Tasmania" } "Victoria" -> "London" [ lhead="cluster_Europe" ltail="cluster_Australia" ] } ' GraphViz2-2.67/t/snapshots/gen_quote_t/0000755000175000017500000000000014266245444020023 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_quote_t/dot_file0000644000175000017500000000067313741675253021543 0ustar osboxesosboxes'digraph Perl { graph [ rankdir="LR" ] node [ shape="oval" ] "Embedded\\nnewline\\nnode\\nname" "Embedded newline label name" [ label="Embedded\\nnewline\\nlabel" ] "Embedded\\ndouble-quote\\nnode\\nname\\n\\"" "Embedded\\double-quote\\label" [ label="Embedded\\ndouble-quote\\nlabel\\n\\"" ] "Line justification 1" [ label="A short line\\rA much longer line" ] "Line justification 2" [ label="A much longer line\\rA short line" ] } ' GraphViz2-2.67/t/snapshots/gen_anonymous_t/0000755000175000017500000000000014266245444020716 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_anonymous_t/dot_file0000644000175000017500000000027113736760027022426 0ustar osboxesosboxes'digraph Perl { graph [ rankdir="TB" ] node [ shape="oval" ] edge [ color="grey" ] "" [ label="" ] "Anonymous label 1" [ label="" ] "Anonymous label 2" [ label="" ] "" -> "" } ' GraphViz2-2.67/t/snapshots/gen_html_labels_2_t/0000755000175000017500000000000014266245444021375 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_html_labels_2_t/dot_file0000644000175000017500000000106013741670365023103 0ustar osboxesosboxes'digraph Perl { graph [ label="HTML label demo # 2 - Using \\<\\ ... \\\\>" ] "Legend" [ label=<
The green node is the start node
Lightblue nodes are for lexeme attributes
Orchid nodes are for lexemes
Golden nodes are for actions
Red nodes are for events
> shape="plaintext" ] } ' GraphViz2-2.67/t/snapshots/gen_record_3_t/0000755000175000017500000000000014266245444020366 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_record_3_t/dot_file0000644000175000017500000000132713750374366022104 0ustar osboxesosboxes'digraph Perl { graph [ label="Record demo 3 - Deeply nested records using nested array-refs" ] "Alphabet" [ label=" a:port_a|{ b:port_b| c|{ d:port_d| e| f|{ g| h:port_h| i| j|{ k| l| m| n:port_n| o| p}| q| r| s:port_s| t}| u| v| w:port_w}| x| y:port_y}| z" shape="record" ] "Alphabet":"port_a" -> "Alphabet":"port_n" [ color="maroon" ] "Alphabet":"port_b" -> "Alphabet":"port_s" [ color="blue" ] "Alphabet":"port_d" -> "Alphabet":"port_w" [ color="red" ] "Alphabet":"port_y" -> "Alphabet":"port_h" [ color="green" ] } ' GraphViz2-2.67/t/snapshots/gen_Heawood_t/0000755000175000017500000000000014266245444020254 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_Heawood_t/dot_file0000644000175000017500000000101013741667372021761 0ustar osboxesosboxes'graph Heawood { graph [ rankdir="TB" ] edge [ color="black" ] node [ color="black" fontname="Arial" height="0.50000" shape="circle" width="0.50000" ] "0" -- "1" "1" -- "2" "2" -- "3" "3" -- "4" "4" -- "5" "5" -- "6" "6" -- "7" "7" -- "8" "8" -- "9" "9" -- "10" "10" -- "11" "11" -- "12" "12" -- "13" "13" -- "0" "0" -- "5" [ len="2.5" ] "2" -- "7" [ len="2.5" ] "4" -- "9" [ len="2.5" ] "6" -- "11" [ len="2.5" ] "8" -- "13" [ len="2.5" ] "10" -- "1" [ len="2.5" ] "12" -- "3" [ len="2.5" ] } ' GraphViz2-2.67/t/snapshots/gen_rank_sub_graph_1_t/0000755000175000017500000000000014266245444022073 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_rank_sub_graph_1_t/dot_file0000644000175000017500000000116013743144323023572 0ustar osboxesosboxes'digraph Perl { graph [ rankdir="TB" ] node [ shape="oval" ] edge [ color="grey" ] "Carnegie" [ shape="circle" ] "Chadstone" [ color="red" shape="circle" ] "Malvern" [ color="green" shape="box" ] "Murrumbeena" [ color="orange" shape="doublecircle" ] "Oakleigh" [ color="blue" ] "Chadstone" -> "Oakleigh" [ arrowhead="odot" ] "Malvern" -> "Carnegie" [ arrowsize="2" ] "Malvern" -> "Oakleigh" [ color="brown" ] subgraph { rank="same" "Malvern" [ color="green" shape="box" ] "Prahran" } subgraph { rank="same" "Oakleigh" [ color="blue" ] "Murrumbeena" [ color="orange" shape="doublecircle" ] } } ' GraphViz2-2.67/t/snapshots/gen_record_2_t/0000755000175000017500000000000014266245444020365 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_record_2_t/dot_file0000644000175000017500000000066313750374366022105 0ustar osboxesosboxes'digraph Perl { graph [ label="Record demo 2 - Nested records using an arrayref of hashrefs as labels" ] node [ shape="record" ] "struct1" [ label=" left| mid dle| right" ] "struct2" [ label=" one| two" ] "struct3" [ label="hello\\nworld|{b|{c\\{\\|\\}\\<\\>\\"\\"| d|e}|f}|g|h" shape="Mrecord" ] "struct1":"f1" -> "struct2":"f0" [ color="blue" ] "struct1":"f2" -> "struct3":"here" [ color="red" ] } ' GraphViz2-2.67/t/snapshots/gen_record_4_t/0000755000175000017500000000000014266245444020367 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_record_4_t/dot_file0000644000175000017500000000057613741720527022104 0ustar osboxesosboxes'digraph Perl { graph [ label="Record demo 4 - Set record shapes in various ways" ] node [ color="magenta" ] "One" [ label="" shape="Mrecord" ] "Two" [ label=" Left| Right" shape="Mrecord" ] "Three" [ color="black" label=" Good| Bad" shape="record" ] "Four" [ label="{Big|Small}" shape="Mrecord" ] "Five" [ label="{Yin|Yang}" shape="record" ] } ' GraphViz2-2.67/t/snapshots/gen_macro_5_t/0000755000175000017500000000000014266245444020213 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_macro_5_t/dot_file0000644000175000017500000000115013741745721021720 0ustar osboxesosboxes'digraph Perl { graph [ compound="true" label="Macro demo 5 - Compound cluster sub-graphs" rankdir="TB" ] edge [ color="grey" ] subgraph "cluster 1" { graph [ label="cluster 1" ] node [ color="magenta" shape="diamond" ] "Chadstone" [ shape="hexagon" ] "Waverley" [ color="orange" ] "Chadstone" -> "Waverley" } subgraph "cluster 2" { graph [ label="cluster 2" ] node [ color="magenta" shape="diamond" ] "Hughesdale" [ shape="hexagon" ] "Notting Hill" [ color="orange" ] "Hughesdale" -> "Notting Hill" } "Chadstone" -> "Notting Hill" [ lhead="cluster 2" ltail="cluster 1" minlen="2" ] } ' GraphViz2-2.67/t/snapshots/gen_class_t/0000755000175000017500000000000014266245444017773 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_class_t/dot_file0000644000175000017500000000226214266245251021501 0ustar osboxesosboxes'graph { graph [ class="graph_root" style="rounded" stylesheet="class-demo-styles.css" ] node [ shape="box" style="rounded" ] subgraph "cluster_match" { graph [ class="match" label="Championship Match" margin="32" ] subgraph "cluster_team1" { graph [ bgcolor="peachpuff" class="team_orange" label="Orange Team" margin="16" ] "Coach O" [ class="coach" fillcolor="white" shape="hexagon" style="filled" ] subgraph { rank="sink" "oplayer1" [ class="player" label="Player O1" ] "oplayer2" [ class="player" label="Player O2" ] "oplayer1" -- "oplayer2" [ class="teammate_of" ] } "Coach O" -- "oplayer1" [ class="coach_of" ] "Coach O" -- "oplayer2" [ class="coach_of" ] } subgraph "cluster_team2" { graph [ bgcolor="lightblue" class="team_blue" label="Blue Team" margin="16" ] "Coach B" [ class="coach" fillcolor="white" shape="hexagon" style="filled" ] subgraph { rank="sink" "bplayer1" [ class="player" label="Player B1" ] "bplayer2" [ class="player" label="Player B2" ] "bplayer1" -- "bplayer2" [ class="teammate_of" ] } "Coach B" -- "bplayer1" [ class="coach_of" ] "Coach B" -- "bplayer2" [ class="coach_of" ] } } } ' GraphViz2-2.67/t/snapshots/gen_parse_regexp_t/0000755000175000017500000000000014266245444021352 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_parse_regexp_t/dot_file0000644000175000017500000000207113776427735023074 0ustar osboxesosboxes'digraph Perl { graph [ rankdir="TB" ] node [ color="blue" shape="oval" ] edge [ color="grey" ] subgraph { rank="same" "26" "28" } subgraph { rank="same" "32" "33" } subgraph { rank="same" "35" "37" } "1" [ label="SBOL /^/" ] "1" -> "2" "17" [ label="END $2" ] "17" -> "30" "2" [ label="START $1" ] "2" -> "4" "20" [ label="START $3" ] "20" -> "22" "22" [ color="black" label="foo" shape="box" ] "22" -> "24" "24" [ label="END $3" ] "24" -> "26" "26" [ label="REPEAT {0,1}" ] "26" -> "28" [ style="dashed" ] "26" -> "30" "28" [ color="black" label="n" shape="box" ] "30" [ label="END $1" ] "30" -> "32" "32" [ label="REPEAT *" ] "32" -> "33" [ style="dashed" ] "32" -> "35" "33" [ color="black" label="x" shape="box" ] "35" [ label="REPEAT {1,2}" ] "35" -> "37" [ style="dashed" ] "35" -> "39" "37" [ color="black" label="y" shape="box" ] "39" [ label="END" ] "4" [ label="" shape="diamond" ] "4" -> "20" "4" -> "5" "5" [ label="START $2" ] "5" -> "7" "7" [ color="red" label="[0-9a-d]" shape="box" ] "7" -> "17" } ' GraphViz2-2.67/t/snapshots/gen_circo_t/0000755000175000017500000000000014266245444017765 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_circo_t/dot_file0000644000175000017500000000033513740710121021457 0ustar osboxesosboxes'digraph Perl { graph [ margin="0.125" page="8.25,10.75" rankdir="TB" rotate="90" size="8.25,10.75" ] node [ shape="oval" ] edge [ color="grey" ] "Here" -> "There" "There" -> "Everywhere" "Everywhere" -> "Here" } ' GraphViz2-2.67/t/snapshots/gen_parse_stt_t/0000755000175000017500000000000014266245444020672 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_parse_stt_t/dot_file0000644000175000017500000001310313776427735022412 0ustar osboxesosboxes'digraph Perl { graph [ rankdir="TB" ] node [ color="blue" shape="oval" ] edge [ color="grey" ] graph [ concentrate="true" rankdir="TB" ] "attribute" [ label="{<[%5cw %3a;]+> /[\\\\w\\ :;]+/}| attribute" shape="record" ] "attribute":"state" -> "attribute":"[%5cw %3a;]+":"w" "attribute":"state" -> "post_attribute":"%7d":"w" "class" [ label="{<[a-z][a-z0-9_]*> /[a-z][a-z0-9_]*/|<%5cs+> /\\\\s+/}| class" shape="record" ] "class":"state" -> "class":"%5cs+":"w" "class":"state" -> "class_daisy_chain":",":"w" "class":"state" -> "edge":"[-%3c=%3e~.]+":"w" "class":"state" -> "start_class_attribute":"%7b":"w" "class":"state" -> "start_group":"%5c(":"w" "class":"state" -> "start_node":"%5c[":"w" "class":"state" -> "start_subclass":"%5c.":"w" "class_attribute" [ label="{<[%5cw %3a;]+> /[\\\\w\\ :;]+/}| class_attribute" shape="record" ] "class_attribute":"state" -> "class_attribute":"[%5cw %3a;]+":"w" "class_attribute":"state" -> "post_class_attribute":"%7d":"w" "class_daisy_chain" [ label="{<,> /,/|<%5cs+> /\\\\s+/}| class_daisy_chain" shape="record" ] "class_daisy_chain":"state" -> "class":"[a-z][a-z0-9_]*":"w" "class_daisy_chain":"state" -> "class_daisy_chain":"%5cs+":"w" "class_daisy_chain":"state" -> "subclass":"%5c.":"w" "daisy_chain" [ label="{<,> /,/|<%5cs+> /\\\\s+/}| daisy_chain" shape="record" ] "daisy_chain":"state" -> "daisy_chain":"%5cs+":"w" "daisy_chain":"state" -> "edge":"[-%3c=%3e~.]+":"w" "daisy_chain":"state" -> "start_node":"%5c[":"w" "edge" [ label="{<[-%3c=%3e~.]+> /[-\\<=\\>~.]+/|<%5cs+> /\\\\s+/}| edge" shape="record" ] "edge":"state" -> "daisy_chain":",":"w" "edge":"state" -> "edge":"%5cs+":"w" "edge":"state" -> "post_group":"%5c)":"w" "edge":"state" -> "start_attribute":"%7b":"w" "edge":"state" -> "start_group":"%5c(":"w" "edge":"state" -> "start_node":"%5c[":"w" "global" [ label="{<%5cs+> /\\\\s+/}| global" shape="record" ] "global":"state" -> "class":"[a-z][a-z0-9_]*":"w" "global":"state" -> "edge":"[-%3c=%3e~.]+":"w" "global":"state" -> "global":"%5cs+":"w" "global":"state" -> "start_class_attribute":"%7b":"w" "global":"state" -> "start_group":"%5c(":"w" "global":"state" -> "start_node":"%5c[":"w" "group" [ label="{<[%5cw.]+%3a> /[\\\\w.]+:/|<%5cs+> /\\\\s+/}| group" shape="record" ] "group":"state" -> "class":"[a-z][a-z0-9_]*":"w" "group":"state" -> "edge":"[-%3c=%3e~.]+":"w" "group":"state" -> "group":"%5cs+":"w" "group":"state" -> "start_node":"%5c[":"w" "node" [ label="{<[%5cw.]+> /[\\\\w.]+/|<%5c[> /\\\\[/|<%5cs+> /\\\\s+/}| node" shape="record" ] "node":"state" -> "node":"%5cs+":"w" "node":"state" -> "post_node":"]":"w" "post_attribute" [ label="{<%5cs+> /\\\\s+/|<%7d> /\\}/}| post_attribute" shape="record" ] "post_attribute":"state" -> "daisy_chain":",":"w" "post_attribute":"state" -> "edge":"[-%3c=%3e~.]+":"w" "post_attribute":"state" -> "post_attribute":"%5cs+":"w" "post_attribute":"state" -> "post_group":"%5c)":"w" "post_attribute":"state" -> "start_node":"%5c[":"w" "post_class_attribute" [ label="{<%5cs+> /\\\\s+/|<%7d> /\\}/}| post_class_attribute" shape="record" ] "post_class_attribute":"state" -> "class":"[a-z][a-z0-9_]*":"w" "post_class_attribute":"state" -> "daisy_chain":",":"w" "post_class_attribute":"state" -> "edge":"[-%3c=%3e~.]+":"w" "post_class_attribute":"state" -> "post_class_attribute":"%5cs+":"w" "post_class_attribute":"state" -> "start_group":"%5c(":"w" "post_class_attribute":"state" -> "start_node":"%5c[":"w" "post_group" [ label="{<%5c)> /\\\\)/|<%5cs+> /\\\\s+/}| post_group" shape="record" ] "post_group":"state" -> "edge":"[-%3c=%3e~.]+":"w" "post_group":"state" -> "post_group":"%5cs+":"w" "post_group":"state" -> "start_group":"%5c(":"w" "post_group":"state" -> "start_node":"%5c[":"w" "post_node" [ label="{<%5cs+> /\\\\s+/|<]> /]/}| post_node" shape="record" ] "post_node":"state" -> "daisy_chain":",":"w" "post_node":"state" -> "edge":"[-%3c=%3e~.]+":"w" "post_node":"state" -> "post_group":"%5c)":"w" "post_node":"state" -> "post_node":"%5cs+":"w" "post_node":"state" -> "start_attribute":"%7b":"w" "post_node":"state" -> "start_group":"%5c(":"w" "post_node":"state" -> "start_node":"%5c[":"w" "start_attribute" [ label="{<%7b> /\\{/}| start_attribute" shape="record" ] "start_attribute":"state" -> "attribute":"[%5cw %3a;]+":"w" "start_class_attribute" [ label="{<%7b> /\\{/}| start_class_attribute" shape="record" ] "start_class_attribute":"state" -> "class_attribute":"[%5cw %3a;]+":"w" "start_group" [ label="{<%5c(> /\\\\(/|<%5cs+> /\\\\s+/}| start_group" shape="record" ] "start_group":"state" -> "edge":"[-%3c=%3e~.]+":"w" "start_group":"state" -> "group":"[%5cw.]+%3a":"w" "start_group":"state" -> "post_group":"%5c)":"w" "start_group":"state" -> "start_group":"%5cs+":"w" "start_node" [ label="{<%5c[> /\\\\[/|<%5cs+> /\\\\s+/}| start_node" shape="record" ] "start_node":"state" -> "node":"[%5cw.]+":"w" "start_node":"state" -> "post_node":"]":"w" "start_node":"state" -> "start_node":"%5cs+":"w" "start_subclass" [ label="{<%5c.> /\\\\./}| start_subclass" shape="record" ] "start_subclass":"state" -> "subclass":"[a-z][a-z0-9_]*":"w" "subclass" [ label="{<[a-z][a-z0-9_]*> /[a-z][a-z0-9_]*/|<%5c.> /\\\\./|<%5cs+> /\\\\s+/}| subclass" shape="record" ] "subclass":"state" -> "class_daisy_chain":",":"w" "subclass":"state" -> "edge":"[-%3c=%3e~.]+":"w" "subclass":"state" -> "node":"%5c[":"w" "subclass":"state" -> "start_class_attribute":"%7b":"w" "subclass":"state" -> "start_group":"%5c(":"w" "subclass":"state" -> "subclass":"%5cs+":"w" } ' GraphViz2-2.67/t/snapshots/gen_trivial_t/0000755000175000017500000000000014266245444020340 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_trivial_t/dot_file0000644000175000017500000000077513736760031022054 0ustar osboxesosboxes'digraph Perl { graph [ rankdir="TB" ] node [ shape="oval" ] edge [ color="grey" ] node [ shape="circle" style="filled" ] edge [ arrowsize="4" color="grey" ] "Carnegie" [ shape="circle" ] "Carnegie" [ color="red" shape="circle" ] node [ shape="circle" style="rounded" ] "Murrumbeena" [ color="green" shape="doublecircle" ] "Oakleigh" [ color="blue" shape="oval" ] "Murrumbeena" -> "Carnegie" [ arrowsize="2" ] edge [ arrowsize="4" color="grey" ] "Murrumbeena" -> "Oakleigh" [ color="brown" ] } ' GraphViz2-2.67/t/snapshots/gen_macro_2_t/0000755000175000017500000000000014266245444020210 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_macro_2_t/dot_file0000644000175000017500000000106513741745721021722 0ustar osboxesosboxes'digraph Perl { graph [ label="Macro demo 2 - Linked non-cluster sub-graphs" rankdir="TB" ] node [ shape="oval" ] edge [ color="grey" ] subgraph "One" { graph [ label="One" ] node [ color="magenta" shape="diamond" ] "Chadstone" [ shape="hexagon" ] "Waverley" [ color="orange" ] "Chadstone" -> "Waverley" } subgraph "Two" { graph [ label="Two" ] node [ color="magenta" shape="diamond" ] "Hughesdale" [ shape="hexagon" ] "Notting Hill" [ color="orange" ] "Hughesdale" -> "Notting Hill" } "Chadstone" -> "Notting Hill" [ minlen="2" ] } ' GraphViz2-2.67/t/snapshots/gen_unnamed_sub_graph_t/0000755000175000017500000000000014266245444022347 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_unnamed_sub_graph_t/dot_file0000644000175000017500000000071313741745774024070 0ustar osboxesosboxes'digraph Perl { graph [ label="Named and unnamed subgraphs" rankdir="TB" ] edge [ color="grey" ] subgraph { graph [ label="Subgraph One" ] node [ color="magenta" shape="diamond" ] "Chadstone" [ shape="hexagon" ] "Waverley" [ color="orange" ] "Chadstone" -> "Waverley" } subgraph { "Glen Waverley" [ color="blue3" shape="pentagon" ] "Mount Waverley" [ color="darkslategrey" shape="rectangle" ] "Glen Waverley" -> "Mount Waverley" } } ' GraphViz2-2.67/t/snapshots/gen_record_1_t/0000755000175000017500000000000014266245444020364 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_record_1_t/dot_file0000644000175000017500000000063513740710121022061 0ustar osboxesosboxes'digraph Perl { graph [ label="Record demo 1 - Nested records using strings as labels" ] node [ shape="record" ] edge [ color="grey" ] "struct1::x" [ label=" left| mid dle| right" ] "struct2" [ label=" one| two" ] "struct3" [ label="hello\\nworld |{ b |{c| d|e}| f}| g | h" ] "struct1::x":f1 -> "struct2":f0 [ color="blue" ] "struct1::x":f2 -> "struct3":here [ color="red" ] } ' GraphViz2-2.67/t/snapshots/gen_utf8_1_t/0000755000175000017500000000000014266245444017774 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_utf8_1_t/dot_file0000644000175000017500000000142613736760031021502 0ustar osboxesosboxes"digraph Perl { graph [ rankdir=\"TB\" ] node [ shape=\"oval\" ] edge [ color=\"grey\" ] \"Zero\" [ label=\"The Orient Express\" ] \"One\" [ label=\"Reichwaldstra\x{df}e\" ] \"Two\" [ label=\"B\x{f6}hme\" ] \"Three\" [ label=\"\x{28e} \x{28f} \x{290} \x{291} \x{292} \x{293} \x{299} \x{29a}\" ] \"Four\" [ label=\"\x{3a0}\x{3b7}\x{3bb}\x{3b7}\x{3ca}\x{3ac}\x{3b4}\x{3b5}\x{3c9} \x{1f08}\x{3c7}\x{3b9}\x{3bb}\x{1fc6}\x{3bf}\x{3c2}\" ] \"Five\" [ label=\"\x{394}\x{394}\x{394}\x{394}\x{394}\x{394}\x{394}\x{394}\x{394}\x{394}\x{394}\x{394}\x{394}\x{394}\x{394}\x{394}\x{394}\" ] \"Zero\" -> \"One\" \"Zero\" -> \"Three\" \"One\" -> \"Two\" \"Three\" -> \"Four\" \"Two\" -> \"Five\" [ label=\"Label has a \x{2603}\" ] \"Four\" -> \"Five\" [ label=\"Label has a \x{2714}\" ] } " GraphViz2-2.67/t/snapshots/gen_macro_1_t/0000755000175000017500000000000014266245444020207 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_macro_1_t/dot_file0000644000175000017500000000075113741745721021722 0ustar osboxesosboxes'digraph Perl { graph [ label="Macro demo 1 - Non-cluster sub-graphs" rankdir="TB" ] edge [ color="grey" ] subgraph "One" { graph [ label="One" ] node [ color="magenta" shape="diamond" ] "Chadstone" [ shape="hexagon" ] "Waverley" [ color="orange" ] "Chadstone" -> "Waverley" } subgraph "Two" { graph [ label="Two" ] node [ color="magenta" shape="diamond" ] "Hughesdale" [ shape="hexagon" ] "Notting Hill" [ color="orange" ] "Hughesdale" -> "Notting Hill" } } ' GraphViz2-2.67/t/snapshots/gen_jointed_edges_t/0000755000175000017500000000000014266245444021471 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_jointed_edges_t/dot_file0000644000175000017500000000115213736760027023200 0ustar osboxesosboxes'digraph Perl { graph [ rankdir="TB" ] node [ shape="oval" style="filled" ] edge [ color="grey" ] "Carnegie" [ color="aquamarine" ] "Murrumbeena" [ color="bisque" ] "Oakleigh" [ color="blueviolet" ] "one" [ color="grey" shape="point" width="0" ] "Murrumbeena" -> "one" [ arrowhead="none" ] "Carnegie" -> "one" [ arrowhead="none" ] "one" -> "Oakleigh" "Ashburton" [ color="lawngreen" ] "Chadstone" [ color="coral" ] "Waverley" [ color="crimson" ] "two" [ color="grey" shape="point" width="0" ] "Ashburton" -> "two" [ arrowhead="none" ] "Chadstone" -> "two" [ arrowhead="none" ] "two" -> "Waverley" } ' GraphViz2-2.67/t/snapshots/gen_macro_3_t/0000755000175000017500000000000014266245444020211 5ustar osboxesosboxesGraphViz2-2.67/t/snapshots/gen_macro_3_t/dot_file0000644000175000017500000000077513741745721021732 0ustar osboxesosboxes'digraph Perl { graph [ label="Macro demo 3 - Cluster sub-graphs" rankdir="TB" ] edge [ color="grey" ] subgraph "cluster 1" { graph [ label="cluster 1" ] node [ color="magenta" shape="diamond" ] "Chadstone" [ shape="hexagon" ] "Waverley" [ color="orange" ] "Chadstone" -> "Waverley" } subgraph "cluster 2" { graph [ label="cluster 2" ] node [ color="magenta" shape="diamond" ] "Hughesdale" [ shape="hexagon" ] "Notting Hill" [ color="orange" ] "Hughesdale" -> "Notting Hill" } } ' GraphViz2-2.67/t/gen.macro.1.t0000644000175000017500000000220513741671122015655 0ustar osboxesosboxes# Annotation: Demonstrates non-cluster subgraphs via a macro. use strict; use warnings; use File::Spec; use GraphViz2; sub macro { my($graph, $name, $node_1, $node_2) = @_; $graph->push_subgraph( name => $name, graph => {label => $name}, node => {color => 'magenta', shape => 'diamond'}, ); $graph->add_node(name => $node_1, shape => 'hexagon'); $graph->add_node(name => $node_2, color => 'orange'); $graph->add_edge(from => $node_1, to => $node_2); $graph->pop_subgraph; } my $id = '1'; my $graph = GraphViz2->new( edge => {color => 'grey'}, global => {directed => 1}, graph => {label => "Macro demo $id - Non-cluster sub-graphs", rankdir => 'TB'}, ); macro($graph, 'One', 'Chadstone', 'Waverley'); macro($graph, 'Two', 'Hughesdale', 'Notting Hill'); if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec -> catfile('html', "macro.$id.$format"); $graph -> run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/gen.record.2.t0000644000175000017500000000252113743156705016043 0ustar osboxesosboxes# Annotation: Nested records using an arrayref of hashrefs as labels. use strict; use warnings; use File::Spec; use GraphViz2; my $id = '2'; my $graph = GraphViz2->new( global => {directed => 1, combine_node_and_port => 0}, graph => {label => "Record demo $id - Nested records using an arrayref of hashrefs as labels"}, node => {shape => 'record'}, ); $graph->add_node(name => 'struct1', label => ' left| mid dle| right'); $graph->add_node(name => 'struct2', label => ' one| two'); $graph->add_node(name => 'struct3', label => [ { text => "hello\\nworld" }, [ { text => 'b' }, [ { text => 'c{|}<>""' }, { text => 'd', port => 'here' }, { text => 'e' }, ], { text => 'f' }, ], { text => 'g' }, { text => 'h' }, ]); $graph->add_edge(from => 'struct1', tailport => 'f1', to => 'struct2', headport => 'f0', color => 'blue'); $graph->add_edge(from => 'struct1', tailport => 'f2', to => 'struct3', headport => 'here', color => 'red'); if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec -> catfile('html', "record.$id.$format"); $graph -> run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/gen.html.labels.2.t0000644000175000017500000000224013741670361016765 0ustar osboxesosboxes# Annotation: Demonstrates a HTML label with a table. use strict; use warnings; use File::Spec; use GraphViz2; my $id = 2; my $graph = GraphViz2->new( global => {directed => 1}, graph => { label => "HTML label demo # $id - Using \\<\\ ... " . "\\\\>", }, ); $graph->add_node( label => q| <
The green node is the start node
Lightblue nodes are for lexeme attributes
Orchid nodes are for lexemes
Golden nodes are for actions
Red nodes are for events
> |, name => 'Legend', shape => 'plaintext', ); if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec -> catfile('html', "html.labels.$id.$format"); $graph -> run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/gen.circo.t0000644000175000017500000000167313741667344015537 0ustar osboxesosboxes# Annotation: Demonstrates calling circo instead of dot. use strict; use warnings; use File::Spec; use GraphViz2; my $graph = GraphViz2->new( edge => {color => 'grey'}, global => {directed => 1}, graph => { rankdir => 'TB', page => '8.25,10.75', rotate => '90', margin => '0.125', size => '8.25,10.75' }, node => {shape => 'oval'}, ); $graph -> add_edge(from => 'Here', to => 'There'); $graph -> add_edge(from => 'There', to => 'Everywhere'); $graph -> add_edge(from => 'Everywhere', to => 'Here'); if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec -> catfile('html', "circo.$format"); $graph->run(driver => 'circo', format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/gen.html.labels.3.t0000644000175000017500000000233213741670550016770 0ustar osboxesosboxes# Annotation: Demonstrates HTML labels with newlines and double-quotes. use strict; use warnings; use File::Spec; use GraphViz2; my $id = 3; my $graph = GraphViz2->new( edge => {color => 'grey'}, global => {directed => 1}, graph => {label => "HTML label demo # $id - Fixing newlines", rankdir => 'TB'}, node => {shape => 'oval'}, ); $graph -> add_node(name => 'One', label => ' Blue
> '); $graph -> add_node(name => 'Two', label => '< Two
Green
>'); $graph -> add_node(name => 'Three', color => 'red', label => ' <
Three
Red
> '); $graph -> add_node(name => 'Four', color => 'magenta', label => '<
Four
magenta
>'); if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec -> catfile('html', "html.labels.$id.$format"); $graph -> run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/gen.plaintext.t0000644000175000017500000000111213741674743016435 0ustar osboxesosboxes# Annotation: Demonstrates a graph with a 'plaintext' shape. use strict; use warnings; use File::Spec; use GraphViz2; my $graph = GraphViz2->new; $graph->add_node(name => 'Murrumbeena', shape => 'plaintext'); if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec -> catfile('html', "plaintext.$format"); $graph -> run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/gen.jointed.edges.t0000644000175000017500000000335013741732370017144 0ustar osboxesosboxes# Annotation: Demonstrates Y-shaped edges between 3 nodes. use strict; use warnings; use File::Spec; use GraphViz2; my %junction = (color => 'grey', shape => 'point', width => 0); # Note: arrowhead is case-sensitive (i.e. arrowHead does not work). my %headless_arrow = (arrowhead => 'none'); my $graph = GraphViz2->new( edge => {color => 'grey'}, global => {directed => 1}, graph => {rankdir => 'TB'}, node => {shape => 'oval', style => 'filled'}, ); # Node set 1: $graph->add_node(name => 'Carnegie', color => 'aquamarine'); $graph->add_node(name => 'Murrumbeena', color => 'bisque'); $graph->add_node(name => 'Oakleigh', color => 'blueviolet'); $graph->add_node(name => 'one', %junction); # 1st of 2 junction nodes $graph->add_edge(from => 'Murrumbeena', to => 'one', %headless_arrow); $graph->add_edge(from => 'Carnegie', to => 'one', %headless_arrow); $graph->add_edge(from => 'one', to => 'Oakleigh'); # Node set 2: $graph->add_node(name => 'Ashburton', color => 'lawngreen'); $graph->add_node(name => 'Chadstone', color => 'coral'); $graph->add_node(name => 'Waverley', color => 'crimson'); $graph->add_node(name => 'two', %junction); # 2nd of 2 junction nodes $graph->add_edge(from => 'Ashburton', to => 'two', %headless_arrow); $graph->add_edge(from => 'Chadstone', to => 'two', %headless_arrow); $graph->add_edge(from => 'two', to => 'Waverley'); if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec->catfile('html', "jointed.edges.$format"); $graph->run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/gen.macro.5.t0000644000175000017500000000247513741673365015705 0ustar osboxesosboxes# Annotation: Demonstrates compound cluster subgraphs via a macro. use strict; use warnings; use File::Spec; use GraphViz2; sub macro { my ($graph, $name, $node_1, $node_2) = @_; $graph->push_subgraph( name => $name, graph => {label => $name}, node => {color => 'magenta', shape => 'diamond'}, ); $graph->add_node(name => $node_1, shape => 'hexagon'); $graph->add_node(name => $node_2, color => 'orange'); $graph->add_edge(from => $node_1, to => $node_2); $graph->pop_subgraph; } my $id = '5'; my $graph = GraphViz2->new( edge => {color => 'grey'}, global => {directed => 1}, graph => { compound => 'true', label => "Macro demo $id - Compound cluster sub-graphs", rankdir => 'TB', }, ); macro($graph, 'cluster 1', 'Chadstone', 'Waverley'); macro($graph, 'cluster 2', 'Hughesdale', 'Notting Hill'); $graph->add_edge( from => 'Chadstone', to => 'Notting Hill', lhead => 'cluster 2', ltail => 'cluster 1', minlen => 2, ); if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec -> catfile('html', "macro.$id.$format"); $graph -> run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/sample.stt.1.dat0000644000175000017500000000017613730646160016412 0ustar osboxesosboxes['foo', 'b', 'bar'], ['foo', '.', 'foo'], ['bar', 'a', 'foo'], ['bar', 'b', 'bar'], ['bar', 'c', 'baz'], ['baz', '.', 'baz'], GraphViz2-2.67/t/gen.map.3.t0000644000175000017500000000307213741673622015345 0ustar osboxesosboxes# Annotation: Demonstrates a graph with a 'plaintext' shape. use strict; use warnings; use File::Spec; use GraphViz2; my $id = 3; my $html_template = <<'EOF'; Demo %1$s - A server-side image map

Demo %1$s - A server-side image map

EOF my $file_main = "gen.map.$id.1.html"; my $file_2 = "gen.map.$id.2.html"; my $file_3 = "gen.map.$id.3.html"; my $graph = GraphViz2->new( edge => {color => 'grey'}, global => { directed => 1, name => 'mainmap', }, graph => {rankdir => 'TB'}, im_meta => { URL => $file_main, # Note: URL must be in caps. }, node => {shape => 'oval'}, ); $graph->add_node(name => 'source', URL => $file_2); $graph->add_node(name => 'destination'); $graph->add_edge(from => 'source', to => 'destination', URL => $file_3); if (@ARGV) { my($format) = shift || 'png'; my $output_file = shift || "map.$id.$format"; (my $volume, my $dirname) = File::Spec->splitpath($output_file); my($im_format) = shift || 'imap'; my $im_output_file = shift || File::Spec->catpath($volume, $dirname, "gen.map.$id.map"); $graph->run(format => $format, output_file => $output_file, im_format => $im_format, im_output_file => $im_output_file); for ($file_main, $file_2, $file_3) { open my $fh, '>', File::Spec->catpath($volume, $dirname, $_) or die "$_: $!"; print $fh sprintf $html_template, $_; } } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/gen.parse.yapp.t0000644000175000017500000000152513743727402016510 0ustar osboxesosboxes# Annotation: Demonstrates graphing a yapp-style grammar. use strict; use warnings; use File::Spec; use GraphViz2; use GraphViz2::Parse::Yapp; my $graph = GraphViz2->new( edge => {color => 'grey'}, global => {directed => 1, combine_node_and_port => 0}, graph => {concentrate => 1, rankdir => 'TB'}, node => {color => 'blue', shape => 'oval'}, ); my $g = GraphViz2::Parse::Yapp->new(graph => $graph); $g->create(file_name => File::Spec -> catfile('t', 'calc.output')); if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec -> catfile('html', "parse.yapp.$format"); $graph->run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/methods.t0000644000175000017500000000516214014556366015323 0ustar osboxesosboxesuse strict; use utf8; use warnings; use warnings qw(FATAL utf8); # Fatalize encoding glitches. use File::Spec; use File::Temp; use GraphViz2; use Graph; use Test::More; use Test::Snapshot; # ------------------------------------------------ # The EXLOCK option is for BSD-based systems. my($temp_dir) = File::Temp -> newdir('temp.XXXX', CLEANUP => 1, EXLOCK => 0, TMPDIR => 1); my $GraphViz2 = GraphViz2->new( im_meta => {URL => 'http://savage.net.au/maps/demo.4.html'} ); my @methods = ( [ add_node => { args => [ name => 'TestNode1', label => 'n1' ] } ], [ add_edge => { args => [ from => 'TestNode1', to => '' ] } ], [ default_subgraph => { } ], [ push_subgraph => { args => [ name => 'subgraph_test', edge => {}, graph => { bgcolor => 'grey', label => 'subgraph_test' } ] } ], [ pop_subgraph => { } ], [ valid_attributes => { } ], [ run_map => { subname => 'run', args => [ format => 'png', output_file => File::Spec -> catfile($temp_dir, 'test_more_run_map.png'), im_output_file => File::Spec -> catfile($temp_dir, 'test_more_run_map.map'), im_format => 'cmapx', ], } ], [ run_mapless => { subname => 'run', args => [ format => 'png', output_file => File::Spec -> catfile($temp_dir, 'test_more_run_mapless.png'), ], } ], ); foreach my $tuple ( @methods ) { my ($sub, $data) = @$tuple; my $subname = GraphViz2::_dor($data->{subname}, $sub); can_ok( $GraphViz2, $subname ); ok $GraphViz2->$subname( @{ $data->{args} || [] } ), "Run $subname with -> " . ((explain $data->{args})[0] || '') ; } is GraphViz2::escape_some_chars(q{\\\\"}, '\\{\\}\\|<>\\s"'), '\\\\\\"', 'quoting'; for ([0,0], [0,1], [1,0], [1,1]) { my ($is_multiv, $is_multie) = @$_; my ($v_add, $v_attr, $e_attr) = qw(add_vertex set_vertex_attribute set_edge_attribute); $v_add .= '_by_id' if $is_multiv; $v_attr .= '_by_id' if $is_multiv; $e_attr .= '_by_id' if $is_multie; my $g = Graph->new(multiedged => $is_multie, multivertexed => $is_multiv); $g->$v_add(@$_) for $is_multiv ? ([ qw(v w) ], [ qw(v z) ]) : [ qw(v) ]; $g->$v_attr(@$_, graphviz => { label => "@$_" }) for $is_multiv ? ([ qw(a w) ], [ qw(a z) ]) : [ qw(a) ]; $g->$e_attr(@$_, graphviz => { label => "@$_" }) for $is_multie ? ([ qw(a b x) ], [ qw(a b y) ]) : [ qw(a b) ]; my $gv = GraphViz2->from_graph($g); is_deeply_snapshot $gv->node_hash, "mve nodes $is_multiv $is_multie"; is_deeply_snapshot $gv->edge_hash, "mve edges $is_multiv $is_multie"; } { my $g = Graph->new(directed => 0, edges => [[qw(x y)]]); my $gv = GraphViz2->from_graph($g); is_deeply_snapshot $gv->dot_input, "undir dot_input"; } done_testing; GraphViz2-2.67/t/calc.output0000644000175000017500000002057113730646160015654 0ustar osboxesosboxesConflicts: ---------- Conflict in state 11 between rule 13 and token '-' resolved as reduce. Conflict in state 11 between rule 13 and token '^' resolved as shift. Conflict in state 11 between rule 13 and token '*' resolved as reduce. Conflict in state 11 between rule 13 and token '+' resolved as reduce. Conflict in state 11 between rule 13 and token '/' resolved as reduce. Conflict in state 21 between rule 10 and token '-' resolved as reduce. Conflict in state 21 between rule 10 and token '^' resolved as shift. Conflict in state 21 between rule 10 and token '*' resolved as shift. Conflict in state 21 between rule 10 and token '+' resolved as reduce. Conflict in state 21 between rule 10 and token '/' resolved as shift. Conflict in state 22 between rule 9 and token '-' resolved as reduce. Conflict in state 22 between rule 9 and token '^' resolved as shift. Conflict in state 22 between rule 9 and token '*' resolved as shift. Conflict in state 22 between rule 9 and token '+' resolved as reduce. Conflict in state 22 between rule 9 and token '/' resolved as shift. Conflict in state 23 between rule 12 and token '-' resolved as reduce. Conflict in state 23 between rule 12 and token '^' resolved as shift. Conflict in state 23 between rule 12 and token '*' resolved as reduce. Conflict in state 23 between rule 12 and token '+' resolved as reduce. Conflict in state 23 between rule 12 and token '/' resolved as reduce. Conflict in state 24 between rule 14 and token '-' resolved as reduce. Conflict in state 24 between rule 14 and token '^' resolved as shift. Conflict in state 24 between rule 14 and token '*' resolved as reduce. Conflict in state 24 between rule 14 and token '+' resolved as reduce. Conflict in state 24 between rule 14 and token '/' resolved as reduce. Conflict in state 25 between rule 11 and token '-' resolved as reduce. Conflict in state 25 between rule 11 and token '^' resolved as shift. Conflict in state 25 between rule 11 and token '*' resolved as reduce. Conflict in state 25 between rule 11 and token '+' resolved as reduce. Conflict in state 25 between rule 11 and token '/' resolved as reduce. Conflict in state 27 between rule 8 and token '-' resolved as shift. Conflict in state 27 between rule 8 and token '^' resolved as shift. Conflict in state 27 between rule 8 and token '*' resolved as shift. Conflict in state 27 between rule 8 and token '+' resolved as shift. Conflict in state 27 between rule 8 and token '/' resolved as shift. Rules: ------ 0: $start -> input $end 1: input -> /* empty */ 2: input -> input line 3: line -> '\n' 4: line -> exp '\n' 5: line -> error '\n' 6: exp -> NUM 7: exp -> VAR 8: exp -> VAR '=' exp 9: exp -> exp '+' exp 10: exp -> exp '-' exp 11: exp -> exp '*' exp 12: exp -> exp '/' exp 13: exp -> '-' exp 14: exp -> exp '^' exp 15: exp -> '(' exp ')' States: ------- State 0: $start -> . input $end (Rule 0) $default reduce using rule 1 (input) input go to state 1 State 1: $start -> input . $end (Rule 0) input -> input . line (Rule 2) $end shift, and go to state 4 '(' shift, and go to state 7 '-' shift, and go to state 2 '\n' shift, and go to state 5 NUM shift, and go to state 6 VAR shift, and go to state 8 error shift, and go to state 9 exp go to state 3 line go to state 10 State 2: exp -> '-' . exp (Rule 13) '(' shift, and go to state 7 '-' shift, and go to state 2 NUM shift, and go to state 6 VAR shift, and go to state 8 exp go to state 11 State 3: line -> exp . '\n' (Rule 4) exp -> exp . '+' exp (Rule 9) exp -> exp . '-' exp (Rule 10) exp -> exp . '*' exp (Rule 11) exp -> exp . '/' exp (Rule 12) exp -> exp . '^' exp (Rule 14) '*' shift, and go to state 17 '+' shift, and go to state 13 '-' shift, and go to state 12 '/' shift, and go to state 15 '\n' shift, and go to state 14 '^' shift, and go to state 16 State 4: $start -> input $end . (Rule 0) $default accept State 5: line -> '\n' . (Rule 3) $default reduce using rule 3 (line) State 6: exp -> NUM . (Rule 6) $default reduce using rule 6 (exp) State 7: exp -> '(' . exp ')' (Rule 15) '(' shift, and go to state 7 '-' shift, and go to state 2 NUM shift, and go to state 6 VAR shift, and go to state 8 exp go to state 18 State 8: exp -> VAR . (Rule 7) exp -> VAR . '=' exp (Rule 8) '=' shift, and go to state 19 $default reduce using rule 7 (exp) State 9: line -> error . '\n' (Rule 5) '\n' shift, and go to state 20 State 10: input -> input line . (Rule 2) $default reduce using rule 2 (input) State 11: exp -> exp . '+' exp (Rule 9) exp -> exp . '-' exp (Rule 10) exp -> exp . '*' exp (Rule 11) exp -> exp . '/' exp (Rule 12) exp -> '-' exp . (Rule 13) exp -> exp . '^' exp (Rule 14) '^' shift, and go to state 16 $default reduce using rule 13 (exp) State 12: exp -> exp '-' . exp (Rule 10) '(' shift, and go to state 7 '-' shift, and go to state 2 NUM shift, and go to state 6 VAR shift, and go to state 8 exp go to state 21 State 13: exp -> exp '+' . exp (Rule 9) '(' shift, and go to state 7 '-' shift, and go to state 2 NUM shift, and go to state 6 VAR shift, and go to state 8 exp go to state 22 State 14: line -> exp '\n' . (Rule 4) $default reduce using rule 4 (line) State 15: exp -> exp '/' . exp (Rule 12) '(' shift, and go to state 7 '-' shift, and go to state 2 NUM shift, and go to state 6 VAR shift, and go to state 8 exp go to state 23 State 16: exp -> exp '^' . exp (Rule 14) '(' shift, and go to state 7 '-' shift, and go to state 2 NUM shift, and go to state 6 VAR shift, and go to state 8 exp go to state 24 State 17: exp -> exp '*' . exp (Rule 11) '(' shift, and go to state 7 '-' shift, and go to state 2 NUM shift, and go to state 6 VAR shift, and go to state 8 exp go to state 25 State 18: exp -> exp . '+' exp (Rule 9) exp -> exp . '-' exp (Rule 10) exp -> exp . '*' exp (Rule 11) exp -> exp . '/' exp (Rule 12) exp -> exp . '^' exp (Rule 14) exp -> '(' exp . ')' (Rule 15) ')' shift, and go to state 26 '*' shift, and go to state 17 '+' shift, and go to state 13 '-' shift, and go to state 12 '/' shift, and go to state 15 '^' shift, and go to state 16 State 19: exp -> VAR '=' . exp (Rule 8) '(' shift, and go to state 7 '-' shift, and go to state 2 NUM shift, and go to state 6 VAR shift, and go to state 8 exp go to state 27 State 20: line -> error '\n' . (Rule 5) $default reduce using rule 5 (line) State 21: exp -> exp . '+' exp (Rule 9) exp -> exp . '-' exp (Rule 10) exp -> exp '-' exp . (Rule 10) exp -> exp . '*' exp (Rule 11) exp -> exp . '/' exp (Rule 12) exp -> exp . '^' exp (Rule 14) '*' shift, and go to state 17 '/' shift, and go to state 15 '^' shift, and go to state 16 $default reduce using rule 10 (exp) State 22: exp -> exp . '+' exp (Rule 9) exp -> exp '+' exp . (Rule 9) exp -> exp . '-' exp (Rule 10) exp -> exp . '*' exp (Rule 11) exp -> exp . '/' exp (Rule 12) exp -> exp . '^' exp (Rule 14) '*' shift, and go to state 17 '/' shift, and go to state 15 '^' shift, and go to state 16 $default reduce using rule 9 (exp) State 23: exp -> exp . '+' exp (Rule 9) exp -> exp . '-' exp (Rule 10) exp -> exp . '*' exp (Rule 11) exp -> exp . '/' exp (Rule 12) exp -> exp '/' exp . (Rule 12) exp -> exp . '^' exp (Rule 14) '^' shift, and go to state 16 $default reduce using rule 12 (exp) State 24: exp -> exp . '+' exp (Rule 9) exp -> exp . '-' exp (Rule 10) exp -> exp . '*' exp (Rule 11) exp -> exp . '/' exp (Rule 12) exp -> exp . '^' exp (Rule 14) exp -> exp '^' exp . (Rule 14) '^' shift, and go to state 16 $default reduce using rule 14 (exp) State 25: exp -> exp . '+' exp (Rule 9) exp -> exp . '-' exp (Rule 10) exp -> exp . '*' exp (Rule 11) exp -> exp '*' exp . (Rule 11) exp -> exp . '/' exp (Rule 12) exp -> exp . '^' exp (Rule 14) '^' shift, and go to state 16 $default reduce using rule 11 (exp) State 26: exp -> '(' exp ')' . (Rule 15) $default reduce using rule 15 (exp) State 27: exp -> VAR '=' exp . (Rule 8) exp -> exp . '+' exp (Rule 9) exp -> exp . '-' exp (Rule 10) exp -> exp . '*' exp (Rule 11) exp -> exp . '/' exp (Rule 12) exp -> exp . '^' exp (Rule 14) '*' shift, and go to state 17 '+' shift, and go to state 13 '-' shift, and go to state 12 '/' shift, and go to state 15 '^' shift, and go to state 16 $default reduce using rule 8 (exp) Summary: -------- Number of rules : 16 Number of terminals : 14 Number of non-terminals : 4 Number of states : 28 GraphViz2-2.67/t/gen.class.t0000644000175000017500000001255314266245251015534 0ustar osboxesosboxes# Annotation: Demonstrates each kind of 'class' attribute # # 'class' attributes appear in the 'svg' output format. # # See comments at the end for examples of how these classes can be used. use strict; use warnings; use File::Spec; use GraphViz2; my $graph = GraphViz2->new( global => { directed => 0, name => '', }, graph => { # Set a class on the overall graph: class => 'graph_root', style => 'rounded', stylesheet => 'class-demo-styles.css', }, node => { shape => 'box', style => 'rounded', }, label => 'Demo of SVG class attributes', ); $graph->push_subgraph( name => 'cluster_match', graph => { label => 'Championship Match', # Setting a class on a cluster: class => 'match', margin => 32, }, ); # Set a class on a subcluster: $graph->push_subgraph( name => 'cluster_team1', graph => { label => 'Orange Team', class => 'team_orange', margin => 16, bgcolor => 'peachpuff', }, ); $graph->add_node( name => 'Coach O', class => 'coach', # This node's class='coach'; others below use 'player' shape => 'hexagon', style => 'filled', fillcolor => 'white', ); # Place all players at the same rank: $graph->push_subgraph(subgraph => {rank => 'sink'}); $graph->add_node(name => 'oplayer1', label => 'Player O1', class => 'player'); $graph->add_node(name => 'oplayer2', label => 'Player O2', class => 'player'); # For edges connecting players, class='teammate_of'; $graph->add_edge(from => 'oplayer1', to => 'oplayer2', class => 'teammate_of'); $graph->pop_subgraph; # End of same-rank group # For edges between coach and players, class='coach_of' $graph->add_edge(from => 'Coach O', to => 'oplayer1', class => 'coach_of'); $graph->add_edge(from => 'Coach O', to => 'oplayer2', class => 'coach_of'); $graph->pop_subgraph; # End of team1 cluster # Render the second team similarly: $graph->push_subgraph( name => 'cluster_team2', graph => { label => 'Blue Team', class => 'team_blue', margin => 16, bgcolor => 'lightblue', }, ); $graph->add_node( name => 'Coach B', class => 'coach', shape => 'hexagon', style => 'filled', fillcolor => 'white', ); # Place all players at the same rank: $graph->push_subgraph(subgraph => {rank => 'sink'}); $graph->add_node(name => 'bplayer1', label => 'Player B1', class => 'player'); $graph->add_node(name => 'bplayer2', label => 'Player B2', class => 'player'); $graph->add_edge(from => 'bplayer1', to => 'bplayer2', class => 'teammate_of'); $graph->pop_subgraph; # End of same-rank group $graph->add_edge(from => 'Coach B', to => 'bplayer1', class => 'coach_of'); $graph->add_edge(from => 'Coach B', to => 'bplayer2', class => 'coach_of'); $graph->pop_subgraph; # End of team2 cluster $graph->pop_subgraph; # End of match cluster if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec -> catfile('html', "class.$format"); $graph -> run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } =begin comment Below is an example of a stylesheet which can be applied to the SVG output of this diagram. To use it: 1. Run this script with the 'svg' file format parameter: $ perl t/gen.class.t svg A file 'class.svg' will be produced which contains a reference to a file in the same directory named 'class-demo-styles.css'. (This name is set in the 'stylesheet' directive above.) 2. Copy the CSS content below into a file in the same directory, using the mentioned filename (class-demo-styles.css). 3. Load the SVG file in an appropriate viewer. The style declarations in the CSS file will modify the default appearance of several parts of the diagram: a. The entire diagram will have a subtle gradient background. b. Each team's panel/cluster will have a custom drop shadow. c. The "Coach" hexagons will be made semi-transparent, letting their team colors show through. d. The edges will have different dash patterns, based on the type of relationship they represent. e. Various text strings will be re-styled as italic or bold italic. (This particular effect can *also* be achieved by sending certain parameters through the Graphviz diagram construction process. Depending on the specific scenario, CSS styles may provide a more convenient way to achieve this customization.) CSS content appears between these lines: ---------------------------------------- /* Add a light gradient background to the whole SVG document: */ svg { background-image: linear-gradient(#eee, #def); } /* Make the graph's default base white rectangle invisible so the above gradient shows through. (It's the only polygon which is a direct descendant of g.graph_root) */ .graph_root > polygon { opacity: 0; } /* Styles for the main cluster: */ .match { font-style: italic; font-weight: bold; } /* Subcluster accents */ .team_orange path { filter: drop-shadow(4px 4px 2px orange); } .team_blue path { filter: drop-shadow(-4px 4px 2px blue); } /* Node styles applied across clusters: */ .coach polygon { fill-opacity: 50%; } .player { font-style: italic; } /* Edge styles based on the named relation: */ .coach_of { stroke-width: 1; stroke-dasharray: 4 6; } .teammate_of { stroke-width: 2; stroke-dasharray: 2 1; } ---------------------------------------- =end comment GraphViz2-2.67/t/calc3.output0000644000175000017500000001535713730646160015745 0ustar osboxesosboxes 0 $accept : list $end 1 list : 2 | list stat '\n' 3 | list error '\n' 4 stat : expr 5 | LETTER '=' expr 6 expr : '(' expr ')' 7 | expr '+' expr 8 | expr '-' expr 9 | expr '*' expr 10 | expr '/' expr 11 | expr '%' expr 12 | expr '&' expr 13 | expr '|' expr 14 | '-' expr 15 | LETTER 16 | number 17 number : DIGIT 18 | number DIGIT state 0 $accept : . list $end (0) list : . (1) . reduce 1 list goto 1 state 1 $accept : list . $end (0) list : list . stat '\n' (2) list : list . error '\n' (3) $end accept error shift 2 DIGIT shift 3 LETTER shift 4 '-' shift 5 '(' shift 6 . error stat goto 7 expr goto 8 number goto 9 state 2 list : list error . '\n' (3) '\n' shift 10 . error state 3 number : DIGIT . (17) . reduce 17 state 4 stat : LETTER . '=' expr (5) expr : LETTER . (15) '=' shift 11 '|' reduce 15 '&' reduce 15 '+' reduce 15 '-' reduce 15 '*' reduce 15 '/' reduce 15 '%' reduce 15 '\n' reduce 15 state 5 expr : '-' . expr (14) DIGIT shift 3 LETTER shift 12 '-' shift 5 '(' shift 6 . error expr goto 13 number goto 9 state 6 expr : '(' . expr ')' (6) DIGIT shift 3 LETTER shift 12 '-' shift 5 '(' shift 6 . error expr goto 14 number goto 9 state 7 list : list stat . '\n' (2) '\n' shift 15 . error state 8 stat : expr . (4) expr : expr . '+' expr (7) expr : expr . '-' expr (8) expr : expr . '*' expr (9) expr : expr . '/' expr (10) expr : expr . '%' expr (11) expr : expr . '&' expr (12) expr : expr . '|' expr (13) '|' shift 16 '&' shift 17 '+' shift 18 '-' shift 19 '*' shift 20 '/' shift 21 '%' shift 22 '\n' reduce 4 state 9 expr : number . (16) number : number . DIGIT (18) DIGIT shift 23 '|' reduce 16 '&' reduce 16 '+' reduce 16 '-' reduce 16 '*' reduce 16 '/' reduce 16 '%' reduce 16 '\n' reduce 16 ')' reduce 16 state 10 list : list error '\n' . (3) . reduce 3 state 11 stat : LETTER '=' . expr (5) DIGIT shift 3 LETTER shift 12 '-' shift 5 '(' shift 6 . error expr goto 24 number goto 9 state 12 expr : LETTER . (15) . reduce 15 state 13 expr : expr . '+' expr (7) expr : expr . '-' expr (8) expr : expr . '*' expr (9) expr : expr . '/' expr (10) expr : expr . '%' expr (11) expr : expr . '&' expr (12) expr : expr . '|' expr (13) expr : '-' expr . (14) . reduce 14 state 14 expr : '(' expr . ')' (6) expr : expr . '+' expr (7) expr : expr . '-' expr (8) expr : expr . '*' expr (9) expr : expr . '/' expr (10) expr : expr . '%' expr (11) expr : expr . '&' expr (12) expr : expr . '|' expr (13) '|' shift 16 '&' shift 17 '+' shift 18 '-' shift 19 '*' shift 20 '/' shift 21 '%' shift 22 ')' shift 25 . error state 15 list : list stat '\n' . (2) . reduce 2 state 16 expr : expr '|' . expr (13) DIGIT shift 3 LETTER shift 12 '-' shift 5 '(' shift 6 . error expr goto 26 number goto 9 state 17 expr : expr '&' . expr (12) DIGIT shift 3 LETTER shift 12 '-' shift 5 '(' shift 6 . error expr goto 27 number goto 9 state 18 expr : expr '+' . expr (7) DIGIT shift 3 LETTER shift 12 '-' shift 5 '(' shift 6 . error expr goto 28 number goto 9 state 19 expr : expr '-' . expr (8) DIGIT shift 3 LETTER shift 12 '-' shift 5 '(' shift 6 . error expr goto 29 number goto 9 state 20 expr : expr '*' . expr (9) DIGIT shift 3 LETTER shift 12 '-' shift 5 '(' shift 6 . error expr goto 30 number goto 9 state 21 expr : expr '/' . expr (10) DIGIT shift 3 LETTER shift 12 '-' shift 5 '(' shift 6 . error expr goto 31 number goto 9 state 22 expr : expr '%' . expr (11) DIGIT shift 3 LETTER shift 12 '-' shift 5 '(' shift 6 . error expr goto 32 number goto 9 state 23 number : number DIGIT . (18) . reduce 18 state 24 stat : LETTER '=' expr . (5) expr : expr . '+' expr (7) expr : expr . '-' expr (8) expr : expr . '*' expr (9) expr : expr . '/' expr (10) expr : expr . '%' expr (11) expr : expr . '&' expr (12) expr : expr . '|' expr (13) '|' shift 16 '&' shift 17 '+' shift 18 '-' shift 19 '*' shift 20 '/' shift 21 '%' shift 22 '\n' reduce 5 state 25 expr : '(' expr ')' . (6) . reduce 6 state 26 expr : expr . '+' expr (7) expr : expr . '-' expr (8) expr : expr . '*' expr (9) expr : expr . '/' expr (10) expr : expr . '%' expr (11) expr : expr . '&' expr (12) expr : expr . '|' expr (13) expr : expr '|' expr . (13) '&' shift 17 '+' shift 18 '-' shift 19 '*' shift 20 '/' shift 21 '%' shift 22 '|' reduce 13 '\n' reduce 13 ')' reduce 13 state 27 expr : expr . '+' expr (7) expr : expr . '-' expr (8) expr : expr . '*' expr (9) expr : expr . '/' expr (10) expr : expr . '%' expr (11) expr : expr . '&' expr (12) expr : expr '&' expr . (12) expr : expr . '|' expr (13) '+' shift 18 '-' shift 19 '*' shift 20 '/' shift 21 '%' shift 22 '|' reduce 12 '&' reduce 12 '\n' reduce 12 ')' reduce 12 state 28 expr : expr . '+' expr (7) expr : expr '+' expr . (7) expr : expr . '-' expr (8) expr : expr . '*' expr (9) expr : expr . '/' expr (10) expr : expr . '%' expr (11) expr : expr . '&' expr (12) expr : expr . '|' expr (13) '*' shift 20 '/' shift 21 '%' shift 22 '|' reduce 7 '&' reduce 7 '+' reduce 7 '-' reduce 7 '\n' reduce 7 ')' reduce 7 state 29 expr : expr . '+' expr (7) expr : expr . '-' expr (8) expr : expr '-' expr . (8) expr : expr . '*' expr (9) expr : expr . '/' expr (10) expr : expr . '%' expr (11) expr : expr . '&' expr (12) expr : expr . '|' expr (13) '*' shift 20 '/' shift 21 '%' shift 22 '|' reduce 8 '&' reduce 8 '+' reduce 8 '-' reduce 8 '\n' reduce 8 ')' reduce 8 state 30 expr : expr . '+' expr (7) expr : expr . '-' expr (8) expr : expr . '*' expr (9) expr : expr '*' expr . (9) expr : expr . '/' expr (10) expr : expr . '%' expr (11) expr : expr . '&' expr (12) expr : expr . '|' expr (13) . reduce 9 state 31 expr : expr . '+' expr (7) expr : expr . '-' expr (8) expr : expr . '*' expr (9) expr : expr . '/' expr (10) expr : expr '/' expr . (10) expr : expr . '%' expr (11) expr : expr . '&' expr (12) expr : expr . '|' expr (13) . reduce 10 state 32 expr : expr . '+' expr (7) expr : expr . '-' expr (8) expr : expr . '*' expr (9) expr : expr . '/' expr (10) expr : expr . '%' expr (11) expr : expr '%' expr . (11) expr : expr . '&' expr (12) expr : expr . '|' expr (13) . reduce 11 16 terminals, 5 nonterminals 19 grammar rules, 33 states GraphViz2-2.67/t/gen.parse.stt.t0000644000175000017500000000161713750412305016342 0ustar osboxesosboxes# Annotation: Demonstrates graphing a Set::FA::Element's state transition table. use strict; use warnings; use File::Spec; use GraphViz2; use GraphViz2::Parse::STT; sub read_file { open my $fh, '<:encoding(UTF-8)', $_[0] or die "$_[0]: $!"; local $/; <$fh>; } my $stt = read_file(File::Spec->catfile('t', 'sample.stt.2.dat')); my $g = GraphViz2::Parse::STT->new(stt => $stt, mode => 're_structs'); if (@ARGV) { my $format = shift || 'svg'; my $output_file = shift || File::Spec->catfile('html', "parse.stt.$format"); my $mode = shift; # default in the module my $graph = GraphViz2->from_graph(GraphViz2::Parse::STT::graphvizify($g->as_graph, $mode)); $graph->run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($g->graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/gen.anonymous.t0000644000175000017500000000164513741667262016466 0ustar osboxesosboxes# Annotation: Demonstrates empty strings for node names and labels. use strict; use warnings; use File::Spec; use GraphViz2; my $graph = GraphViz2->new( edge => {color => 'grey'}, global => {directed => 1}, graph => {rankdir => 'TB'}, node => {shape => 'oval'}, ); $graph -> add_node(name => '', label => ''); # Same as add_node(). $graph -> add_node(name => 'Anonymous label 1', label => ''); $graph -> add_node(name => 'Anonymous label 2', label => ''); $graph -> add_edge(from => '', to => ''); # This uses the name '', and hence the first node. if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec -> catfile('html', "anonymous.$format"); $graph->run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/gen.map.4.t0000644000175000017500000000307513741673765015361 0ustar osboxesosboxes# Annotation: Demonstrates a graph with a 'plaintext' shape. use strict; use warnings; use File::Spec; use GraphViz2; my $id = 4; my $html_template = <<'EOF'; Demo %1$s - A server-side image map

Demo %1$s - A server-side image map

EOF my $file_main = "gen.map.$id.1.html"; my $file_2 = "gen.map.$id.2.html"; my $file_3 = "gen.map.$id.3.html"; my $graph = GraphViz2->new( edge => {color => 'grey'}, global => { directed => 1, name => 'mainmap', }, graph => {rankdir => 'TB'}, im_meta => { URL => $file_main, # Note: URL must be in caps. }, node => {shape => 'oval'}, ); $graph->add_node(name => 'source', URL => $file_2); $graph->add_node(name => 'destination'); $graph->add_edge(from => 'source', to => 'destination', URL => $file_3); if (@ARGV) { my($format) = shift || 'svg'; my $output_file = shift || "map.$id.$format"; my($im_format) = shift || 'cmapx'; (my $volume, my $dirname) = File::Spec->splitpath($output_file); my $im_output_file = shift || File::Spec->catpath($volume, $dirname, "gen.map.$id.map"); $graph -> run(format => $format, output_file => $output_file, im_format => $im_format, im_output_file => $im_output_file); for ($file_main, $file_2, $file_3) { open my $fh, '>', File::Spec->catpath($volume, $dirname, $_) or die "$_: $!"; print $fh sprintf $html_template, $_; } } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/gen.cluster.t0000644000175000017500000000330013741667661016110 0ustar osboxesosboxes# Annotation: Demonstrates a cluster. use strict; use warnings; use File::Spec; use GraphViz2; my $graph = GraphViz2->new( edge => {color => 'grey'}, global => {directed => 1}, graph => {clusterrank => 'local', compound => 1, rankdir => 'TB'}, node => {shape => 'oval'}, ); $graph -> push_subgraph(name => 'cluster_Europe', graph => { bgcolor => 'grey', label => 'Europe' }); $graph -> add_node(name => 'London', color => 'blue'); $graph -> add_node( name => 'Paris', color => 'green', label => 'City of\nlurve', ); $graph -> add_edge(from => 'London', to => 'Paris'); $graph -> add_edge(from => 'Paris', to => 'London'); $graph -> pop_subgraph; $graph -> add_node(name => 'New York', color => 'yellow'); $graph -> add_edge(from => 'London', to => 'New York', label => 'Far'); $graph -> push_subgraph(name => 'cluster_Australia', graph => { bgcolor => 'grey', label => 'Australia', }); $graph -> add_node(name => 'Victoria', color => 'blue'); $graph -> add_node(name => 'New South Wales', color => 'green'); $graph -> add_node(name => 'Tasmania', color => 'red'); $graph -> add_edge(from => 'Victoria', to => 'New South Wales'); $graph -> add_edge(from => 'Victoria', to => 'Tasmania'); $graph -> pop_subgraph; $graph -> add_edge( from => 'Victoria', to => 'London', ltail => 'cluster_Australia', lhead => 'cluster_Europe', ); if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec -> catfile('html', "cluster.$format"); $graph -> run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/gen.trivial.t0000644000175000017500000000235613741723566016110 0ustar osboxesosboxes# Annotation: Demonstrates a trivial 3-node graph, with colors. use strict; use warnings; use File::Spec; use GraphViz2; my $graph = GraphViz2->new( edge => {color => 'grey'}, global => {directed => 1}, graph => {rankdir => 'TB'}, node => {shape => 'oval'}, ); $graph->default_node(shape => 'circle', style => 'filled'); $graph->default_edge(arrowsize => 4); $graph->add_node(name => 'Carnegie', shape => 'circle'); $graph->add_node(name => 'Carnegie', color => 'red'); $graph->default_node(style => 'rounded'); $graph->add_node(name => 'Murrumbeena', shape => 'doublecircle', color => 'green'); $graph->add_node(name => 'Oakleigh', shape => 'oval', color => 'blue'); $graph->add_edge(from => 'Murrumbeena', to => 'Carnegie', arrowsize => 2); $graph->default_edge(arrowsize => 4); $graph->add_edge(from => 'Murrumbeena', to => 'Oakleigh', color => 'brown'); if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec -> catfile('html', "trivial.$format"); $graph -> run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/gen.quote.t0000644000175000017500000000226613741675247015575 0ustar osboxesosboxes# Annotation: Demonstrates (1) newlines and double-quotes in node names and labels, (2) justification. use strict; use warnings; use File::Spec; use GraphViz2; my $graph = GraphViz2->new( global => {directed => 1}, graph => {rankdir => 'LR'}, node => {shape => 'oval'}, ); $graph->add_node(name => "Embedded\\nnewline\\nnode\\nname"); $graph->add_node( name => "Embedded newline label name", label => "Embedded\\nnewline\\nlabel", ); $graph->add_node(name => "Embedded\\ndouble-quote\\nnode\\nname\\n\\\""); $graph->add_node( name => "Embedded\\double-quote\\label", label => qq|Embedded\\ndouble-quote\\nlabel\\n\"|, ); $graph->add_node( name => 'Line justification 1', label => "A short line\\rA much longer line", ); $graph->add_node( name => 'Line justification 2', label => "A much longer line\\rA short line", ); if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec -> catfile('html', "quote.$format"); $graph -> run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/gen.macro.2.t0000644000175000017500000000237213741671513015667 0ustar osboxesosboxes# Annotation: Demonstrates linked non-cluster subgraphs via a macro. use strict; use warnings; use File::Spec; use GraphViz2; sub macro { my ($graph, $name, $node_1, $node_2) = @_; $graph->push_subgraph( name => $name, graph => {label => $name}, node => {color => 'magenta', shape => 'diamond'}, ); $graph->add_node(name => $node_1, shape => 'hexagon'); $graph->add_node(name => $node_2, color => 'orange'); $graph->add_edge(from => $node_1, to => $node_2); $graph->pop_subgraph; } my $id = '2'; my $graph = GraphViz2->new( edge => {color => 'grey'}, global => {directed => 1}, graph => {label => "Macro demo $id - Linked non-cluster sub-graphs", rankdir => 'TB'}, node => {shape => 'oval'}, ); macro($graph, 'One', 'Chadstone', 'Waverley'); macro($graph, 'Two', 'Hughesdale', 'Notting Hill'); $graph->add_edge(from => 'Chadstone', to => 'Notting Hill', minlen => 2); if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec -> catfile('html', "macro.$id.$format"); $graph -> run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/sample.stt.2.dat0000644000175000017500000000521113730646160016406 0ustar osboxesosboxes['attribute', '[\w :;]+', 'attribute'] ['attribute', '}', 'post_attribute'] ['class', ',', 'class_daisy_chain'] ['class', '[-<=>~.]+', 'edge'] ['class', '\(', 'start_group'] ['class', '\.', 'start_subclass'] ['class', '\[', 'start_node'] ['class', '\s+', 'class'] ['class', '{', 'start_class_attribute'] ['class_attribute', '[\w :;]+', 'class_attribute'] ['class_attribute', '}', 'post_class_attribute'] ['class_daisy_chain', '[a-z][a-z0-9_]*', 'class'] ['class_daisy_chain', '\.', 'subclass'] ['class_daisy_chain', '\s+', 'class_daisy_chain'] ['daisy_chain', '[-<=>~.]+', 'edge'] ['daisy_chain', '\[', 'start_node'] ['daisy_chain', '\s+', 'daisy_chain'] ['edge', ',', 'daisy_chain'] ['edge', '\(', 'start_group'] ['edge', '\)', 'post_group'] ['edge', '\[', 'start_node'] ['edge', '\s+', 'edge'] ['edge', '{', 'start_attribute'] ['global', '[-<=>~.]+', 'edge'] ['global', '[a-z][a-z0-9_]*', 'class'] ['global', '\(', 'start_group'] ['global', '\[', 'start_node'] ['global', '\s+', 'global'] ['global', '{', 'start_class_attribute'] ['group', '[-<=>~.]+', 'edge'] ['group', '[a-z][a-z0-9_]*', 'class'] ['group', '\[', 'start_node'] ['group', '\s+', 'group'] ['node', '\s+', 'node'] ['node', ']', 'post_node'] ['post_attribute', ',', 'daisy_chain'] ['post_attribute', '[-<=>~.]+', 'edge'] ['post_attribute', '\)', 'post_group'] ['post_attribute', '\[', 'start_node'] ['post_attribute', '\s+', 'post_attribute'] ['post_class_attribute', ',', 'daisy_chain'] ['post_class_attribute', '[-<=>~.]+', 'edge'] ['post_class_attribute', '[a-z][a-z0-9_]*', 'class'] ['post_class_attribute', '\(', 'start_group'] ['post_class_attribute', '\[', 'start_node'] ['post_class_attribute', '\s+', 'post_class_attribute'] ['post_group', '[-<=>~.]+', 'edge'] ['post_group', '\(', 'start_group'] ['post_group', '\[', 'start_node'] ['post_group', '\s+', 'post_group'] ['post_node', ',', 'daisy_chain'] ['post_node', '[-<=>~.]+', 'edge'] ['post_node', '\(', 'start_group'] ['post_node', '\)', 'post_group'] ['post_node', '\[', 'start_node'] ['post_node', '\s+', 'post_node'] ['post_node', '{', 'start_attribute'] ['start_attribute', '[\w :;]+', 'attribute'] ['start_class_attribute', '[\w :;]+', 'class_attribute'] ['start_group', '[-<=>~.]+', 'edge'] ['start_group', '[\w.]+:', 'group'] ['start_group', '\)', 'post_group'] ['start_group', '\s+', 'start_group'] ['start_node', '[\w.]+', 'node'] ['start_node', '\s+', 'start_node'] ['start_node', ']', 'post_node'] ['start_subclass', '[a-z][a-z0-9_]*', 'subclass'] ['subclass', ',', 'class_daisy_chain'] ['subclass', '[-<=>~.]+', 'edge'] ['subclass', '\(', 'start_group'] ['subclass', '\[', 'node'] ['subclass', '\s+', 'subclass'] ['subclass', '{', 'start_class_attribute'] GraphViz2-2.67/t/gen.unnamed.sub.graph.t0000644000175000017500000000234613741745770017755 0ustar osboxesosboxes# Annotation: Demonstrates named and unnamed subgraphs. use strict; use warnings; use File::Spec; use GraphViz2; my $graph = GraphViz2->new( edge => {color => 'grey'}, global => {directed => 1}, graph => {label => 'Named and unnamed subgraphs', rankdir => 'TB'}, ); $graph->push_subgraph( graph => {label => 'Subgraph One'}, node => {color => 'magenta', shape => 'diamond'}, ); $graph->add_node(name => 'Chadstone', shape => 'hexagon'); $graph->add_node(name => 'Waverley', color => 'orange'); $graph->add_edge(from => 'Chadstone', to => 'Waverley'); $graph->pop_subgraph; $graph->push_subgraph; $graph->add_node(name => 'Glen Waverley', color => 'blue3', shape => 'pentagon'); $graph->add_node(name => 'Mount Waverley', color => 'darkslategrey', shape => 'rectangle'); $graph->add_edge(from => 'Glen Waverley', to => 'Mount Waverley'); $graph->pop_subgraph; if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec -> catfile('html', "unnamed.sub.graph.$format"); $graph -> run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/record.old.t0000644000175000017500000000243613743122156015706 0ustar osboxesosboxes# Annotation: Nested records using an arrayref of hashrefs as labels. use strict; use warnings; use File::Spec; use GraphViz2; my $id = '2'; my $graph = GraphViz2->new( global => {directed => 1, combine_node_and_port => 1}, graph => {label => "Record demo $id - Nested records using an arrayref of hashrefs as labels"}, node => {shape => 'record'}, ); $graph->add_node(name => 'struct1', label => ' left| mid dle| right'); $graph->add_node(name => 'struct2', label => ' one| two'); $graph->add_node(name => 'struct3', label => [ { text => "hello\\nworld", }, { text => '{b', }, { text => '{c', }, { port => '', text => 'd', }, { text => 'e}', }, { text => 'f}', }, { text => 'g', }, { text => 'h', }, ]); $graph->add_edge(from => 'struct1:f1', to => 'struct2:f0', color => 'blue'); $graph->add_edge(from => 'struct1:f2', to => 'struct3:here', color => 'red'); if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec -> catfile('html', "record.$id.$format"); $graph -> run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/t/gen.parse.regexp.t0000644000175000017500000000204713747323075017033 0ustar osboxesosboxes# Annotation: Demonstrates graphing a Perl regular expression. use strict; use warnings; use File::Spec; use GraphViz2; use GraphViz2::Parse::Regexp; my $graph = GraphViz2->new( edge => {color => 'grey'}, global => {directed => 1}, graph => {rankdir => 'TB'}, node => {color => 'blue', shape => 'oval'}, ); my $g = GraphViz2::Parse::Regexp->new(graph => $graph); my $re = '^(([abcd0-9])|(foo)n?)x*y{1,2}'; $g->create(regexp => $re); if (@ARGV) { my($format) = shift || 'svg'; my($output_file) = shift || File::Spec -> catfile('html', "parse.regexp.$format"); $graph -> run(format => $format, output_file => $output_file); } else { # run as a test require Test::More; require Test::Snapshot; local our $TODO = 'seems to vary by Perl version'; my $gd = GraphViz2::Parse::Regexp::to_graph($re); my $gvre = GraphViz2::Parse::Regexp->new(as_graph => $gd); Test::Snapshot::is_deeply_snapshot($gvre->graph->dot_input, 'dot file'); Test::Snapshot::is_deeply_snapshot($graph->dot_input, 'dot file'); Test::More::done_testing(); } GraphViz2-2.67/README.md0000644000175000017500000012073113776430753014514 0ustar osboxesosboxes# NAME GraphViz2 - A wrapper for AT&T's Graphviz # Synopsis ## Sample output See [https://graphviz-perl.github.io/](https://graphviz-perl.github.io/). ## Perl code ### Typical Usage use strict; use warnings; use File::Spec; use GraphViz2; use Log::Handler; my $logger = Log::Handler->new; $logger->add(screen => { maxlevel => 'debug', message_layout => '%m', minlevel => 'error' }); my $graph = GraphViz2->new( edge => {color => 'grey'}, global => {directed => 1}, graph => {label => 'Adult', rankdir => 'TB'}, logger => $logger, node => {shape => 'oval'}, ); $graph->add_node(name => 'Carnegie', shape => 'circle'); $graph->add_node(name => 'Murrumbeena', shape => 'box', color => 'green'); $graph->add_node(name => 'Oakleigh', color => 'blue'); $graph->add_edge(from => 'Murrumbeena', to => 'Carnegie', arrowsize => 2); $graph->add_edge(from => 'Murrumbeena', to => 'Oakleigh', color => 'brown'); $graph->push_subgraph( name => 'cluster_1', graph => {label => 'Child'}, node => {color => 'magenta', shape => 'diamond'}, ); $graph->add_node(name => 'Chadstone', shape => 'hexagon'); $graph->add_node(name => 'Waverley', color => 'orange'); $graph->add_edge(from => 'Chadstone', to => 'Waverley'); $graph->pop_subgraph; $graph->default_node(color => 'cyan'); $graph->add_node(name => 'Malvern'); $graph->add_node(name => 'Prahran', shape => 'trapezium'); $graph->add_edge(from => 'Malvern', to => 'Prahran'); $graph->add_edge(from => 'Malvern', to => 'Murrumbeena'); my $format = shift || 'svg'; my $output_file = shift || File::Spec->catfile('html', "sub.graph.$format"); $graph->run(format => $format, output_file => $output_file); # Description ## Overview This module provides a Perl interface to the amazing [Graphviz](http://www.graphviz.org/), an open source graph visualization tool from AT&T. It is called GraphViz2 so that pre-existing code using (the Perl module) GraphViz continues to work. To avoid confusion, when I use [GraphViz2](https://metacpan.org/pod/GraphViz2) (note the capital V), I'm referring to this Perl module, and when I use [Graphviz](http://www.graphviz.org/) (lower-case v) I'm referring to the underlying tool (which is in fact a set of programs). Version 1.00 of [GraphViz2](https://metacpan.org/pod/GraphViz2) is a complete re-write, by Ron Savage, of GraphViz V 2, which was written by Leon Brocard. The point of the re-write is to provide access to all the latest options available to users of [Graphviz](http://www.graphviz.org/). GraphViz2 V 1 is not backwards compatible with GraphViz V 2, despite the considerable similarity. It was not possible to maintain compatibility while extending support to all the latest features of [Graphviz](http://www.graphviz.org/). To ensure [GraphViz2](https://metacpan.org/pod/GraphViz2) is a light-weight module, [Moo](https://metacpan.org/pod/Moo) has been used to provide getters and setters, rather than [Moose](https://metacpan.org/pod/Moose). As of V 2.43, `GraphViz2` supports image maps, both client and server side. See ["Image Maps"](#image-maps) below. ## What is a Graph? An undirected graph is a collection of nodes optionally linked together with edges. A directed graph is the same, except that the edges have a direction, normally indicated by an arrow head. A quick inspection of [Graphviz](http://www.graphviz.org/)'s [gallery](http://www.graphviz.org/gallery/) will show better than words just how good [Graphviz](http://www.graphviz.org/) is, and will reinforce the point that humans are very visual creatures. # Installation Of course you need to install AT&T's Graphviz before using this module. See [http://www.graphviz.org/download/](http://www.graphviz.org/download/). # Constructor and Initialization ## Calling new() `new()` is called as `my($obj) = GraphViz2 -> new(k1 => v1, k2 => v2, ...)`. It returns a new object of type `GraphViz2`. Key-value pairs accepted in the parameter list: ### edge => $hashref The _edge_ key points to a hashref which is used to set default attributes for edges. Hence, allowable keys and values within that hashref are anything supported by [Graphviz](http://www.graphviz.org/). The default is {}. This key is optional. ### global => $hashref The _global_ key points to a hashref which is used to set attributes for the output stream. This key is optional. Valid keys within this hashref are: #### combine\_node\_and\_port New in 2.58. It defaults to true, but in due course (currently planned May 2021) it will default to false. When true, `add_node` and `add_edge` will escape only some characters in the label and names, and in particular the "from" and "to" parameters on edges will combine the node name and port in one string, with a `:` in the middle (except for special treatment of double-colons). When the option is false, any name may be given to nodes, and edges can be created between them. To specify ports, give the additional parameter of `tailport` or `headport`. To specify a compass point in addition, give array-refs with two values for these parameters. Also, `add_node`'s treatment of labels is more DWIM, with `{` etc being transparently quoted. #### directed => $Boolean This option affects the content of the output stream. directed => 1 outputs 'digraph name {...}', while directed => 0 outputs 'graph name {...}'. At the Perl level, directed graphs have edges with arrow heads, such as '->', while undirected graphs have unadorned edges, such as '--'. The default is 0. This key is optional. #### driver => $program\_name This option specifies which external program to run to process the output stream. The default is to use [File::Which](https://metacpan.org/pod/File%3A%3AWhich)'s which() method to find the 'dot' program. This key is optional. #### format => $string This option specifies what type of output file to create. The default is 'svg'. Output formats of the form 'png:gd' etc are also supported, but only the component before the first ':' is validated by [GraphViz2](https://metacpan.org/pod/GraphViz2). This key is optional. #### label => $string This option specifies what an edge looks like: '->' for directed graphs and '--' for undirected graphs. You wouldn't normally need to use this option. The default is '->' if directed is 1, and '--' if directed is 0. This key is optional. #### name => $string This option affects the content of the output stream. name => 'G666' outputs 'digraph G666 {...}'. The default is 'Perl' :-). This key is optional. #### record\_shape => /^(?:M?record)$/ This option affects the shape of records. The value must be 'Mrecord' or 'record'. Mrecords have nice, rounded corners, whereas plain old records have square corners. The default is 'Mrecord'. See [Record shapes](http://www.graphviz.org/doc/info/shapes.html#record) for details. #### strict => $Boolean This option affects the content of the output stream. strict => 1 outputs 'strict digraph name {...}', while strict => 0 outputs 'digraph name {...}'. The default is 0. This key is optional. #### timeout => $integer This option specifies how long to wait for the external program before exiting with an error. The default is 10 (seconds). This key is optional. ### graph => $hashref The _graph_ key points to a hashref which is used to set default attributes for graphs. Hence, allowable keys and values within that hashref are anything supported by [Graphviz](http://www.graphviz.org/). The default is {}. This key is optional. ### logger => $logger\_object Provides a logger object so $logger\_object -> $level($message) can be called at certain times. Any object with `debug` and `error` methods will do, since these are the only levels emitted by this module. One option is a [Log::Handler](https://metacpan.org/pod/Log%3A%3AHandler) object. Retrieve and update the value with the logger() method. By default (i.e. without a logger object), [GraphViz2](https://metacpan.org/pod/GraphViz2) prints warning and debug messages to STDOUT, and dies upon errors. However, by supplying a log object, you can capture these events. Not only that, you can change the behaviour of your log object at any time, by calling ["logger($logger\_object)"](#logger-logger_object). See also the verbose option, which can interact with the logger option. This key is optional. ### node => $hashref The _node_ key points to a hashref which is used to set default attributes for nodes. Hence, allowable keys and values within that hashref are anything supported by [Graphviz](http://www.graphviz.org/). The default is {}. This key is optional. ### subgraph => $hashref The _subgraph_ key points to a hashref which is used to set attributes for all subgraphs, unless overridden for specific subgraphs in a call of the form push\_subgraph(subgraph => {$attribute => $string}). Valid keys within this hashref are: - rank => $string This option affects the content of all subgraphs, unless overridden later. A typical usage would be new(subgraph => {rank => 'same'}) so that all nodes mentioned within each subgraph are constrained to be horizontally aligned. See scripts/rank.sub.graph.1.pl for sample code. Possible values for $string are: max, min, same, sink and source. See the [Graphviz 'rank' docs](http://www.graphviz.org/doc/info/attrs.html#d:rank) for details. The default is {}. This key is optional. ### verbose => $Boolean Provides a way to control the amount of output when a logger is not specified. Setting verbose to 0 means print nothing. Setting verbose to 1 means print the log level and the message to STDOUT, when a logger is not specified. Retrieve and update the value with the verbose() method. The default is 0. See also the logger option, which can interact with the verbose option. This key is optional. ## Validating Parameters The secondary keys (under the primary keys 'edge|graph|node') are checked against lists of valid attributes (stored at the end of this module, after the \_\_DATA\_\_ token, and made available using [Data::Section::Simple](https://metacpan.org/pod/Data%3A%3ASection%3A%3ASimple)). This mechanism has the effect of hard-coding [Graphviz](http://www.graphviz.org/) options in the source code of [GraphViz2](https://metacpan.org/pod/GraphViz2). Nevertheless, the implementation of these lists is handled differently from the way it was done in V 2. V 2 ships with a set of scripts, scripts/extract.\*.pl, which retrieve pages from the [Graphviz](http://www.graphviz.org/) web site and extract the current lists of valid attributes. These are then copied manually into the source code of [GraphViz2](https://metacpan.org/pod/GraphViz2), meaning any time those lists change on the [Graphviz](http://www.graphviz.org/) web site, it's a trivial matter to update the lists stored within this module. See ["Scripts Shipped with this Module" in GraphViz2](https://metacpan.org/pod/GraphViz2#Scripts-Shipped-with-this-Module). ## Alternate constructor and object method ### from\_graph my $gv = GraphViz2->from_graph($g); # alternatively my $gv = GraphViz2->new; $gv->from_graph($g); # for handy debugging of arbitrary graphs: GraphViz2->from_graph($g)->run(format => 'svg', output_file => 'output.svg'); Takes a [Graph](https://metacpan.org/pod/Graph) object. This module will figure out various defaults from it, including whether it is directed or not. Will also use any node-, edge-, and graph-level attributes named `graphviz` as a hash-ref for setting attributes on the corresponding entities in the constructed GraphViz2 object. These will override the figured-out defaults referred to above. For a `multivertexed` graph, will only create one node per vertex, but will search all the multi-IDs for a `graphviz` attribute, taking the first one it finds (sorted alphabetically). For a `multiedged` graph, will create one edge per multi-edge. Will only set the `global` attribute if called as a constructor. This will be dropped from any passed-in graph-level `graphviz` attribute when called as an object method. A special graph-level attribute (under `graphviz`) called `groups` will be given further special meaning: it is an array-ref of hash-refs. Those will have keys, used to create subgraphs: - attributes Hash-ref of arguments to supply to `push_subgraph` for this subgraph. - nodes Array-ref of node names to put in this subgraph. Example: $g->set_graph_attribute(graphviz => { groups => [ {nodes => [1, 2], attributes => {subgraph=>{rank => 'same'}}}, ], # other graph-level attributes... }); # Attribute Scope ## Graph Scope The graphical elements graph, node and edge, have attributes. Attributes can be set when calling new(). Within new(), the defaults are graph => {}, node => {}, and edge => {}. You override these with code such as new(edge => {color => 'red'}). These attributes are pushed onto a scope stack during new()'s processing of its parameters, and they apply thereafter until changed. They are the 'current' attributes. They live at scope level 0 (zero). You change the 'current' attributes by calling any of the methods default\_edge(%hash), default\_graph(%hash) and default\_node(%hash). See scripts/trivial.pl (["Scripts Shipped with this Module" in GraphViz2](https://metacpan.org/pod/GraphViz2#Scripts-Shipped-with-this-Module)) for an example. ## Subgraph Scope When you wish to create a subgraph, you call push\_subgraph(%hash). The word push emphasises that you are moving into a new scope, and that the default attributes for the new scope are pushed onto the scope stack. This module, as with [Graphviz](http://www.graphviz.org/), defaults to using inheritance of attributes. That means the parent's 'current' attributes are combined with the parameters to push\_subgraph(%hash) to generate a new set of 'current' attributes for each of the graphical elements, graph, node and edge. After a single call to push\_subgraph(%hash), these 'current' attributes will live a level 1 in the scope stack. See scripts/sub.graph.pl (["Scripts Shipped with this Module" in GraphViz2](https://metacpan.org/pod/GraphViz2#Scripts-Shipped-with-this-Module)) for an example. Another call to push\_subgraph(%hash), _without_ an intervening call to pop\_subgraph(), will repeat the process, leaving you with a set of attributes at level 2 in the scope stack. Both [GraphViz2](https://metacpan.org/pod/GraphViz2) and [Graphviz](http://www.graphviz.org/) handle this situation properly. See scripts/sub.sub.graph.pl (["Scripts Shipped with this Module" in GraphViz2](https://metacpan.org/pod/GraphViz2#Scripts-Shipped-with-this-Module)) for an example. At the moment, due to design defects (IMHO) in the underlying [Graphviz](http://www.graphviz.org/) logic, there are some tiny problems with this: - A global frame I can't see how to make the graph as a whole (at level 0 in the scope stack) have a frame. - Frame color When you specify graph => {color => 'red'} at the parent level, the subgraph has a red frame. I think a subgraph should control its own frame. - Parent and child frames When you specify graph => {color => 'red'} at the subgraph level, both that subgraph and it children have red frames. This contradicts what happens at the global level, in that specifying color there does not given the whole graph a frame. - Frame visibility A subgraph whose name starts with 'cluster' is currently forced to have a frame, unless you rig it by specifying a color the same as the background. For sample code, see scripts/sub.graph.frames.pl. Also, check [the pencolor docs](http://www.graphviz.org/doc/info/attrs.html#d:pencolor) for how the color of the frame is chosen by cascading thru a set of options. I've posted an email to the [Graphviz](http://www.graphviz.org/) mailing list suggesting a new option, framecolor, so deal with this issue, including a special color of 'invisible'. # Image Maps As of V 2.43, `GraphViz2` supports image maps, both client and server side. For web use, note that these options also take effect when generating SVGs, for a much lighter-weight solution to hyperlinking graph nodes and edges. ## The Default URL See the [Graphviz docs for 'cmapx'](http://www.graphviz.org/doc/info/output.html#d:cmapx). Their sample code has a dot file - x.gv - containing this line: URL="http://www.research.att.com/base.html"; The way you set such a url in `GraphViz2` is via a new parameter to `new()`. This parameter is called `im_meta` and it takes a hashref as a value. Currently the only key used within that hashref is the case-sensitive `URL`. Thus you must do this to set a URL: my($graph) = GraphViz2 -> new ( ... im_meta => { URL => 'http://savage.net.au/maps/demo.3.1.html', # Note: URL must be in caps. }, ); See maps/demo.3.pl and maps/demo.4.pl for sample code. ## Typical Code Normally you would call `run()` as: $graph -> run ( format => $format, output_file => $output_file ); That line was copied from scripts/cluster.pl. To trigger image map processing, you must include 2 new parameters: $graph -> run ( format => $format, output_file => $output_file, im_format => $im_format, im_output_file => $im_output_file ); That line was copied from maps/demo.3.pl, and there is an identical line in maps/demo.4.pl. ## The New Parameters to run() - im\_format => $str Expected values: 'imap' (server-side) and 'cmapx' (client-side). Default value: 'cmapx'. - im\_output\_file => $file\_name The name of the output map file. Default: ''. If you do not set it to anything, the new image maps code is ignored. ## Sample Code Various demos are shipped in the new maps/ directory: Each demo, when FTPed to your web server displays some text with an image in the middle. In each case you can click on the upper oval to jump to one page, or click on the lower oval to jump to a different page, or click anywhere else in the image to jump to a third page. - demo.1.\* This set demonstrates a server-side image map but does not use `GraphViz2`. You have to run demo.1.sh which generates demo.1.map, and then you FTP the whole dir maps/ to your web server. URL: your.domain.name/maps/demo.1.html. - demo.2.\* This set demonstrates a client-side image map but does not use `GraphViz2`. You have to run demo.2.sh which generates demo.2.map, and then you manually copy demo.2.map into demo.2.html, replacing any version of the map already present. After that you FTP the whole dir maps/ to your web server. URL: your.domain.name/maps/demo.2.html. - demo.3.\* This set demonstrates a server-side image map using `GraphViz2` via demo.3.pl. Note line 54 of demo.3.pl which sets the default `im_format` to 'imap'. URL: your.domain.name/maps/demo.3.html. - demo.4.\* This set demonstrates a client-side image map using `GraphViz2` via demo.4.pl. As with demo.2.\* there is some manually editing to be done. Note line 54 of demo.4.pl which sets the default `im_format` to 'cmapx'. This is the only important difference between this demo and the previous one. There are other minor differences, in that one uses 'svg' and the other 'png'. And of course the urls of the web pages embedded in the code and in those web pages differs, just to demonstate that the maps do indeed lead to different pages. URL: your.domain.name/maps/demo.4.html. # Methods ## add\_edge(from => $from\_node\_name, to => $to\_node\_name, \[label => $label, %hash\]) Adds an edge to the graph. Returns $self to allow method chaining. Here, \[\] indicate optional parameters. Add a edge from 1 node to another. $from\_node\_name and $to\_node\_name default to ''. %hash is any edge attributes accepted as [Graphviz attributes](https://www.graphviz.org/doc/info/attrs.html). These are validated in exactly the same way as the edge parameters in the calls to default\_edge(%hash), new(edge => {}) and push\_subgraph(edge => {}). To make the edge start or finish on a port, see ["combine\_node\_and\_port"](#combine_node_and_port). ## add\_node(name => $node\_name, \[%hash\]) my $graph = GraphViz2->new(global => {combine_node_and_port => 0}); $graph->add_node(name => 'struct3', shape => 'record', label => [ { text => "hello\\nworld" }, [ { text => 'b' }, [ { text => 'c{}' }, # reproduced literally { text => 'd', port => 'here' }, { text => 'e' }, ] { text => 'f' }, ], { text => 'g' }, { text => 'h' }, ]); Adds a node to the graph. Returns $self to allow method chaining. If you want to embed newlines or double-quotes in node names or labels, see scripts/quote.pl in ["Scripts Shipped with this Module" in GraphViz2](https://metacpan.org/pod/GraphViz2#Scripts-Shipped-with-this-Module). If you want anonymous nodes, see scripts/anonymous.pl in ["Scripts Shipped with this Module" in GraphViz2](https://metacpan.org/pod/GraphViz2#Scripts-Shipped-with-this-Module). Here, \[\] indicates an optional parameter. %hash is any node attributes accepted as [Graphviz attributes](https://www.graphviz.org/doc/info/attrs.html). These are validated in exactly the same way as the node parameters in the calls to default\_node(%hash), new(node => {}) and push\_subgraph(node => {}). The attribute name 'label' may point to a string or an arrayref. ### If it is a string... The string is the label. If the `shape` is a record, you can give any text and it will be passed for interpretation by Graphviz. This means you will need to quote < and > (port specifiers), `|` (cell separator) and `{` `}` (structure depth) with `\` to make them appear literally. For records, the cells start horizontal. Each additional layer of structure will switch the orientation between horizontal and vertical. ### If it is an arrayref of strings... - The node is forced to be a record The actual shape, 'record' or 'Mrecord', is set globally, with: my($graph) = GraphViz2 -> new ( global => {record_shape => 'record'}, # Override default 'Mrecord'. ... ); Or set locally with: $graph -> add_node(name => 'Three', label => ['Good', 'Bad'], shape => 'record'); - Each element in the array defines a field in the record These fields are combined into a single node - Each element is treated as a label - Each label is given a port name (1 .. N) of the form "port$port\_count" - Judicious use of '{' and '}' in the label can make this record appear horizontally or vertically, and even nested ### If it is an arrayref of hashrefs... - The node is forced to be a record The actual shape, 'record' or 'Mrecord', can be set globally or locally, as explained just above. - Each element in the array defines a field in the record - Each element is treated as a hashref with keys 'text' and 'port' The 'port' key is optional. - The value of the 'text' key is the label - The value of the 'port' key is the port - Judicious use of '{' and '}' in the label can make this record appear horizontally or vertically, and even nested See scripts/html.labels.\*.pl and scripts/record.\*.pl for sample code. See also ["How labels interact with ports"](#how-labels-interact-with-ports). For more details on this complex topic, see [Records](http://www.graphviz.org/doc/info/shapes.html#record) and [Ports](http://www.graphviz.org/doc/info/attrs.html#k:portPos). ## default\_edge(%hash) Sets defaults attributes for edges added subsequently. Returns $self to allow method chaining. %hash is any edge attributes accepted as [Graphviz attributes](https://www.graphviz.org/doc/info/attrs.html). These are validated in exactly the same way as the edge parameters in the calls to new(edge => {}) and push\_subgraph(edge => {}). ## default\_graph(%hash) Sets defaults attributes for the graph. Returns $self to allow method chaining. %hash is any graph attributes accepted as [Graphviz attributes](https://www.graphviz.org/doc/info/attrs.html). These are validated in exactly the same way as the graph parameter in the calls to new(graph => {}) and push\_subgraph(graph => {}). ## default\_node(%hash) Sets defaults attributes for nodes added subsequently. Returns $self to allow method chaining. %hash is any node attributes accepted as [Graphviz attributes](https://www.graphviz.org/doc/info/attrs.html). These are validated in exactly the same way as the node parameters in the calls to new(node => {}) and push\_subgraph(node => {}). ## default\_subgraph(%hash) Sets defaults attributes for clusters and subgraphs. Returns $self to allow method chaining. %hash is any cluster or subgraph attribute accepted as [Graphviz attributes](https://www.graphviz.org/doc/info/attrs.html). These are validated in exactly the same way as the subgraph parameter in the calls to new(subgraph => {}) and push\_subgraph(subgraph => {}). ## dot\_input() Returns the output stream, formatted nicely, to be passed to the external program (e.g. dot). ## dot\_output() Returns the output from calling the external program (e.g. dot). You _must_ call run() before calling dot\_output(), since it is only during the call to run() that the output of the external program is stored in the buffer controlled by dot\_output(). This output is available even if run() does not write the output to a file. ## edge\_hash() Returns, at the end of the run, a hashref keyed by node name, specifically the node at the arrow_tail_ end of the hash, i.e. where the edge starts from. Use this to get a list of all nodes and the edges which leave those nodes, the corresponding destination nodes, and the attributes of each edge. my($node_hash) = $graph -> node_hash; my($edge_hash) = $graph -> edge_hash; for my $from (sort keys %$node_hash) { my($attr) = $$node_hash{$from}{attributes}; my($s) = join(', ', map{"$_ => $$attr{$_}"} sort keys %$attr); print "Node: $from\n"; print "\tAttributes: $s\n"; for my $to (sort keys %{$$edge_hash{$from} }) { for my $edge (@{$$edge_hash{$from}{$to} }) { $attr = $$edge{attributes}; $s = join(', ', map{"$_ => $$attr{$_}"} sort keys %$attr); print "\tEdge: $from$$edge{from_port} -> $to$$edge{to_port}\n"; print "\t\tAttributes: $s\n"; } } } If the caller adds the same edge two (or more) times, the attributes from each call are _not_ coalesced (unlike ["node\_hash()"](#node_hash)), but rather the attributes from each call are stored separately in an arrayref. A bit more formally then, $$edge\_hash{$from\_node}{$to\_node} is an arrayref where each element describes one edge, and which defaults to: { attributes => {}, from_port => $from_port, to_port => $to_port, } If _from\_port_ is not provided by the caller, it defaults to '' (the empty string). If it is provided, it contains a leading ':'. Likewise for _to\_port_. See scripts/report.nodes.and.edges.pl (a version of scripts/html.labels.1.pl) for a complete example. ## log(\[$level, $message\]) Logs the message at the given log level. Returns $self to allow method chaining. Here, \[\] indicate optional parameters. $level defaults to 'debug', and $message defaults to ''. If called with $level eq 'error', it dies with $message. ## logger($logger\_object) Gets or sets the log object. Here, \[\] indicates an optional parameter. ## node\_hash() Returns, at the end of the run, a hashref keyed by node name. Use this to get a list of all nodes and their attributes. my($node_hash) = $graph -> node_hash; for my $name (sort keys %$node_hash) { my($attr) = $$node_hash{$name}{attributes}; my($s) = join(', ', map{"$_ => $$attr{$_}"} sort keys %$attr); print "Node: $name\n"; print "\tAttributes: $s\n"; } If the caller adds the same node two (or more) times, the attributes from each call are _coalesced_ (unlike ["edge\_hash()"](#edge_hash)), meaning all attributes from all calls are combined under the _attributes_ sub-key. A bit more formally then, $$node\_hash{$node\_name} is a hashref where each element describes one node, and which defaults to: { attributes => {}, } See scripts/report.nodes.and.edges.pl (a version of scripts/html.labels.1.pl) for a complete example, including usage of the corresponding ["edge\_hash()"](#edge_hash) method. ## pop\_subgraph() Pop off and discard the top element of the scope stack. Returns $self to allow method chaining. ## push\_subgraph(\[name => $name, edge => {...}, graph => {...}, node => {...}, subgraph => {...}\]) Sets up a new subgraph environment. Returns $self to allow method chaining. Here, \[\] indicate optional parameters. name => $name is the name to assign to the subgraph. Name defaults to ''. So, without $name, 'subgraph {' is written to the output stream. With $name, 'subgraph "$name" {' is written to the output stream. Note that subgraph names beginning with 'cluster' [are special to Graphviz](http://www.graphviz.org/doc/info/attrs.html#d:clusterrank). See scripts/rank.sub.graph.\[1234\].pl for the effect of various values for $name. edge => {...} is any edge attributes accepted as [Graphviz attributes](https://www.graphviz.org/doc/info/attrs.html). These are validated in exactly the same way as the edge parameters in the calls to default\_edge(%hash), new(edge => {}) and push\_subgraph(edge => {}). graph => {...} is any graph attributes accepted as [Graphviz attributes](https://www.graphviz.org/doc/info/attrs.html). These are validated in exactly the same way as the graph parameters in the calls to default\_graph(%hash), new(graph => {}) and push\_subgraph(graph => {}). node => {...} is any node attributes accepted as [Graphviz attributes](https://www.graphviz.org/doc/info/attrs.html). These are validated in exactly the same way as the node parameters in the calls to default\_node(%hash), new(node => {}) and push\_subgraph(node => {}). subgraph => {..} is for setting attributes applicable to clusters and subgraphs. Currently the only subgraph attribute is `rank`, but clusters have many attributes available. See the second column of the [Graphviz attribute docs](https://www.graphviz.org/doc/info/attrs.html) for details. A typical usage would be push\_subgraph(subgraph => {rank => 'same'}) so that all nodes mentioned within the subgraph are constrained to be horizontally aligned. See scripts/rank.sub.graph.\[12\].pl and scripts/sub.graph.frames.pl for sample code. ## valid\_attributes() Returns a hashref of all attributes known to this module, keyed by type to hashrefs to true values. Stored in this module, using [Data::Section::Simple](https://metacpan.org/pod/Data%3A%3ASection%3A%3ASimple). These attributes are used to validate attributes in many situations. You wouldn't normally need to use this method. See scripts/report.valid.attributes.pl. See ["Scripts Shipped with this Module" in GraphViz2](https://metacpan.org/pod/GraphViz2#Scripts-Shipped-with-this-Module). ## run(\[driver => $exe, format => $string, timeout => $integer, output\_file => $output\_file\]) Runs the given program to process the output stream. Returns $self to allow method chaining. Here, \[\] indicate optional parameters. $driver is the name of the external program to run. It defaults to the value supplied in the call to new(global => {driver => '...'}), which in turn defaults to [File::Which](https://metacpan.org/pod/File%3A%3AWhich)'s which('dot') return value. $format is the type of output file to write. It defaults to the value supplied in the call to new(global => {format => '...'}), which in turn defaults to 'svg'. $timeout is the time in seconds to wait while the external program runs, before dieing with an error. It defaults to the value supplied in the call to new(global => {timeout => '...'}), which in turn defaults to 10. $output\_file is the name of the file into which the output from the external program is written. There is no default value for $output\_file. If a value is not supplied for $output\_file, the only way to recover the output of the external program is to call dot\_output(). This method performs a series of tasks: - Run the chosen external program on the ["dot\_input"](#dot_input) - Capture STDOUT and STDERR from that program - Die if STDERR contains anything - Copies STDOUT to the buffer controlled by the dot\_output() method - Write the captured contents of STDOUT to $output\_file, if $output\_file has a value ## stringify\_attributes($context, $option) Returns a string suitable to writing to the output stream. $context is one of 'edge', 'graph', 'node', or a special string. See the code for details. You wouldn't normally need to use this method. ## validate\_params($context, \\%attributes) Validate the given attributes within the given context. Also, if $context is 'subgraph', attributes are allowed to be in the 'cluster' context. Returns $self to allow method chaining. $context is one of 'edge', 'global', 'graph', or 'node'. You wouldn't normally need to use this method. ## verbose(\[$integer\]) Gets or sets the verbosity level, for when a logging object is not used. Here, \[\] indicates an optional parameter. # MISC ## Graphviz version supported GraphViz2 targets V 2.34.0 of [Graphviz](http://www.graphviz.org/). This affects the list of available attributes per graph item (node, edge, cluster, etc) available. See the second column of the [Graphviz attribute docs](https://www.graphviz.org/doc/info/attrs.html) for details. ## Supported file formats Parses the output of `dot -T?`, so depends on local installation. ## Special characters in node names and labels [GraphViz2](https://metacpan.org/pod/GraphViz2) escapes these 2 characters in those contexts: \[\]. Escaping the 2 chars \[\] started with V 2.10. Previously, all of \[\]{} were escaped, but {} are used in records to control the orientation of fields, so they should not have been escaped in the first place. It would be nice to also escape | and <, but these characters are used in specifying fields and ports in records. See the next couple of points for details. ## Ports Ports are what [Graphviz](http://www.graphviz.org/) calls those places on the outline of a node where edges leave and terminate. The [Graphviz](http://www.graphviz.org/) syntax for ports is a bit unusual: - This works: "node\_name":port5 - This doesn't: "node\_name:port5" Let me repeat - that is Graphviz syntax, not GraphViz2 syntax. In Perl, you must do this: $graph -> add_edge(from => 'struct1:f1', to => 'struct2:f0', color => 'blue'); You don't have to quote all node names in [Graphviz](http://www.graphviz.org/), but some, such as digits, must be quoted, so I've decided to quote them all. ## How labels interact with ports You can specify labels with ports in these ways: - As a string $graph -> add_node(name => 'struct3', label => "hello\nworld |{ b |{c| d|e}| f}| g | h"); Here, the string contains a port (<here>), field markers (|), and orientation markers ({}). Clearly, you must specify the field separator character '|' explicitly. In the next 2 cases, it is implicit. Then you use $graph -> add\_edge(...) to refer to those ports, if desired: $graph -> add_edge(from => 'struct1:f2', to => 'struct3:here', color => 'red'); The same label is specified in the next case. - As an arrayref of hashrefs From scripts/record.2.pl: $graph -> add_node(name => 'struct3', label => [ { text => "hello\nworld", }, { text => '{b', }, { text => '{c', }, { port => '', text => 'd', }, { text => 'e}', }, { text => 'f}', }, { text => 'g', }, { text => 'h', }, ]); Each hashref is a field, and hence you do not specify the field separator character '|'. Then you use $graph -> add\_edge(...) to refer to those ports, if desired. Again, from scripts/record.2.pl: $graph -> add_edge(from => 'struct1:f2', to => 'struct3:here', color => 'red'); The same label is specified in the previous case. - As an arrayref of strings From scripts/html.labels.1.pl: $graph -> add_node(name => 'Oakleigh', shape => 'record', color => 'blue', label => ['West Oakleigh', 'East Oakleigh']); Here, again, you do not specify the field separator character '|'. What happens is that each string is taken to be the label of a field, and each field is given an auto-generated port name of the form "<port$n>", where $n starts from 1. Here's how you refer to those ports, again from scripts/html.labels.1.pl: $graph -> add_edge(from => 'Murrumbeena', to => 'Oakleigh:port2', color => 'green', label => 'Run
Sprint>'); See also the docs for the `add_node(name => $node_name, [%hash])` method. ## Attributes for clusters Just use subgraph => {...}, because the code (as of V 2.22) accepts attributes belonging to either clusters or subgraphs. An example attribute is `pencolor`, which is used for clusters but not for subgraphs: $graph->push_subgraph( graph => {label => 'Child the Second'}, name => 'cluster Second subgraph', node => {color => 'magenta', shape => 'diamond'}, subgraph => {pencolor => 'white'}, # White hides the cluster's frame. ); # other nodes or edges can be added within it... $graph->pop_subgraph; # TODO - Handle edges such as 1 -> 2 -> {A B}, as seen in [Graphviz](http://www.graphviz.org/)'s graphs/directed/switch.gv But how? - Validate parameters more carefully, e.g. to reject non-hashref arguments where appropriate Some method parameter lists take keys whose value must be a hashref. # A Extremely Short List of Other Graphing Software [Axis Maps](http://www.axismaps.com/). [Polygon Map Generation](http://www-cs-students.stanford.edu/~amitp/game-programming/polygon-map-generation/). Read more on that [here](http://blogs.perl.org/users/max_maischein/2011/06/display-your-data---randompoissondisc.html). [Voronoi Applications](http://www.voronoi.com/wiki/index.php?title=Voronoi_Applications). # Thanks Many thanks are due to the people who chose to make [Graphviz](http://www.graphviz.org/) Open Source. And thanks to [Leon Brocard](http://search.cpan.org/~lbrocard/), who wrote [GraphViz](https://metacpan.org/pod/GraphViz), and kindly gave me co-maint of the module. # Version Numbers Version numbers < 1.00 represent development versions. From 1.00 up, they are production versions. # Repository [https://github.com/ronsavage/GraphViz2.git](https://github.com/ronsavage/GraphViz2.git) # Author [GraphViz2](https://metacpan.org/pod/GraphViz2) was written by Ron Savage __ in 2011. Home page: [http://savage.net.au/index.html](http://savage.net.au/index.html). # Copyright Australian copyright (c) 2011, Ron Savage. All Programs of mine are 'OSI Certified Open Source Software'; you can redistribute them and/or modify them under the terms of The Perl License, a copy of which is available at: http://dev.perl.org/licenses/ GraphViz2-2.67/META.yml0000644000175000017500000000162514266245444014502 0ustar osboxesosboxes--- abstract: "A wrapper for AT&T's Graphviz" author: - 'Ron Savage ' build_requires: ExtUtils::MakeMaker: '0' Test::More: '0.88' Test::Snapshot: '0.06' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 0 generated_by: 'ExtUtils::MakeMaker version 7.44, CPAN::Meta::Converter version 2.150010' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: GraphViz2 no_index: directory: - t - inc requires: Data::Section::Simple: '0.02' File::Which: '1.21' Graph: '0.9716' IPC::Run3: '0.048' Moo: '2.001001' Types::Standard: '1.000005' perl: '5.008008' resources: bugtracker: https://github.com/graphviz-perl/GraphViz2/issues license: http://dev.perl.org/licenses/ repository: https://github.com/graphviz-perl/GraphViz2.git version: '2.67' x_serialization_backend: 'CPAN::Meta::YAML version 0.018' GraphViz2-2.67/Changes0000644000175000017500000010516314266245377014533 0ustar osboxesosboxes2.67 2022-07-21 - update to latest allowed entity attributes - thanks @nowthis 2.66 2021-02-21 - from_graph not drop isolated vertices - thanks @jhannah-mm for report 2.65 2021-01-09 - undirected edges don't get doubled in from_graph, changes dot_input 2.64 2021-01-01 - fix multigraph-handling 2.63 2021-01-01 - from_graph to handle multigraphs 2.62 2020-11-16 - fix label quoting 2.61 2020-11-08 - support Perl 5.8.8 2.60 2020-11-04 - fix GraphViz2->from_graph object method - Parse::STT add visualise modes: REs become nodes or ports for clearer transitions - more robust quoting for record ports - add handling array-ref for record ports 2.59 2020-10-31 - GraphViz2->from_graph, dep on Graph, use as intermediate in Parse::Regexp - convert Parse::Yacc, Parse::Yapp, Parse::STT to graph-based 2.58 2020-10-19 - add combine_node_and_port global option - add_node(label => [{port=>...}]) no need <> 2.57 2020-10-15 - drop unnecessary use of "open" and "charnames" - setting defaults for subgraphs no longer affects outside those - now let DOT figure out inheritance rather than respecifying everything in subgraphs - fixed docs on controlling subgraph attributes 2.56 2020-10-13 - fix problem when "which" returns empty list not scalar - thanks @eserte for report 2.55 2020-10-13 - fix bugs in webgen scripts - make dot_input be lazy-built - zap undoc-ed dependency method that actually belongs in ::Parse::ISA 2.54 2020-10-09 - zap report_valid_attributes method - drop Log::Handler, any object that can "debug" or "error" works 2.53 2020-10-09 - drop Try::Tiny 2.52 2020-10-09 - zap obsolete special case in a test/webgen for Darwin - fix #7. Thanks @dbevans for report! 2.51 2020-10-08 - DOT input now prettier - tests rearranged so can run in parallel, ergo quicker - tests now rigorously capture DOT input in each scenario - generating web content updated, output removed from distro so smaller - drop Set::Array 2.50 2020-10-02 - dropped 3 more unnecessary deps, metadata tweak 2.49 2020-09-27 - split out into own distro: GraphViz2::DBI, GraphViz2::Parse::XML, GraphViz2::Parse::ISA, GraphViz2::Data::Grapher, GraphViz2::Parse::RecDescent 2.48 2020-09-17 - updated graphviz URLs to current graphviz.org ones - use GitHub issue tracker 2.47 2018-02-23T10:06:00 - Adopt github pull request from ThornyS, with thanx. This is part of CPAN via the PRC aka Pull Request Challenge. - Adjust tests in t/test_more_methods.t to work with BSD-based systems, and for systems which don't use '/' as a dir separator. As a consequence, running the tests no longer leaves test output files in t/. - Fix some code in Config.pm which triggered a warning from Perl which said: 'panic: attempt to copy freed scalar ...'. 2.46 2017-05-11T09:57:00 - Change the default value for the 'graph' attribute within GraphViz2::Parse::ISA from {} to ''. This makes it match the default for each other module in the GraphViz2::Parse::* namespace. The problem was reported by Kevin Ryde (RT#121599) who used GraphViz2::Parse::ISA all by itself, without using it via GraphViz2, and the BUILD sub has a check on the pre-existing value of $self -> graph, which returned {} instead of the empty string, so the code did not initialize $self -> graph properly with a new GraphViz2 object. 2.45 2017-04-16T10:03:00 - In stringify_attributes(), check for undefined hash entries before processing them. If detected, they are set to ''. Reported by Raphael Crochet. See RT#121122. - Bump pre-req version of File::Which from V 1.09 to 1.21. This should solve issues of which() not finding *.exe files under Cygwin. See File::Which's Changes file for details. Reported by Raphael Crochet. See RT#121090. - Reduce pre-req version of Test::More from 1.001014 to 1.001002. 2.44 2016-10-24:08:52:00 - The patch in V 2.43 assumed one of the output files had no path, and used that name as a file name within a temporary directory to generate a *.gv file for dot. The code now just uses 'temp.gv'. Thanx to jahagirdar for reporting this via the github repo. 2.43 2016-10-20T18:20:00 - Alongside the parameters format and output_file, add im_format and im_output_file for image maps. - Add docs for the new parameters. - User Types::Standard to tighten constraints on parameters passed to new(). - Add maps/ containing various demos. 2.42 2016-08-02T12:29:00 - Seems I misunderstood something simple. This time, I've eliminated shape 'square'. Thanx again to Daran Davis. 2.41 2016-08-02T09:00:00 - To preempt more problems, I've made the same change as in V 2.40, in report.nodes.and.edges.pl, html.labels.1.pl, sub.graph.pl, sub.sub.graph.pl, trivial.pl and sub.graph.frames.pl. I can't explain why the tickets mentioned in 2.40 did not include these examples. 2.40 2016-08-02T08:41:00 - In scripts/rank.sub.graph.*.pl, change shape 'box' to 'doublecircle'. Some users have reported errors with 'box'. See RT#81467 (Reported by D. Thomas on 2012-11-26, which I rejected. My apologies to D. Thomas), and RT#116628 (Reported by Daran Davis on 2016-08-01, which I now accept). I tested firstly by using 'rect' instead of 'box', and that also works for me (the output is the same as with 'rect'), but using 'doublecircle' is visually more impressive. Also, I hope all versions of Graphviz in use will support 'doublecircle'. I can't see anything in the changelog for Graphviz to say when 'box' became available, but it must have been years ago now. 2.39 2016-05-23T12:24:00 - Escape { in regexp in t/test.t to keep recent Perls happy. - Add a dependency on Perl V 5.10.1. 2.38 2016-05-03T15:22:00 - Remove confusing para about $label in docs for add_edge(). See RT#105861. Thanx BOBMATH. - Change licence to Perl. See LICENSE file. - Update some pre-reqs. - Adopt new repo format. - Wind back min Perl version from 5.14.2 to 5.10.1 and File::Copy from 2.21 to 2.14. - Remove t/version.t in order to get rid of Test::Version. 2.37 Sun Oct 11 12:19:00 2015 - Fix the default for the graph parameter in GraphViz2::Parse::*, to be '' instead of {}. See notes for V 2.36 for more info. GraphViz2::Parse::Regexp was fixed in V 2.36. This versions fixes all the other modules in lib/GraphViz2/Parse/. - Rename the internal attribute and method 'isa' to 'is_a', to avoid clashing with UNIVERSAL::isa. Thanx to Kevin Ryde to suggesting this. - In scripts/html.labels.2.pl, rename palegreen to palegreen. - Re-generate the demo page with the new version #. 2.36 Tue Oct 6 20:43:00 2015 - Fix the default for the graph parameter in GraphViz2::Parse::Regexp, to be '' instead of {}. The {} tricked the 'if' in sub BUILD into not initializing the 'graph' attribute and hence forced the caller to provide a value. See RT#107566. Reported by Kevin Ryde. - Remove .gitignore from MANIFEST. Add it to MANIFEST.SKIP. 2.35 Wed Feb 18 16:32:00 2015 - Rename CHANGES to Changes as perl CPAN::Spec::Changes. - Add github repo to Build.PL. - Add LICENSE to disto and MANIFEST. 2.34 Mon Oct 27 13:31:00 2014 - Expand the algorithm used to find a primary table/key pair for a given foreign table/key. - Expand the FAQ to explain this algorithm. It's the first Q/A in the FAQ. - Reformat the docs to have a max of 100 chars per line. - Reformat this file to have a max of 100 chars per line. 2.33 Sat Aug 30 08:27:00 2014 - Stop emitting empty node labels. Thanx to Fjodor Fedov (see RT#98426). - Update docs on how to download AT&T's Graphviz. Thanx to Alex Becker (see RT#98405). 2.32 Mon Aug 18 15:57:00 2014 - In subs add_edge() and add_node(), remove leading and trailing whitespace from HTML tables. This is formatting sugar, but it also affects the regexp used later when stringify_attributes() is called. The same patch was put into stringify_attributes() in V 2.20, but there are cases when this whitespace processing must take place somewhat before that sub is called. See RT#98098. Many thanx to Andrew Beverley for reporting this. - Add scripts/html.labels.3.pl to test the above. - Replace Perl6::Slurp with File::Slurp, as part of my policy to reduce the # of modules used. 2.31 Fri Aug 8 11:37:00 2014 - Re-write the code which splits port (and compass) off node names. A single regexp was not good enough. 2.30 Tue Aug 5 16:22:00 2014 - Expand GraphViz2::DBI to associate foreign keys column names with the corresponding primary table's primary key name. Fallback is to the previous behaviour. See the FAQ for GraphViz2::DBI for details. - The new code requires DBIx::Admin::TableInfo V 3.00, and Lingua::EN::PluralToSingular V 0.14. - Add sequential numbers to the column names. There are actually the Graphviz port numbers. The table name is port 0. - Make foreign key edges point to the table name, rather than the primary key name. This shifts such edges from entering the right-hand side of the table to entering via the left, simplifying the image, since they no longer overlap with edges entering or leaving the right-hand side. 2.29 Wed Jul 9 16:46:00 2014 - Add exclude => [qw/table_1 table_2 .../] and include => [qw/table_3 table_4 .../] options to GraphViz2::DBI.create(). 2.28 Tue Feb 18 15:25:00 2014 - Remove the method get_column_info(). - Patch GraphViz2::DBI method create() to use DBIx::Admin::TableInfo, rather than approximately duplicate the code. - The hashref returned by table_info() has some changed some keys: o New: attributes o Changed: column_names => columns o Unchanged: foreign_keys o New: primary_keys Further, 'column_names' used to point to an array. 'columns' now points to a hashref. - Remove 'use utf8' from modules, because it's global (affects all modules loaded I gather). - Remove 'use open qw(:std :utf8)'. - Remove 'use charnames qw(:full :short)' because it is never used. - Update scripts/dbi.schema.pl to use $ENV{DBI_SCHEMA} for Postgres, if set. 2.27 Tue Feb 11 13:40:00 2014 - Stop using bare word file handles (again). - Change sub BUILD() in all sub-classes to check whether or not a 'graph' parameter was supplied to new(). If so, use it rather than instantiating a new GraphViz2 object. This fixes a problem with those script/*.pl demos which use this feature. - Fiddle t/sample.recdescent.1.dat, since the original test data happened to look like HTML, and that stopped GraphViz2.stringify_attributes() adding double-quotes to protect the label. That in turn caused dot to issue a syntax error when the generated DOT data was read. 2.26 Thu Jan 30 12:44:00 2014 - Stop using bare word file handles. - Remove these lines from GraphViz2.pm (because utf8 is global and the others are not needed): use utf8; use open qw(:std :utf8); # Undeclared streams in UTF-8. use charnames qw(:full :short); # Unneeded in v5.16. 2.25 Mon Jan 6 17:06:00 2014 - Remove debug printing of the output of 'dot -T?' (a list of valid output formats). - Fix typos in output names in rank.sub.graph.1.pl and rank.sub.graph.2.pl. rank.sub.graph.1.pl was outputting to html/rank.sub.graph.2.svg and visa versa. 2.24 Mon Dec 2 09:30:00 2013 - Rewrite scripts/extract.output.formats.pl, which used to download a page of documentation from graphviz.org, and parse it to build a list of output formats. The problems are: (1) Forgetting to run the script; (2) the on-line docs being out-of-date. So, as of this version, I now parse the output of 'dot -T?', which will not just be more accurate, but will also include the formats supported by locally-installed plugins. It's a win-win result. - Put that new logic into GraphViz2's load_valid_attributes() method. - Expand the FAQ to discuss the above operation. 2.23 Sun Dec 1 09:42:00 2013 - Update lists of valid attibutes by running scripts/extract.*.pl, and incorporating the changes into the source code of GraphViz2.pm (at the end). I use Data::Section::Simple to read this data. These lists now correspond to Graphviz V 2.34. Changes: o Remove 'aspect' as an attribute 'graph'. o Add 'inputscale' as an attribute of 'graph'. o Add 'overlap_shrink' as an attribute of 'graph'. o Add 'star' as an attribute of 'node shape'. o Add 'underline' as an attribute of 'node shape'. o Add 'xdot1.2' as an attribute of 'output format'. o Add 'xdot1.4' as an attribute of 'output format'. See RT#91073. Many thanx for Kent Fredric for bringing this to my attention. - Add t/version.t, which uses Test::Version. - Update pre-reqs. - Add use strict/warnings to Build.PL and Makefile.PL. 2.22 Fri Sep 6 16:03:00 2013 - Fix handling of graph attributes which are for clusters but not for subgraphs. See scripts/sub.graph.frames.pl for sample code. See the demo page for the output of that script. See the new FAQ item 'How do I specify attributes for clusters?' for details. Many thanx to Bill Hollingsworth (private email) for prompting me to investigate this bug. - Document method default_subgraph(). 2.21 Fri Sep 6 13:00:00 2013 - Replace Path::Tiny with File::Spec, because the former's list of dependencies is soooo long. Changed files: GraphViz2::Config, scripts/copy.config.pl, scripts/find.config.pl, Build.PL and Makefile.PL. See: RT#88435 (for Tree::DAG_Node) for an explanation. 2.20 Tue Aug 27 16:22:00 2013 - In sub stringify_attributes(), remove leading and trailing whitespace from HTML tables like so: s/^\s+(<)/$1/ and s/(>)\s+$/$1/. This is formatting sugar, but it also affects the next regexp. - In the same sub, change a regexp from /^<.+>$/ to /^<.+>$/s. Note trailing 's'. This allows '.' to match newlines within HTML lables. This fixes a bug (not yet reported). - Both these changes allow HTML labels to be more nicely formatted in the source code of the module which uses GraphViz2 (such as MarpaX::Grammar::GraphViz2). - Split scripts/html.labels.pl into scripts/html.labels.1.pl and scripts/html.labels.2.pl. These demonstrate the 2 types of HTML labels. - Document sub escape_some_chars(). - Add an FAQ item on using whitespace in HTML labels. 2.19 Tue Aug 20 10:44:00 2013 - Remove the global graph attribute 'record_orientation', as forewarned in V 2.10, in June. - Fix handling of some global attributes. Previously, some options mentioned in the call to new(...) were not set by that call. The options affected were: 'driver', 'format', 'strict', 'subgraph' and 'timeout'. The options 'driver', 'format', and 'timeout' could be set in the call to run(...), but in new(...), the value of 'format' was ignored, and the value of 'subgraph' generated an error referring to '... not a valid attribute ...'. Thanx to Larry Knibb for reporting this problem, for 'driver'. - Remove reference to GraphViz2::Parse::Marpa from scripts/pod2html.sh. See comment below for V 2.15. This module has been replaced by MarpaX::Grammar::GraphViz2, which depends on MarpaX::Grammar::Parser. 2.18 Wed Aug 7 09:22:00 2013 - Remove print from sub BUILD() left over from debugging. - Update Build.PL and Makefile.PL to include bugtracker and license items. Also replace the Perl version checking code with a simple 'require 5.014.002; # For the utf8 stuff.'. 2.17 Sat Aug 3 08:58:00 2013 - Fix handling of record shapes. E.g. label => [] ignored global default shape 'Mrecord'. Thanx to Kent Fredric for this report. See RT#87560. - Fix handling of the graph's default name. This is used in 'digraph $name {...}'. Previously, names like '' or '0' were ignored, and such values defaulted to 'Perl'. - Add scripts/record.4.pl to demonstrate setting record-style node attributes. - Fix names of html/utf8*.svg and png files, as output by scripts/utf8*.pl. - Fix scripts/generate.sh.pl which generates scripts/generate.(png,svg).sh. 2.16 Fri Aug 2 10:54:00 2013 - Patch GraphViz2.pm to handle both SVG and (eg) PNG output. I failed to test this properly. Thanx to Kent Fredric for this report. See RT#87525. - Patch scripts/Heawood.pl to use font ArialMT instead of Arial when running under Darwin (OSX). Thanx to David Jack Olrik for this report. See RT#87478. - Remove special case code in scripts/jointed.edges.pl. It used png:gd instead of png, to work round an unknown Graphviz problem. Let's hope /your/ version of Graphviz is up-to-date. - Patch scripts/circo.pl: Add comment searched for by t/test.t, which is used to label html/index.html. - Add scripts/generate.sh.pl to generate scripts/generate.png.sh and scripts/generate.svg.sh. This adds 2 tests, and 2 images to the demo page, and fixes various typos in those *.sh files. - Switch from Hash::FieldHash to Moo. - Add config/.htgraphviz2 to help the author generate the demo. - Add GraphViz2::Config.pm, and split GraphViz2::Filer off from GraphViz2::Utils, which alsp helps generate the demo. - Switch from Hash::FieldHash to Moo. - Use File::Slurp except where I need Perl6::Slurp's utf8 option. I needed File::Slurp's write_file() for the new script scripts/generate.sh.pl, so I decided to use it in most places. - Rename scripts/utf8.pl to scripts/utf8.1.pl. - Rename scripts/utf8.test.pl to scripts/utf8.2.pl. - Rename html/utf8.svg to html/utf8.1.svg. - Rename html/utf8.test.svg to html/utf8.2.svg. 2.15 Mon Jul 29 14:42:00 2013 - Remove GraphViz2::Parse::Marpa, until it's re-written to use Jeffrey Kegler's code to dump a grammar. - Add scripts/circo.pl and it's output html/circo.svg. - For non-HTML labels, escape double-quotes if they are not already escaped. This allows pathological labels such as '\"'. - For all labels, escape '[' and ']' if they are not already escaped. The rationale for this is shrouded in the mists of time :-(. - Put author tests in xt/author. - Add Algorithm::Dependency::Source::HoA V 1.110 to the pre-reqs to keep my home-grown Build.PL and Makefile.PL checker a bit quieter. - Add Config V 0, channames V 0 and open V 0 to the pre-reqs. - Move t/pod.t to xt/author/. - Switch from IPC::Run to IPC::Run3. This after a discussion with Larry Knibb re the fact that his code is hanging under Apache (mod_cgi) on Windows. He suggested using qx//, but I've gone for IPC::Run3. The other reason to switch is the overly-long bug list for IPC::Run, including Larry's report RT#87397. I went thru the same issues with Lee when switching from system() to IPC::Run for V 2.02. See RT#76459. IPC::Run3 has the advantage of letting me set binmode on various file handles. - For all modules and some scripts, convert: use strict; use warnings; into: use strict; use utf8; use warnings; use warnings qw(FATAL utf8); # Fatalize encoding glitches. use open qw(:std :utf8); # Undeclared streams in UTF-8. use charnames qw(:full :short); # Unneeded in v5.16. 2.14 Mon Jul 1 10:09:00 2013 - Patch push_subgraph() to correctly handle the case of an unnamed subgraph. The code was outputting 'subgraph "" {...}'. The extra "" are now suppressed. The code also handles the case of the name being undef. There are no doc changes because the docs described precisely what should have happened, thusly: So, without $name, 'subgraph {' is written to the output stream. With $name, 'subgraph "$name" {' is written to the output stream. Many thanx (again) to Larry Marso for reporting this, with sample code. - Add scripts/unnamed.sub.graph.pl. 2.13 Fri Jun 28 12:02:00 2013 - Oops - Patch scripts/record.1.pl as was allegedly done for V 2.11. 2.12 Thu Jun 27 14:40:00 2013 - Add scripts/plaintext.pl, which generates a 'Use of uninitialized value...' error under V 2.11 and, after the fix, does not do so under V 2.12. Many thanx to Larry Marso for the (private email) report. 2.11 Thu Jun 27 09:21:00 2013 - Correct spelling of Kent Fredric's name below, with apologies. - Patch scripts/record.1.pl and scripts/record.2.pl to use '\\n' to get a literal '\n' in the output dot file. The original works my Debian machine, but needs fixing in case there's someone out there not using Debian :-). - Remove debug log message from add_edge(). 2.10 Mon Jun 24 11:05:00 2013 - Overview: Re-work the label and port handing code. - Note: the global graph attribute 'record_orientation' no longer does anything. The new label syntax, (next, and in detail in the FAQ (https://metacpan.org/module/GraphViz2#How-labels-interact-with-ports) ), is now the recommended way of using labels to specify both ports and orientation. Using 'record_orientation' will not cause parameter validation to fail, it just won't have any effect. The attribute will be removed in a future version, so prepare now by deleting it from your code. - Labels can be a string, an arrayref of strings, or an arrayref of hashrefs. The latter alternative is new. The keys to the hashrefs are 'text' and 'port', with the latter being optional. See the FAQ topic mentioned above. See scripts/record.2.pl for sample code. Many thanx to Kent Fredric for the report (RT#85976), and the list of suggestions. - Add scripts/record.3.pl and add it to the demo generating code scripts/generate.*.sh. It demonstrates deeply nested record structures using a string as a label. The same effect could be achieved by using an arrayref of hashrefs, of course. scripts/record.2.pl shows how. - Stop escaping the 2 label characters { and }, since they are used to orient fields within records. On the demo page http://savage.net.au/Perl-modules/html/graphviz2/, see scripts/record.*.pl. Expand the FAQ discussion of escaping to cover this issue. - Remove restriction that port names in calls to add_edge() had to start with 'port'. This was due to my misreading of the Graphviz docs, where all examples used a 'port' prefix. The code also now checks for '*::*', in case the program is using Perl classes for node names, in which case we don't want the first ':' to be taken as the introduction for a port name. - Update words/tokens (arrow shapes etc) stored within the source code, by running scripts/extract.*.pl and storing the output in lib/GraphViz2.pm after the __DATA__ token. Yes, I know this is hard-coding. See the amazing module Data::Section::Simple for details. The set of words/tokens matches Graphviz 2.30.1, as of today, 2013-06-24. - Rename CHANGES to Changes as per CPAN::Changes::SPEC. - Reformat the POD in lib/GraphViz2.pm slightly. - Remove scripts/parse.marpa.pl and t/sample.marpa.1.dat, as a step in removing all Marpa-related material, because it uses the deprecated NAIF interface. All new Marpa work should use the scanless interface (SCIF). - Change Build.PL and Makefile.PL to check for Perl being at least V 5.14.0. If you are using an earlier version, you can forgo utf8 support by editing the files to relax this restriction. Some tests (utf8.pl, utf8.test.pl) will presumably fail as a consequence. Lastly, my attention has been drawn to Unicode::Semantics::up(), but I've chosen not to use it. 2.09 Fri May 31 09:57:00 2013 - Re-write the code in Build.PL and Makefile.PL which checks for Graphviz (dot) being installed, since the previous code, using a pipe, was failing on some versions of Windows. - Assume Config.pm is installed, and hence remove it from the pre-reqs. This also stops a warning message generated because Config's version # is undef. - Likewise assume File::Spec and File::Temp are installed, and 'recent enough'. This is because the new code uses these 3 modules before specifying the pre-reqs. - Bump the pre-req for Perl from 5.10.0 to 5.14, since we 'use feature qw/unicode_strings/. - Re-write the code in Build.PL and Makefile.PL which checks for Perl being 'recent enough', since the previous code, using a pre-req of "perl => '5.10.0'" generates a warning message when using Makefile.PL (for my current Perl V 5.14.2). Now we analyze $Config{version}. - One CPAN Tester found tests failing even though Graphviz was installed. The problem was that the Times font was missing. The new code should fail during 'perl Build.PL', or 'perl Makefile.PL', rather than during testing, which is good. 2.08 Thu Mar 21 13:16:00 2013 - Add $ENV{DBI_SCHEMA} to scripts/dbi.schema.pl. - For the MusicBrainz database, use DBI_SCHEMA=musicbrainz,cover_art_archive,report,statistics. See http://musicbrainz.org/doc/MusicBrainz_Database for details. Users of cpanm will want 'cpanm Carton' instead of 'sudo cpan Carton' in Perl dependencies. See https://github.com/metabrainz/musicbrainz-server/blob/master/INSTALL.md for details. 2.07 Wed Mar 13 13:24:00 2013 - Extend GraphViz2::DBI to handle SQLite using pragma foreign_key_list($table_name). - Add scripts/sqlite.foreign.keys.pl to help analyze that pragma's output. - Remove the string 'App-Office-CMS' from scripts/dbi.schema.pl. That is, the create() method is called as $g -> create(name => ''). This has the effect of removing the global node from the resultant graph. All tables were descendents of this node, but with schemas of dozens or hundreds of tables, it became confusing. - Patch dbi.schema.pl to set the DBI connect attr sqlite_unicode and foreign_keys pragma if using SQLite. 2.06 Thu Nov 8 12:38:00 2012 - No code changes. - For pre-reqs such as strict, warnings, etc, which ship with Perl, set the version # to 0. Reported as RT#80663 by Father Chrysostomos for Tree::DAG_Node. 2.05 Tue Oct 2 10:20:00 2012 - No fundamental code changes, so no need to upgrade, unless you need the following new features. - After a request from Jack Maney, author of the Perl module Hypatia: o Document the mutator node_hash(), which returns a hashref keyed by node name. Use this to get a list of all nodes and their attributes. o Add a new mutator, edge_hash(), which also returns a hashref keyed by node name. The node is the one at the arrow/tail/ end of the edge, i.e. where the edge starts from. Use this to learn all sorts of things about the edge. o Add scripts/report.nodes.and.edges.pl (a version of scripts/html.labels.pl) to demonstrate how to access this data. o Update to POD to match. 2.04 Fri Aug 17 10:48:00 2012 - Add Perl V 5.14.2 to the pre-reqs, for the utf8 stuff. - Re-write the subgraph handling code, which was broken up to V 2.03. Add samples, script/rank.sub.graph.[1234].pl, to demonstrate subgraph name effects and node rankings. script/rank.sub.graph.[12].pl use subgraphs to force a set of nodes to be horizontally aligned. script/rank.sub.graph.[34].pl show the effects of subgraph name changes on the same data. 2.03 Mon Jun 18 9:47:00 2012 - Switch from double to single quotes in line 22 of GraphViz2::Parse::Regexp, so the resultant string, treated as Perl code, runs on Windows. Reported by Max Maischein as RT#77869. - Also, slightly reformat line 39 of that module. 2.02 Thu Apr 19 11:51:00 2012 - Accept patch from Lee as in RT#76459, to replace the code which writes the dot input file to a file with binmode, and just pass the data to dot via IPC::Run. Happily, this allows me to eliminate 'use open qw/:encoding(UTF-8) :std/;' in t/test.t. - Update the pre-reqs in Build.PL and Makefile.PL. - Add scripts/utf8.test.pl to the list of scripts shipped with this module. - Add scripts/utf8* to scripts/generate.*.sh. - Make scripts/generate.*.sh emit a warning when DBI_DSN is not set. 2.01 Wed Mar 7 08:50:00 2012 - I only tested V 2.00 by outputting to SVG (a text format), but outputting to a binary format such as PNG was broken. So, remove the 'use open qw/:encoding(UTF-8) :std/;', and restore binmode, in GraphViz2.pm. - Remove log to screen in utf8.pl, since Log::Handler doesn't accept utf8 as a logger option. - Copy scripts/utf8.pl to scripts/utf8.test.pl and edit to display just 5 delta characters. See html/utf8.test.svg. PNG is ok too. This demonstrates (hopefully) we can get the correct output on a binary format despite the 'Wide character in print...' message. - Add FAQ topic regarding this 'Wide character in print...' problem. 2.00 Tue Mar 6 16:02:00 2012 - Support utf8 in labels. This required changes to GraphViz2.pm. See scripts/utf8.pl and html/utf8.svg. I'm using Perl V 5.14.2. Sample output is online at http://savage.net.au/Perl-modules/html/graphviz2/utf8.svg. - Add an item to the FAQ about how to write scripts using utf8. - Re-write GraphViz2::Parse::ISA to draw multiple class hierarchies on 1 graph. This means the API for that class has changed. Specifically, create() no longer exists. Call add() 1 or more times instead. Then, call generate_graph(). See the docs for details. Sample output is online at http://savage.net.au/Perl-modules/html/graphviz2/parse.isa.svg. 1.13 Sun Dec 25 10:33:00 2011 - Change to in the demo creation code, to keep poor old FireFox happy. - Change various things in html/graphviz2.index.tx to we validate as XHTML 1.0 Strict. - Unreleased. 1.12 Wed Dec 14 11:22:00 2011 - Add 5 scripts, scripts/macro.*.pl, to demonstrate using a Perl sub as a macro to generate both cluster and non-cluster sub-graphs. - Update the TODO in GraphViz2.pm, since it erroneously gave the impression the current code did not handle compound clusters. - Re-generate the demo. See: http://savage.net.au/Perl-modules/html/graphviz2/. - Adopt Flavio Poletti's suggestion of trying to pipe to dot, in Build.PL/Makefile.PL, rather than using File::Which, to see if dot (Graphviz) is installed. This (hopefully) solves the problem of using File::Which on systems where it is not installed, before Build.PL/Makefile.PL has a chance to tell the user that File::Which is required. See: RT#73077. - No code changes in *.pm files. 1.11 Tue Nov 8 10:21:00 2011 - Fix bug where double quotes in HTML labels were being escaped and should not have been. Many thanx to Fitz Elliott for the report and initial patch. See https://rt.cpan.org/Ticket/Display.html?id=72259. - Patched scripts/html.labels.pl as per Fitz's suggested test code. - Rename t/lib/Parent.pm to t/lib/Adult.pm to avoid a reported problem with "use parent 'Parent';" under Windows. This affect various files. See https://rt.cpan.org/Public/Bug/Display.html?id=69076. 1.10 Thu Sep 1 10:45:00 2011 - Fix bug in GraphViz in handling of nodes whose shape is none or plaintext, and whose label is HTML. - Fix bug in scripts/generate.png.sh to add png parameter to 'perl -Ilib scripts/generate.demo.pl png'. - HTML labels work without any code changes, so references to them not working have been removed from the docs. - GraphViz2 is called by the now-released Graph::Easy::Marpa, so remove that item from the TODO list. - Fix doc referring to scripts/generate.index.pl. It now refers to scripts/generate.demo.pl. - Add DOCTYPE and Content-type to html/graphviz.index.tx. Output by scripts/generate.demo.pl, html/index.html, now handles UTF-8 properly. - Add alt to the tags output by scripts/generate.demo.pl. - Add scripts/html.labels.pl. This code includes a demo of using ports. - Reorder methods in GraphViz2::Utils to be in alphabetical order. 1.09 Thu Jul 21 14:13:00 2011 - Patch parameter validation to allow for output image types such as png:gd etc. Only the prefix before the first ':' is validated. 1.08 Thu Jul 21 11:53:00 2011 - Change the behaviour of sub log() in GraphViz.pm. Now if called with $level eq 'error', it dies with $message. - Change references to the color darkblue to blue, so it's compatible with both the X11 and SVG color schemes. - Likewise change chartreuse to lawngreen. - Rename scripts/generate.index.pl to scripts/generate.demo.pl, as a more meaningful name. - Patch scripts/generate.demo.pl to accept the output image type as a command line parameter. - Use this new feature to generate png files, and upload them to the demo site http://savage.net.au/Perl-modules/html/graphviz2/ even though png is often uglier than svg. I did this because Iceweasel (Firefox) V 3.5.16 was not displaying svgs within the index file,even though they are fine when displayed directly. - Add scripts/generate.png.sh. - Note: scripts/jointed.edges.pl must call dot with -Tpng:gd and not -Tpng, for unknown reasons. - Make both scripts/generate.png.sh and scripts/generate.svg.sh redirect their log files to /tmp, so that we don't have to ship the logs, and also so they don't keep changing and hence need checking in. - Remove ./dbi.schema.log from the distro. 1.07 Mon Jul 4 15:46:00 2011 - Use Date::Format to add a date stamp at the end of html/index.html, as output by generate.index.pl. 1.06 Tue Jun 28 11:10:00 2011 - Change usage of File::Temp -> newdir to fix problems testing on BSD-based systems. - Add scripts/jointed.edges.pl. This demo - in Graph::Easy syntax - ships with Graph::Easy::Marpa. - Re-write generate.index.pl to put all demo data (where available) and images on 1 page. - Upload demo to (new location) http://savage.net.au/Perl-modules/html/graphviz2/index.html. 1.05 Fri Jun 24 12:40:00 2011 - Implement GraphViz2::Parse::Marpa, along with scripts/parse.marpa.pl and t/sample.marpa.1.dat. The output is html/parse.marpa.svg. - Implement GraphViz2::Parse::STT, along with scripts/parse.stt.pl and t/sample.stt.1.dat. The output is html/parse.stt.svg. - Add use File::Spec to t/test.t 1.04 Wed Jun 22 9:36:00 2011 - Reduce required version of File::Basename to 2.77, which came with Perl 5.10.1. - Stop trying to write to t/html/, and use File::Temp for a directory instead. That way, it doesn't matter who owns t/html/, nor whether or not it's writable. 1.03 Sun Jun 19 16:27:00 2011 - Tweak File::Temp -> new to be File::Temp ->new(EXLOCK => 0) for BSD-based systems. 1.02 Fri Jun 17 8:36:00 2011 - Add the pre-requisite Log::Handler to Build.PL and Makefile.PL. - Release HTML::Entities::Interpolate V 1.04 and Set::Array V 0.23 to CPAN. - Add README file. - Clean up TODO list. 1.01 Wed Jun 15 15:00:00 2011 - Quote cluster/subgraph names so they may contain weird characters. - Add method dependency(data => $depend) to GraphViz2.pm, which accepts an object of type Algorithm::Dependency. See scripts/dependency.pl and html/dependency.svg. - Add GraphViz2::Parse::ISA, and scripts/parse.isa.pl, and the t/lib/Parent hierarchy. 1.00 Wed Jun 15 14:26:00 2011 - This is a re-write of GraphViz. The method parameter lists are incompatible. Sorry, but it now supports all options and attributes in Graphviz V 2.23.6. - Rewrite GraphViz, GraphViz::Data::Grapher, GraphViz::Parse::RecDescent, GraphViz::Parse::Yacc and GraphViz::Parse::Yapp. The core code of *::RecDescent, *::Yacc and *::Yapp has been copied from GraphViz, with tiny changes. - GraphViz2::Data::Grapher uses Tree::DAG_Node to hold information, before calling external plotting programs. The tree is available for you to process whether or not you actually plot the graph. - GraphViz::Regex renamed GraphViz2::Parse::Regexp. - GraphViz::XML renamed GraphViz2::Parse::XML. And it uses XML::Tiny by default. One demo shows how to use XML::Bare instead. - All new documentation. - All new demos, in scripts/*.pl. These are documented in GraphViz's POD. - All demo output included, in html/*.html and html/*.svg. GraphViz2-2.67/lib/0000755000175000017500000000000014266245444013773 5ustar osboxesosboxesGraphViz2-2.67/lib/GraphViz2/0000755000175000017500000000000014266245444015607 5ustar osboxesosboxesGraphViz2-2.67/lib/GraphViz2/Parse/0000755000175000017500000000000014266245444016661 5ustar osboxesosboxesGraphViz2-2.67/lib/GraphViz2/Parse/Yapp.pm0000644000175000017500000001377113743740357020143 0ustar osboxesosboxespackage GraphViz2::Parse::Yapp; use strict; use warnings; use warnings qw(FATAL utf8); # Fatalize encoding glitches. our $VERSION = '2.47'; use GraphViz2; use Moo; use Graph::Directed; my %EDGEATTR = (headport => 'port1'); my %GRAPHVIZ_ARGS = ( edge => {color => 'grey'}, global => {directed => 1, combine_node_and_port => 0}, graph => {rankdir => 'TB'}, node => {color => 'blue', shape => 'oval'}, ); has as_graph => ( is => 'lazy', required => 0, ); sub _build_as_graph { to_graph($_[0]->file_name) } has graph => ( is => 'lazy', #isa => 'GraphViz2', required => 0, ); sub _build_graph { GraphViz2->new(%GRAPHVIZ_ARGS)->from_graph(graphvizify($_[0]->as_graph)); } has file_name => ( is => 'rw', required => 0, ); sub read_file { open my $fh, '<:encoding(UTF-8)', $_[0] or die "$_[0]: $!"; map +((chomp, $_)[1]), <$fh>; } sub create { my ($self, %arg) = @_; $self->file_name($arg{file_name}); $self->graph->from_graph(graphvizify($self->as_graph)); return $self; } sub to_graph { my ($file_name) = @_; my $g = Graph::Directed->new; my (%edges, %labels); for my $line (read_file($file_name)) { next if ($line !~ /\w/) || ($line !~ /^\d+:\s+/); $line =~ s/^\d+:\s+//; my ($rule, $text) = split ' -> ', $line, 2; $text = '(empty)' if ($text eq '/* empty */'); push @{$labels{$rule}}, $text; @{$edges{$rule}}{split ' ', $text} = (); # only needs to exist } for my $f (keys %edges) { $g->add_edges(map [$f, $_], grep $edges{$_}, keys %{$edges{$f}}); $g->set_vertex_attribute($f, labels => $labels{$f}); } $g; } sub _quote { my $t = $_[0]; $t =~ s/\\/\\\\/g; $t; } sub graphvizify { my ($g) = @_; for my $v ($g->vertices) { $g->set_vertex_attribute($v, graphviz => { label => [$v, [ map _quote($_).'\\l', @{$g->get_vertex_attribute($v, 'labels')} ]], }); $g->set_edge_attribute(@$_, graphviz => \%EDGEATTR) for $g->edges_from($v); } $g->set_graph_attribute(graphviz => { global => $GRAPHVIZ_ARGS{global} }); $g; } 1; =head1 NAME L - Visualize a yapp grammar as a graph =head1 SYNOPSIS use GraphViz2::Parse::Yapp; # no objects - quicker my $gd = GraphViz2::Parse::Yapp::to_graph('t/calc.output'); # populate a GraphViz2 object with a Graph::Directed of a parser my $gv = GraphViz2->from_graph(GraphViz2::Parse::Yapp::graphvizify($gd)); # OO interface, using lazy-built attributes my $gvp = GraphViz2::Parse::Yapp->new(file_name => $file_name); my $gd = $gvp->as_graph; # Graph::Directed object # or supply a suitable Graph::Directed object my $gvp = GraphViz2::Parse::Yapp->new(as_graph => $gd); # then get the GraphViz2 object my $gv = $gvp->graph; # DEPRECATED ways to get $gvp with populated $gv my $gvp = GraphViz2::Parse::Yapp->new; $gvp->create(file_name => 't/calc.output'); my $gv = $gvp->graph; # or give it a pre-set-up GraphViz2 object my $gv = GraphViz2->new(...); my $gvp = GraphViz2::Parse::Yapp->new(graph => $gv); # call ->create as above # produce a visualisation my $format = shift || 'svg'; my $output_file = shift || "output.$format"; $gv->run(format => $format, output_file => $output_file); See F. =head1 DESCRIPTION Takes a yapp grammar and converts it into a L object, or directly into a L object. =head1 FUNCTIONS This is the recommended interface. =head2 to_graph my $gd = GraphViz2::Parse::Yapp::to_graph('t/calc.output'); Given a yapp grammar, returns a L object describing the finite state machine for it. =head2 graphvizify my $gv = GraphViz2->from_graph(GraphViz2::Parse::Yapp::graphvizify($gd)); Mutates the given graph object to add to it the C attributes visualisation "hints" that will make the L method visualise this regular expression in the most meaningful way, including labels and groupings. It is idempotent as it simply sets the C attribute of the relevant graph entities. Returns the graph object for convenience. =head1 METHODS This is a L class, but with a recommended functional interface. =head2 Constructor attributes =head3 file_name The name of a yapp output file. See F. This key is optional. You need to provide it by the time you access either the L or L. =head3 as_graph The L object to use. If not given, will be lazily built on access, from the L. =head3 graph The L object to use. This allows you to configure it as desired. This key is optional. If provided, the C method will populate it. If not, it will have these defaults, lazy-built and populated from the L. my $gv = GraphViz2->new( edge => {color => 'grey'}, global => {directed => 1}, graph => {rankdir => 'TB'}, node => {color => 'blue', shape => 'oval'}, ); =head2 create(regexp => $regexp) DEPRECATED. Mutates the object to set the C attribute, then accesses the C attribute (possibly lazy-building it), then Cs its C attribute with that information, then Cs its C. Returns $self for method chaining. =head1 THANKS Many thanks are due to the people who chose to make L Open Source. And thanks to L, who wrote L, and kindly gave me co-maint of the module. =head1 AUTHOR L was written by Ron Savage Iron@savage.net.auE> in 2011. Home page: L. =head1 COPYRIGHT Australian copyright (c) 2011, Ron Savage. All Programs of mine are 'OSI Certified Open Source Software'; you can redistribute them and/or modify them under the terms of The Perl License, a copy of which is available at: http://dev.perl.org/licenses/ =cut GraphViz2-2.67/lib/GraphViz2/Parse/Regexp.pm0000644000175000017500000002340013752041527020443 0ustar osboxesosboxespackage GraphViz2::Parse::Regexp; use strict; use warnings; use warnings qw(FATAL utf8); # Fatalize encoding glitches. our $VERSION = '2.48'; use IPC::Run3; # For run3(). use GraphViz2; use Moo; use Graph::Directed; my %GRAPHVIZ_ARGS = ( edge => {color => 'grey'}, global => {directed => 1}, graph => {rankdir => 'TB'}, node => {color => 'blue', shape => 'oval'}, ); my %STATE2LABEL = ( PLUS => '+', STAR => '*', ); my %NODETYPE2ARGS = ( exact => { shape => 'box', color => 'black' }, anyof => { shape => 'box', color => 'red' }, branch => { shape => 'diamond' }, ); my %TYPE2LABEL = ( anyof => sub { "[$_[0]]" }, exact => sub { $_[0] }, open => sub { "START \$$_[0]" }, close => sub { "END \$$_[0]" }, repeat => sub { "REPEAT $_[0]" }, branch => sub { '' }, nothing => sub { 'Match empty string' }, minmod => sub { 'Next operator\nnon-greedy' }, succeed => sub { 'SUCCEED' }, ); my %EDGETYPE2ARGS = ( of => { style => 'dashed' }, cond => { style => 'dashed' }, ); sub maybe_subgraph { my ($g, $v) = @_; return unless my @e = grep $g->get_edge_attribute(@$_, 'type'), $g->edges_from($v); { attributes => { subgraph => { rank => 'same' } }, nodes => [ $v, map $_->[1], @e ], }; } has as_graph => ( is => 'lazy', required => 0, ); sub _build_as_graph { to_graph($_[0]->regexp) } sub to_graph { my ($regexp) = @_; my $g = Graph::Directed->new; run3 [$^X, '-Mre=debug', '-e', q|qr/$ARGV[0]/|, $regexp], undef, \my $stdout, \my $stderr, ; my (%following, %states, $last_id); for my $line ( split /\n/, $stderr ) { next unless my ($id, $state) = $line =~ /(\d+):\s+(.+)$/; $states{$id} = $state; $following{$last_id} = $id if $last_id; $last_id = $id; } die 'Error compiling regexp' if !defined $last_id; my %done; my @todo = (1); while (@todo) { my $id = pop @todo; next if !$id or $done{$id}++; my $state = $states{$id} || ''; my $following = $following{$id}; $state =~ s/\s*\((\d+)\)$//; my $next = $1; push @todo, $following; push @todo, $next if $next; my $match; if ( ($match) = $state =~ /^EXACTF?L? <(.+)>$/ ) { $g->set_vertex_attributes($id, { type => 'exact', content => $match }); $g->add_edge($id, $next) if $next != 0; $done{$following}++ unless $next; } elsif ( ($match) = $state =~ /^ANYOF\[(.+)\]/ ) { $g->set_vertex_attributes($id, { type => 'anyof', content => $match }); $g->add_edge($id, $next) if $next != 0; $done{$following}++ unless $next; } elsif ( (my $matchtype, $match) = $state =~ /^(OPEN|CLOSE)(\d+)/ ) { $g->set_vertex_attributes($id, { type => lc $matchtype, content => $match }); $g->add_edge($id, $matchtype eq 'OPEN' ? $following : $next); } elsif ( $state =~ /^BRANCH/ ) { my $branch = $next; my @children; push @children, $following; while ($branch && ($states{$branch}||'') =~ /^BRANCH|TAIL/ ) { $done{$branch}++; push @children, $following{$branch}; push @todo, $following{$branch}; ($branch) = $states{$branch} =~ /(\d+)/; } $g->set_vertex_attributes($id, { type => lc $state }); $g->add_edges(map [$id, $_], @children); } elsif ( my ($repetition) = $state =~ /^(PLUS|STAR)/ ) { $g->set_vertex_attributes($id, { type => 'repeat', content => $STATE2LABEL{$repetition} }); $g->set_edge_attributes($id, $following, { type => 'of' }); $g->add_edge($id, $next); } elsif ( my ( $type, $min, $max ) = $state =~ /^CURLY([NMX]?)\[?\d*\]?\s*\{(\d+),(\d+)\}/ ) { $g->set_vertex_attributes($id, { type => 'repeat', content => "{$min,$max}" }); $g->set_edge_attributes($id, $following, { type => 'of' }); $g->add_edge($id, $next); } elsif ( $state =~ /^SUCCEED/ ) { $g->set_vertex_attributes($id, { type => lc $state }); $done{$following}++; } elsif ( $state =~ /^(UNLESSM|IFMATCH|IFTHEN)/ ) { $g->set_vertex_attributes($id, { type => lc $state }); $g->set_edge_attributes($id, $following, { type => 'cond' }); $g->add_edge($id, $next); } else { $g->set_vertex_attributes($id, { type => lc $state }); $g->add_edge($id, $next) if ($next||0) != 0; } } $g; } has graph => ( is => 'lazy', #isa => 'GraphViz2', required => 0, ); sub _build_graph { GraphViz2->new(%GRAPHVIZ_ARGS)->from_graph(graphvizify($_[0]->as_graph)); } has regexp => ( is => 'rw', required => 0, ); sub create { my ($self, %arg) = @_; $self->regexp($arg{regexp}); $self->graph->from_graph(graphvizify($self->as_graph)); return $self; } sub graphvizify { my ($g) = @_; my @groups; for my $v (sort $g->vertices) { push @groups, maybe_subgraph($g, $v); my $attrs = $g->get_vertex_attributes($v); my $type = $attrs->{type}; my $labelmaker = $TYPE2LABEL{$type}; my $label = $labelmaker ? $labelmaker->(GraphViz2::_dor($attrs->{content}, '')) : uc $type; $g->set_vertex_attribute($v, graphviz => { label => $label, %{$NODETYPE2ARGS{$type}||{}} }); for my $e (sort {$a->[1] cmp $b->[1]} $g->edges_from($v)) { my $e_attrs = $g->get_edge_attributes(@$e); my $e_type = $e_attrs->{type}; $g->set_edge_attribute(@$e, graphviz => $EDGETYPE2ARGS{$e_type||''}||{}); } } $g->set_graph_attribute(graphviz => { groups => \@groups }); $g; } 1; =pod =head1 NAME L - Visualize a Perl regular expression as a graph =head1 SYNOPSIS use GraphViz2::Parse::Regexp; # no objects - quicker my $gd = GraphViz2::Parse::Regexp::to_graph('(([abcd0-9])|(foo))'); # populate a GraphViz2 object with a Graph::Directed of a regexp my $gv = GraphViz2->from_graph(GraphViz2::Parse::Regexp::graphvizify($gd)); # OO interface, using lazy-built attributes my $gvre = GraphViz2::Parse::Regexp->new(regexp => $regexp); my $gd = $gvre->as_graph; # Graph::Directed object # or supply a suitable Graph::Directed object my $gvre = GraphViz2::Parse::Regexp->new(as_graph => $gd); # then get the GraphViz2 object my $gv = $gvre->graph; # DEPRECATED ways to get $gvre with populated $gv my $gvre = GraphViz2::Parse::Regexp->new; $gvre->create(regexp => '(([abcd0-9])|(foo))'); my $gv = $gvre->graph; # or give it a pre-set-up GraphViz2 object my $gv = GraphViz2->new(...); my $gvre = GraphViz2::Parse::Regexp->new(graph => $gv); # call ->create as above # produce a visualisation my $format = shift || 'svg'; my $output_file = shift || "output.$format"; $gv->run(format => $format, output_file => $output_file); See F. =head1 DESCRIPTION Takes a Perl regular expression and converts it into a L object, or directly into a L object. =head1 FUNCTIONS This is the recommended interface. =head2 to_graph my $gd = GraphViz2::Parse::Regexp::to_graph('(([abcd0-9])|(foo))'); Given a Perl regular expression, returns a L object describing the finite state machine for it. =head2 graphvizify my $gv = GraphViz2->from_graph(GraphViz2::Parse::Regexp::graphvizify($gd)); Mutates the given graph object to add to it the C attributes visualisation "hints" that will make the L method visualise this regular expression in the most meaningful way, including labels and groupings. It is idempotent as it simply sets the C attribute of the relevant graph entities. Returns the graph object for convenience. =head1 METHODS This is a L class, but with a recommended functional interface. =head2 Constructor attributes =head3 regexp The regular expression to use. This key is optional. You need to provide it by the time you access either the L or L. =head3 as_graph The L object to use. If not given, will be lazily built on access, from the L. =head3 graph The L object to use. This allows you to configure it as desired. This key is optional. If provided, the C method will populate it. If not, it will have these defaults, lazy-built and populated from the L. my $gv = GraphViz2->new( edge => {color => 'grey'}, global => {directed => 1}, graph => {rankdir => 'TB'}, node => {color => 'blue', shape => 'oval'}, ); =head2 create(regexp => $regexp) DEPRECATED. Mutates the object to set the C attribute, then accesses the C attribute (possibly lazy-building it), then Cs its C attribute with that information, then Cs its C. Returns $self for method chaining. =head1 THANKS Many thanks are due to the people who chose to make L Open Source. And thanks to L, who wrote L, and kindly gave me co-maint of the module. =head1 AUTHOR L was written by Ron Savage Iron@savage.net.auE> in 2011. Home page: L. =head1 COPYRIGHT Australian copyright (c) 2011, Ron Savage. All Programs of mine are 'OSI Certified Open Source Software'; you can redistribute them and/or modify them under the terms of The Perl License, a copy of which is available at: http://dev.perl.org/licenses/ =cut GraphViz2-2.67/lib/GraphViz2/Parse/Yacc.pm0000644000175000017500000001405313747332774020107 0ustar osboxesosboxespackage GraphViz2::Parse::Yacc; use strict; use warnings; use warnings qw(FATAL utf8); # Fatalize encoding glitches. our $VERSION = '2.47'; use GraphViz2; use Moo; use Graph::Directed; my %EDGEATTR = (headport => 'port1'); my %GRAPHVIZ_ARGS = ( edge => {color => 'grey'}, global => {directed => 1, combine_node_and_port => 0}, graph => {rankdir => 'TB'}, node => {color => 'blue', shape => 'oval'}, ); has as_graph => ( is => 'lazy', required => 0, ); sub _build_as_graph { to_graph($_[0]->file_name) } has graph => ( is => 'lazy', #isa => 'GraphViz2', required => 0, ); sub _build_graph { GraphViz2->new(%GRAPHVIZ_ARGS)->from_graph(graphvizify($_[0]->as_graph)); } has file_name => ( is => 'rw', required => 0, ); sub read_file { open my $fh, '<:encoding(UTF-8)', $_[0] or die "$_[0]: $!"; map +((chomp, $_)[1]), <$fh>; } sub create { my ($self, %arg) = @_; $self->file_name($arg{file_name}); $self->graph->from_graph(graphvizify($self->as_graph)); return $self; } sub to_graph { my ($file_name) = @_; my $g = Graph::Directed->new; my (%edges, %labels, $rule); for my $line (read_file($file_name)) { next if ($line !~ /\w/) || ($line !~ /^\s+\d+\s+/); $line =~ s/^\s+\d+\s+//; $rule = $1 if $line =~ s/([^ ]+) : ?//; $line =~ s/\|\s+//; my $text = $line =~ /^\s*$/ ? '(empty)' : $line; @{$edges{$rule}}{split ' ', $text} = (); # only needs to exist push @{$labels{$rule}}, $text; } for my $f (keys %edges) { $g->add_edges(map [$f, $_], grep $edges{$_}, keys %{$edges{$f}}); $g->set_vertex_attribute($f, labels => $labels{$f}); } $g; } sub _quote { my $t = $_[0]; $t =~ s/\\/\\\\/g; $t; } sub graphvizify { my ($g) = @_; for my $v ($g->vertices) { $g->set_vertex_attribute($v, graphviz => { label => [$v, [ map _quote($_).'\\l', @{$g->get_vertex_attribute($v, 'labels')} ]], }); $g->set_edge_attribute(@$_, graphviz => \%EDGEATTR) for $g->edges_from($v); } $g->set_graph_attribute(graphviz => { global => $GRAPHVIZ_ARGS{global} }); $g; } 1; =head1 NAME L - Visualize a yacc grammar as a graph =head1 SYNOPSIS use GraphViz2::Parse::Yacc; # no objects - quicker my $gd = GraphViz2::Parse::Yacc::to_graph('t/calc3.output'); # populate a GraphViz2 object with a Graph::Directed of a parser my $gv = GraphViz2->from_graph(GraphViz2::Parse::Yacc::graphvizify($gd)); # OO interface, using lazy-built attributes my $gvp = GraphViz2::Parse::Yacc->new(file_name => $file_name); my $gd = $gvp->as_graph; # Graph::Directed object # or supply a suitable Graph::Directed object my $gvp = GraphViz2::Parse::Yacc->new(as_graph => $gd); # then get the GraphViz2 object my $gv = $gvp->graph; # DEPRECATED ways to get $gvp with populated $gv my $gvp = GraphViz2::Parse::Yacc->new; $gvp->create(file_name => 't/calc3.output'); my $gv = $gvp->graph; # or give it a pre-set-up GraphViz2 object my $gv = GraphViz2->new(...); my $gvp = GraphViz2::Parse::Yacc->new(graph => $gv); # call ->create as above # produce a visualisation my $format = shift || 'svg'; my $output_file = shift || "output.$format"; $gv->run(format => $format, output_file => $output_file); See F. =head1 DESCRIPTION Takes a yacc grammar and converts it into a L object, or directly into a L object. =head1 FUNCTIONS This is the recommended interface. =head2 to_graph my $gd = GraphViz2::Parse::Yacc::to_graph('t/calc3.output'); Given a yacc grammar, returns a L object describing the finite state machine for it. =head2 graphvizify my $gv = GraphViz2->from_graph(GraphViz2::Parse::Yacc::graphvizify($gd)); Mutates the given graph object to add to it the C attributes visualisation "hints" that will make the L method visualise this regular expression in the most meaningful way, including labels and groupings. It is idempotent as it simply sets the C attribute of the relevant graph entities. Returns the graph object for convenience. =head1 METHODS This is a L class, but with a recommended functional interface. =head2 Constructor attributes =head3 file_name The name of a yacc output file. See F. This key is optional. You need to provide it by the time you access either the L or L. =head3 as_graph The L object to use. If not given, will be lazily built on access, from the L. =head3 graph The L object to use. This allows you to configure it as desired. This key is optional. If provided, the C method will populate it. If not, it will have these defaults, lazy-built and populated from the L. my $gv = GraphViz2->new( edge => {color => 'grey'}, global => {directed => 1}, graph => {rankdir => 'TB'}, node => {color => 'blue', shape => 'oval'}, ); =head2 create(file_name => $file_name) DEPRECATED. Mutates the object to set the C attribute, then accesses the C attribute (possibly lazy-building it), then Cs its C attribute with that information, then Cs its C. Returns $self for method chaining. =head1 THANKS Many thanks are due to the people who chose to make L Open Source. And thanks to L, who wrote L, and kindly gave me co-maint of the module. =head1 AUTHOR L was written by Ron Savage Iron@savage.net.auE> in 2011. Home page: L. =head1 COPYRIGHT Australian copyright (c) 2011, Ron Savage. All Programs of mine are 'OSI Certified Open Source Software'; you can redistribute them and/or modify them under the terms of The Perl License, a copy of which is available at: http://dev.perl.org/licenses/ =cut GraphViz2-2.67/lib/GraphViz2/Parse/STT.pm0000644000175000017500000002346213752040061017663 0ustar osboxesosboxespackage GraphViz2::Parse::STT; use strict; use warnings; use warnings qw(FATAL utf8); # Fatalize encoding glitches. our $VERSION = '2.47'; use GraphViz2; use Moo; use Text::ParseWords; use Graph::Directed; my %GRAPHVIZ_ARGS = ( edge => {color => 'grey'}, global => {directed => 1, combine_node_and_port => 0}, graph => {rankdir => 'TB'}, node => {color => 'blue', shape => 'oval'}, ); has as_graph => ( is => 'lazy', required => 0, ); sub _build_as_graph { to_graph($_[0]->stt) } has graph => ( is => 'lazy', #isa => 'GraphViz2', required => 0, ); sub _build_graph { GraphViz2->new(%GRAPHVIZ_ARGS)->from_graph(graphvizify($_[0]->as_graph, $_[0]->mode)); } has stt => ( is => 'rw', required => 0, ); has mode => ( is => 'rw', required => 0, ); sub _quote { my $t = $_[0]; $t =~ s/\\/\\\\/g; $t; } sub create { my ($self, %arg) = @_; $self->stt($arg{stt}); $self->mode($arg{mode}) if exists $arg{mode}; $self->graph->from_graph(graphvizify($self->as_graph, $self->mode)); return $self; } sub to_graph { my ($stt) = @_; my $g = Graph::Directed->new; for my $line (split /\n/, $stt) { $line =~ s/^\s*\[?//; $line =~ s/\s*(],?)?$//; my ($f, $re, $t) = quotewords('\s*,\s*', 0, $line); $g->set_edge_attributes($f, $t, { type => 'transition', re => $re }); $g->set_vertex_attribute($_, type => 'state') for $f, $t; } return $g; } sub graphvizify { my ($g, $mode) = @_; $mode = GraphViz2::_dor($mode, 're_structs'); if ($mode eq 're_nodes') { my %state2re_s; for my $v (grep $g->get_vertex_attribute($_, 'type') eq 'state', $g->vertices) { for my $e (grep $g->get_edge_attribute(@$_, 'type') eq 'transition', $g->edges_from($v)) { my ($re, $t) = ($g->get_edge_attribute(@$e, 're'), $e->[1]); $state2re_s{$t}{ my $re_node_id = "$re:$t" } = 1; $g->set_vertex_attributes($re_node_id, { type => 're', graphviz => { label => "/" . _quote($re) . "/", color => 'black', shape => 'box', }, }); $g->set_edge_attribute($v, $re_node_id, type => 'state_re'); $g->set_edge_attribute($re_node_id, $t, type => 're_state'); $g->delete_edge(@$e); } } my @groups = map +{ attributes => { name => "cluster_$_", subgraph => { rank => "TB" } }, nodes => [ $_, sort keys %{$state2re_s{$_}} ], }, sort keys %state2re_s; $g->set_graph_attribute( graphviz => { global => $GRAPHVIZ_ARGS{global}, graph => { clusterrank => 'local', compound => 1 }, groups => \@groups, }, ); } elsif ($mode eq 're_structs') { for my $v (grep $g->get_vertex_attribute($_, 'type') eq 'state', $g->vertices) { my @ins = grep $_->[1] eq 'transition', map [ $_->[0], @{ $g->get_edge_attributes(@$_) }{qw(type re)} ], $g->edges_to($v); $g->set_edge_attribute( $_->[0], $v, graphviz => { tailport => 'state', headport => [ $_->[2], 'w' ] }, ) for @ins; my %unique_re = map +($_->[2] => undef), @ins; $g->set_vertex_attribute($v, graphviz => { shape => 'record', label => [ [ map +{ text => "/"._quote($_)."/", port => $_ }, sort keys %unique_re ], { text => _quote($v), port => 'state' }, ], }); } $g->set_graph_attribute(graphviz => { global => $GRAPHVIZ_ARGS{global}, graph => { concentrate => 'true' } }); } elsif ($mode eq 're_edges') { for my $v (grep $g->get_vertex_attribute($_, 'type') eq 'state', $g->vertices) { for my $e (grep $g->get_edge_attribute(@$_, 'type') eq 'transition', $g->edges_from($v)) { $g->set_edge_attribute( @$e, graphviz => { label => "/" . _quote($g->get_edge_attribute(@$e, 're')) . "/" }, ); } } $g->set_graph_attribute(graphviz => { global => $GRAPHVIZ_ARGS{global} }); } else { die "Unknown STT visualisation mode '$mode'"; } $g; } 1; =pod =head1 NAME L - Visualize a Set::FA::Element state transition table as a graph =head1 SYNOPSIS use GraphViz2::Parse::STT; use File::Slurp; # For read_file(). my $stt = read_file('sample.stt.1.dat'); # no objects - quicker my $gd = GraphViz2::Parse::STT::to_graph($stt); # populate a GraphViz2 object with a Graph::Directed of a parser my $gv = GraphViz2->from_graph(GraphViz2::Parse::STT::graphvizify($gd)); # visualise with another mode $gd = GraphViz2::Parse::STT::graphvizify($gd, 're_nodes'); # or 're_edges' # OO interface, using lazy-built attributes my $gvp = GraphViz2::Parse::STT->new(stt => $stt, mode => 're_structs'); my $gd = $gvp->as_graph; # Graph::Directed object # or supply a suitable Graph::Directed object my $gvp = GraphViz2::Parse::STT->new(as_graph => $gd); # then get the GraphViz2 object my $gv = $gvp->graph; # DEPRECATED ways to get $gvp with populated $gv my $gvp = GraphViz2::Parse::STT->new; $gvp->create(stt => $stt); my $gv = $gvp->graph; # or give it a pre-set-up GraphViz2 object my $gv = GraphViz2->new(...); my $gvp = GraphViz2::Parse::STT->new(graph => $gv); # call ->create as above # produce a visualisation my $format = shift || 'svg'; my $output_file = shift || "output.$format"; $gv->run(format => $format, output_file => $output_file); See F. Note: F is output from L V 0.70, and can be used instead of F in the above code. =head1 DESCRIPTION Takes a L-style state transition table and converts it into a L object, or directly into a L object. =head1 FUNCTIONS This is the recommended interface. =head2 to_graph my $gd = GraphViz2::Parse::STT::to_graph($stt); Given STT text, returns a L object describing the finite state machine for it. The nodes are all states, and the edges are regular expressions that cause a transition to another state. =head2 graphvizify my $gv = GraphViz2->from_graph(GraphViz2::Parse::STT::graphvizify($gd, $mode)); Mutates the given graph object to add to it the C attributes visualisation "hints" that will make the L method visualise this regular expression in the most meaningful way, including labels and groupings. It is idempotent, but in C mode, it deletes the transition edges and replaces them with additional nodes and edges. If a second argument is given, it will be the visualisation "mode". The default is C. Also available is C, and C where the regular expressions are simply added as labels to the state-transition edges. Returns the graph object for convenience. =head1 METHODS This is a L class, but with a recommended functional interface. =head2 Constructor attributes =head3 stt Text with a state transition table, with a Perl-ish list of arrayrefs, each with 3 elements. That is, it is the I of the arrayref 'transitions', which is one of the keys in the parameter list to L's new(). A quick summary of each element of this list, where each element is an arrayref with 3 elements: =over 4 =item o [0] A state name =item o [1] A regexp =item o [2] Another state name (which may be the same as the first) =back The DFA in L tests the 'current' state against the state name ([0]), and for each state name which matches, tests the regexp ([1]) against the next character in the input stream. The first regexp to match causes the DFA to transition to the state named in the 3rd element of the arrayref ([2]). See F for an example. This key is optional. You need to provide it by the time you access either the L or L. =head3 as_graph The L object to use. If not given, will be lazily built on access, from the L. =head3 graph The L object to use. This allows you to configure it as desired. This key is optional. If provided, the C method will populate it. If not, it will have these defaults, lazy-built and populated from the L. my $gv = GraphViz2->new( edge => {color => 'grey'}, global => {directed => 1}, graph => {rankdir => 'TB'}, node => {color => 'blue', shape => 'oval'}, ); =head3 mode The mode to be used by L. =head2 create(regexp => $regexp, mode => $mode) DEPRECATED. Mutates the object to set the C attribute, then accesses the C attribute (possibly lazy-building it), then Cs its C attribute with that information, then Cs its C. Returns $self for method chaining. =head1 THANKS Many thanks are due to the people who chose to make L Open Source. And thanks to L, who wrote L, and kindly gave me co-maint of the module. =head1 AUTHOR L was written by Ron Savage Iron@savage.net.auE> in 2011. Home page: L. =head1 COPYRIGHT Australian copyright (c) 2011, Ron Savage. All Programs of mine are 'OSI Certified Open Source Software'; you can redistribute them and/or modify them under the terms of The Perl License, a copy of which is available at: http://dev.perl.org/licenses/ =cut GraphViz2-2.67/lib/GraphViz2.pm0000644000175000017500000016611214266245275016156 0ustar osboxesosboxespackage GraphViz2; use strict; use warnings; use warnings qw(FATAL utf8); # Fatalize encoding glitches. use Data::Section::Simple 'get_data_section'; use File::Temp; # For newdir(). use File::Which; # For which(). use Moo; use IPC::Run3; # For run3(). use Types::Standard qw/Any ArrayRef HasMethods HashRef Int Str/; our $VERSION = '2.67'; my $DATA_SECTION = get_data_section; # load once my $DEFAULT_COMBINE = 1; # default for combine_node_and_port my %CONTEXT_QUOTING = ( label => '\\{\\}\\|<>\\s"', label_legacy => '"', ); my %PORT_QUOTING = map +($_ => sprintf "%%%02x", ord $_), qw(% \\ : " { } | < >); my $PORT_QUOTE_CHARS = join '', '[', (map quotemeta, sort keys %PORT_QUOTING), ']'; has command => ( default => sub{[]}, is => 'ro', isa => ArrayRef, required => 0, ); has dot_input => ( is => 'lazy', isa => Str, required => 0, ); sub _build_dot_input { my ($self) = @_; join('', @{ $self->command }) . "}\n"; } has dot_output => ( default => sub{return ''}, is => 'rw', isa => Str, required => 0, ); has edge => ( default => sub{return {} }, is => 'rw', isa => HashRef, required => 0, ); has edge_hash => ( default => sub{return {} }, is => 'rw', isa => HashRef, required => 0, ); has global => ( default => sub{return {} }, is => 'rw', isa => HashRef, required => 0, ); has graph => ( default => sub{return {} }, is => 'rw', isa => HashRef, required => 0, ); has im_meta => ( default => sub{return {} }, is => 'rw', isa => HashRef, required => 0, ); has logger => ( is => 'rw', isa => HasMethods[qw(debug error)], required => 0, ); has node => ( default => sub{return {} }, is => 'rw', isa => HashRef, required => 0, ); has node_hash => ( default => sub{return {} }, is => 'rw', isa => HashRef, required => 0, ); has scope => ( default => sub{[]}, is => 'ro', isa => ArrayRef, required => 0, ); has subgraph => ( default => sub{return {} }, is => 'rw', isa => HashRef, required => 0, ); has verbose => ( default => sub{return 0}, is => 'rw', isa => Int, required => 0, ); my $VALID_ATTRIBUTES = _build_valid_attributes(); sub valid_attributes { $VALID_ATTRIBUTES } sub _build_valid_attributes { my %data = map +($_ => [ grep !/^$/ && !/^(?:\s*)#/, split /\n/, $$DATA_SECTION{$_} ]), keys %$DATA_SECTION; # Reorder them so the major key is the context and the minor key is the attribute. # I.e. $attribute{global}{directed} => undef means directed is valid in a global context. my %attribute; # Common attributes are a special case, since one attribute can be valid is several contexts... # Format: attribute_name => context_1, context_2. for my $a (@{ delete $data{common_attribute} }) { my ($attr, $contexts) = split /\s*=>\s*/, $a; $attribute{$_}{$attr} = undef for split /\s*,\s*/, $contexts; } @{$attribute{$_}}{ @{$data{$_}} } = () for keys %data; @{$attribute{subgraph}}{ keys %{ delete $attribute{cluster} } } = (); \%attribute; } has valid_output_format => ( is => 'lazy', isa => HashRef, required => 0, ); sub _build_valid_output_format { my ($self) = @_; run3 ['dot', "-T?"], undef, \my $stdout, \my $stderr, ; $stderr =~ s/.*one of:\s+//; +{ map +($_ => undef), split /\s+/, $stderr }; } sub _dor { return $_[0] if defined $_[0]; $_[1] } # // sub BUILD { my($self) = @_; my($globals) = $self -> global; my($global) = { combine_node_and_port => _dor($$globals{combine_node_and_port}, $DEFAULT_COMBINE), directed => $$globals{directed} ? 'digraph' : 'graph', driver => $$globals{driver} || scalar(which('dot')), format => $$globals{format} || 'svg', im_format => $$globals{im_format} || 'cmapx', label => $$globals{directed} ? '->' : '--', name => _dor($$globals{name}, 'Perl'), record_shape => ($$globals{record_shape} && $$globals{record_shape} =~ /^(M?record)$/) ? $1 : 'Mrecord', strict => _dor($$globals{strict}, 0), timeout => _dor($$globals{timeout}, 10), }; my($im_metas) = $self -> im_meta; my($im_meta) = { URL => $$im_metas{URL} || '', }; $self -> global($global); $self -> im_meta($im_meta); $self->validate_params('global', $self->global); $self->validate_params('graph', $self->graph); $self->validate_params('im_meta', $self->im_meta); $self->validate_params('node', $self->node); $self->validate_params('edge', $self->edge); $self->validate_params('subgraph', $self->subgraph); push @{ $self->scope }, { edge => $self -> edge, graph => $self -> graph, node => $self -> node, subgraph => $self -> subgraph, }; my(%global) = %{$self -> global}; my(%im_meta) = %{$self -> im_meta}; $self -> log(debug => "Default global: $_ => $global{$_}") for sort keys %global; $self -> log(debug => "Default im_meta: $_ => $im_meta{$_}") for grep{$im_meta{$_} } sort keys %im_meta; my($command) = (${$self -> global}{strict} ? 'strict ' : '') . (${$self -> global}{directed} . ' ') . ${$self -> global}{name} . " {\n"; for my $key (grep{$im_meta{$_} } sort keys %im_meta) { $command .= _indent(qq|$key = "$im_meta{$key}"; \n|, $self->scope); } push @{ $self->command }, $command; $self -> default_graph; $self -> default_node; $self -> default_edge; } # End of BUILD. sub _edge_name_port { my ($self, $name) = @_; $name = _dor($name, ''); # Remove :port:compass, if any, from name. # But beware Perl-style node names like 'A::Class'. my @field = split /(:(?!:))/, $name; $field[0] = $name if !@field; # Restore Perl module names: # o A: & B to A::B. # o A: & B: & C to A::B::C. splice @field, 0, 3, "$field[0]:$field[2]" while $field[0] =~ /:$/; # Restore: # o : & port to :port. # o : & port & : & compass to :port:compass. $name = shift @field; ($name, join '', @field); } sub add_edge { my($self, %arg) = @_; my $from = _dor(delete $arg{from}, ''); my $to = _dor(delete $arg{to}, ''); my $label = _dor($arg{label}, ''); $label =~ s/^\s*(<)\n?/$1/; $label =~ s/\n?(>)\s*$/$1/; $arg{label} = $label if (defined $arg{label}); $self->validate_params('edge', \%arg); my @nodes; for my $tuple ([ $from, 'tailport' ], [ $to, 'headport' ]) { my ($name, $argname) = @$tuple; my $port = ''; if ($self->global->{combine_node_and_port}) { ($name, $port) = $self->_edge_name_port($name); } elsif (defined(my $value = delete $arg{$argname})) { $port = join ':', '', map qq{"$_"}, map escape_port($_), ref $value ? @$value : $value; } push @nodes, [ $name, $port ]; next if (my $nh = $self->node_hash)->{$name}; $self->log(debug => "Implicitly added node: $name"); $nh->{$name}{attributes} = {}; } # Add this edge to the hashref of all edges. push @{$self->edge_hash->{$nodes[0][0]}{$nodes[1][0]}}, { attributes => \%arg, from_port => $nodes[0][1], to_port => $nodes[1][1], }; # Add this edge to the DOT output string. my $dot = $self->stringify_attributes(join(" ${$self->global}{label} ", map qq|"$_->[0]"$_->[1]|, @nodes), \%arg); push @{ $self->command }, _indent($dot, $self->scope); $self -> log(debug => "Added edge: $dot"); return $self; } # End of add_edge. sub _indent { my ($text, $scope) = @_; return '' if $text !~ /\S/; (' ' x @$scope) . $text; } sub _compile_record { my ($port_count, $item, $add_braces, $quote_more) = @_; my $text; if (ref $item eq 'ARRAY') { my @parts; for my $l (@$item) { ($port_count, my $t) = _compile_record($port_count, $l, 1, $quote_more); push @parts, $t; } $text = join '|', @parts; $text = "{$text}" if $add_braces; } elsif (ref $item eq 'HASH') { my $port = $item->{port} || 0; $text = escape_some_chars(_dor($item->{text}, ''), $CONTEXT_QUOTING{$quote_more ? 'label' : 'label_legacy'}); if ($port) { $port =~ s/^\s*?\s*$//; $port = escape_port($port); $text = "<$port> $text"; } } else { $text = " " . escape_some_chars($item, $CONTEXT_QUOTING{$quote_more ? 'label' : 'label_legacy'}); } ($port_count, $text); } sub add_node { my ($self, %arg) = @_; my $name = _dor(delete $arg{name}, ''); $self->validate_params('node', \%arg); my $node = $self->node_hash; %arg = (%{$$node{$name}{attributes} || {}}, %arg); $$node{$name}{attributes} = \%arg; my $label = _dor($arg{label}, ''); $label =~ s/^\s*(<)\n?/$1/; $label =~ s/\n?(>)\s*$/$1/; $arg{label} = $label if defined $arg{label}; # Handle ports. if (ref $label eq 'ARRAY') { (undef, $arg{label}) = _compile_record(0, $label, 0, !$self->global->{combine_node_and_port}); $arg{shape} ||= $self->global->{record_shape}; } elsif ($arg{shape} && ( ($arg{shape} =~ /M?record/) || ( ($arg{shape} =~ /(?:none|plaintext)/) && ($label =~ /^stringify_attributes(qq|"$name"|, \%arg); push @{ $self->command }, _indent($dot, $self->scope); $self->log(debug => "Added node: $dot"); return $self; } sub default_edge { my($self, %arg) = @_; $self->validate_params('edge', \%arg); my $scope = $self->scope->[-1]; $$scope{edge} = {%{$$scope{edge} || {}}, %arg}; push @{ $self->command }, _indent($self->stringify_attributes('edge', $$scope{edge}), $self->scope); $self -> log(debug => 'Default edge: ' . join(', ', map{"$_ => $$scope{edge}{$_}"} sort keys %{$$scope{edge} }) ); return $self; } # End of default_edge. # ----------------------------------------------- sub default_graph { my($self, %arg) = @_; $self->validate_params('graph', \%arg); my $scope = $self->scope->[-1]; $$scope{graph} = {%{$$scope{graph} || {}}, %arg}; push @{ $self->command }, _indent($self->stringify_attributes('graph', $$scope{graph}), $self->scope); $self -> log(debug => 'Default graph: ' . join(', ', map{"$_ => $$scope{graph}{$_}"} sort keys %{$$scope{graph} }) ); return $self; } # End of default_graph. # ----------------------------------------------- sub default_node { my($self, %arg) = @_; $self->validate_params('node', \%arg); my $scope = $self->scope->[-1]; $$scope{node} = {%{$$scope{node} || {}}, %arg}; push @{ $self->command }, _indent($self->stringify_attributes('node', $$scope{node}), $self->scope); $self -> log(debug => 'Default node: ' . join(', ', map{"$_ => $$scope{node}{$_}"} sort keys %{$$scope{node} }) ); return $self; } # End of default_node. # ----------------------------------------------- sub default_subgraph { my($self, %arg) = @_; $self->validate_params('subgraph', \%arg); my $scope = $self->scope->[-1]; $$scope{subgraph} = {%{$$scope{subgraph} || {}}, %arg}; push @{ $self->command }, _indent($self->stringify_attributes('subgraph', $$scope{subgraph}), $self->scope); $self -> log(debug => 'Default subgraph: ' . join(', ', map{"$_ => $$scope{subgraph}{$_}"} sort keys %{$$scope{subgraph} }) ); return $self; } # End of default_subgraph. sub escape_port { my ($s) = @_; $s =~ s/($PORT_QUOTE_CHARS)/$PORT_QUOTING{$1}/g; $s; } sub escape_some_chars { my ($s, $quote_chars) = @_; return $s if substr($s, 0, 1) eq '<'; # HTML label $s =~ s/(\\.)|([$quote_chars])/ defined($1) ? $1 : '\\' . $2 /ge; return $s; } sub log { my($self, $level, $message) = @_; $level ||= 'debug'; $message ||= ''; if ($self->logger) { $self->logger->$level($message); } else { die $message if $level eq 'error'; print "$level: $message\n" if $self->verbose; } return $self; } # End of log. # ----------------------------------------------- sub pop_subgraph { my($self) = @_; pop @{ $self->scope }; push @{ $self->command }, _indent("}\n", $self->scope); return $self; } # End of pop_subgraph. # ----------------------------------------------- sub push_subgraph { my($self, %arg) = @_; my($name) = delete $arg{name}; $name = defined($name) && length($name) ? qq|"$name"| : ''; $self->validate_params('graph', $arg{graph}); $self->validate_params('node', $arg{node}); $self->validate_params('edge', $arg{edge}); $self->validate_params('subgraph', $arg{subgraph}); $arg{subgraph} = { %{ $self->subgraph||{} }, %{$arg{subgraph}||{}} }; push @{ $self->command }, "\n" . _indent(join(' ', grep length, "subgraph", $name, "{\n"), $self->scope); push @{ $self->scope }, \%arg; $self -> default_graph; $self -> default_node; $self -> default_edge; $self -> default_subgraph; return $self; } # End of push_subgraph. # ----------------------------------------------- sub run { my($self, %arg) = @_; my($driver) = delete $arg{driver} || ${$self -> global}{driver}; my($format) = delete $arg{format} || ${$self -> global}{format}; my($im_format) = delete $arg{im_format} || ${$self -> global}{im_format}; my($timeout) = delete $arg{timeout} || ${$self -> global}{timeout}; my($output_file) = delete $arg{output_file} || ''; my($im_output_file) = delete $arg{im_output_file} || ''; for ($format, $im_format) { my $prefix = $_; $prefix =~ s/:.+$//; # In case of 'png:gd', etc. $self->log(error => "Error: '$prefix' is not a valid output format") if !exists $self->valid_output_format->{$prefix}; } $self -> log(debug => $self -> dot_input); # Warning: Do not use $im_format in this 'if', because it has a default value. if ($im_output_file) { return $self -> run_map($driver, $output_file, $format, $timeout, $im_output_file, $im_format); } else { return $self -> run_mapless($driver, $output_file, $format, $timeout); } } # End of run. # ----------------------------------------------- sub run_map { my($self, $driver, $output_file, $format, $timeout, $im_output_file, $im_format) = @_; $self -> log(debug => "Driver: $driver. Output file: $output_file. Format: $format. IM output file: $im_output_file. IM format: $im_format. Timeout: $timeout second(s)"); # The EXLOCK option is for BSD-based systems. my($temp_dir) = File::Temp -> newdir('temp.XXXX', CLEANUP => 1, EXLOCK => 0, TMPDIR => 1); my($temp_file) = File::Spec -> catfile($temp_dir, 'temp.gv'); open(my $fh, '> :raw', $temp_file) || die "Can't open(> $temp_file): $!"; print $fh $self -> dot_input; close $fh; my(@args) = ("-T$im_format", "-o$im_output_file", "-T$format", "-o$output_file", $temp_file); system($driver, @args); return $self; } # End of run_map. # ----------------------------------------------- sub run_mapless { my($self, $driver, $output_file, $format, $timeout) = @_; $self -> log(debug => "Driver: $driver. Output file: $output_file. Format: $format. Timeout: $timeout second(s)"); # Usage of utf8 here relies on ISO-8859-1 matching Unicode for low chars. # It saves me the effort of determining if the input contains Unicode. run3 [$driver, "-T$format"], \$self -> dot_input, \my $stdout, \my $stderr, { binmode_stdin => ':utf8', binmode_stdout => ':raw', binmode_stderr => ':raw', }; die $stderr if ($stderr); $self -> dot_output($stdout); if ($output_file) { open(my $fh, '> :raw', $output_file) || die "Can't open(> $output_file): $!"; print $fh $stdout; close $fh; $self -> log(debug => "Wrote $output_file. Size: " . length($stdout) . ' bytes'); } return $self; } # End of run_mapless. sub stringify_attributes { my($self, $context, $option) = @_; # Add double-quotes around anything (e.g. labels) which does not look like HTML. my @pairs; for my $key (sort keys %$option) { my $text = _dor($$option{$key}, ''); $text =~ s/^\s+(<)/$1/; $text =~ s/(>)\s+$/$1/; $text = qq|"$text"| if $text !~ /^<.+>$/s; push @pairs, qq|$key=$text|; } return join(' ', @pairs) . "\n" if $context eq 'subgraph'; return join(' ', $context, '[', @pairs, ']') . "\n" if @pairs; $context =~ /^(?:edge|graph|node)/ ? '' : "$context\n"; } sub validate_params { my($self, $context, $attributes) = @_; my $valid = $VALID_ATTRIBUTES->{$context}; my @invalid = grep !exists $valid->{$_}, keys %$attributes; $self->log(error => "Error: '$_' is not a valid attribute in the '$context' context") for sort @invalid; return $self; } # End of validate_params. sub from_graph { my ($self, $g) = @_; die "from_graph: '$g' not a Graph" if !$g->isa('Graph'); my %g_attrs = %{ $g->get_graph_attribute('graphviz') || {} }; my $global = { directed => $g->is_directed, %{delete $g_attrs{global}||{}} }; my $groups = delete $g_attrs{groups} || []; if (ref $self) { for (sort keys %g_attrs) { my $method = "default_$_"; $self->$method(%{ $g_attrs{$_} }); } } else { $self = $self->new(global => $global, %g_attrs); } for my $group (@$groups) { $self->push_subgraph(%{ $group->{attributes} || {} }); $self->add_node(name => $_) for @{ $group->{nodes} || [] }; $self->pop_subgraph; } my ($is_multiv, $is_multie) = map $g->$_, qw(multivertexed multiedged); my ($v_attr, $e_attr) = qw(get_vertex_attribute get_edge_attribute); $v_attr .= '_by_id' if $is_multiv; $e_attr .= '_by_id' if $is_multie; my %first2edges; for my $e (sort {$a->[0] cmp $b->[0] || $a->[1] cmp $b->[1]} $g->unique_edges) { my @edges = $is_multie ? map $g->$e_attr(@$e, $_, 'graphviz') || {}, sort $g->get_multiedge_ids(@$e) : $g->$e_attr(@$e, 'graphviz')||{}; push @{ $first2edges{$e->[0]} }, map [ from => $e->[0], to => $e->[1], %$_ ], @edges; } for my $v (sort $g->unique_vertices) { my @vargs = $v; if ($is_multiv) { my ($found_id) = grep $g->has_vertex_attribute_by_id($v, $_, 'graphviz'), sort $g->get_multivertex_ids($v); @vargs = defined $found_id ? (@vargs, $found_id) : (); } my $attrs = @vargs ? $g->$v_attr(@vargs, 'graphviz') || {} : {}; $self->add_node(name => $v, %$attrs) if keys %$attrs or $g->is_isolated_vertex($v); $self->add_edge(@$_) for @{ $first2edges{$v} }; } $self; } # ----------------------------------------------- 1; =pod =head1 NAME GraphViz2 - A wrapper for AT&T's Graphviz =head1 Synopsis =head2 Sample output See L. =head2 Perl code =head3 Typical Usage use strict; use warnings; use File::Spec; use GraphViz2; use Log::Handler; my $logger = Log::Handler->new; $logger->add(screen => { maxlevel => 'debug', message_layout => '%m', minlevel => 'error' }); my $graph = GraphViz2->new( edge => {color => 'grey'}, global => {directed => 1}, graph => {label => 'Adult', rankdir => 'TB'}, logger => $logger, node => {shape => 'oval'}, ); $graph->add_node(name => 'Carnegie', shape => 'circle'); $graph->add_node(name => 'Murrumbeena', shape => 'box', color => 'green'); $graph->add_node(name => 'Oakleigh', color => 'blue'); $graph->add_edge(from => 'Murrumbeena', to => 'Carnegie', arrowsize => 2); $graph->add_edge(from => 'Murrumbeena', to => 'Oakleigh', color => 'brown'); $graph->push_subgraph( name => 'cluster_1', graph => {label => 'Child'}, node => {color => 'magenta', shape => 'diamond'}, ); $graph->add_node(name => 'Chadstone', shape => 'hexagon'); $graph->add_node(name => 'Waverley', color => 'orange'); $graph->add_edge(from => 'Chadstone', to => 'Waverley'); $graph->pop_subgraph; $graph->default_node(color => 'cyan'); $graph->add_node(name => 'Malvern'); $graph->add_node(name => 'Prahran', shape => 'trapezium'); $graph->add_edge(from => 'Malvern', to => 'Prahran'); $graph->add_edge(from => 'Malvern', to => 'Murrumbeena'); my $format = shift || 'svg'; my $output_file = shift || File::Spec->catfile('html', "sub.graph.$format"); $graph->run(format => $format, output_file => $output_file); =head1 Description =head2 Overview This module provides a Perl interface to the amazing L, an open source graph visualization tool from AT&T. It is called GraphViz2 so that pre-existing code using (the Perl module) GraphViz continues to work. To avoid confusion, when I use L (note the capital V), I'm referring to this Perl module, and when I use L (lower-case v) I'm referring to the underlying tool (which is in fact a set of programs). Version 1.00 of L is a complete re-write, by Ron Savage, of GraphViz V 2, which was written by Leon Brocard. The point of the re-write is to provide access to all the latest options available to users of L. GraphViz2 V 1 is not backwards compatible with GraphViz V 2, despite the considerable similarity. It was not possible to maintain compatibility while extending support to all the latest features of L. To ensure L is a light-weight module, L has been used to provide getters and setters, rather than L. As of V 2.43, C supports image maps, both client and server side. See L below. =head2 What is a Graph? An undirected graph is a collection of nodes optionally linked together with edges. A directed graph is the same, except that the edges have a direction, normally indicated by an arrow head. A quick inspection of L's L will show better than words just how good L is, and will reinforce the point that humans are very visual creatures. =head1 Installation Of course you need to install AT&T's Graphviz before using this module. See L. =head1 Constructor and Initialization =head2 Calling new() C is called as C<< my($obj) = GraphViz2 -> new(k1 => v1, k2 => v2, ...) >>. It returns a new object of type C. Key-value pairs accepted in the parameter list: =head3 edge => $hashref The I key points to a hashref which is used to set default attributes for edges. Hence, allowable keys and values within that hashref are anything supported by L. The default is {}. This key is optional. =head3 global => $hashref The I key points to a hashref which is used to set attributes for the output stream. This key is optional. Valid keys within this hashref are: =head4 combine_node_and_port New in 2.58. It defaults to true, but in due course (currently planned May 2021) it will default to false. When true, C and C will escape only some characters in the label and names, and in particular the "from" and "to" parameters on edges will combine the node name and port in one string, with a C<:> in the middle (except for special treatment of double-colons). When the option is false, any name may be given to nodes, and edges can be created between them. To specify ports, give the additional parameter of C or C. To specify a compass point in addition, give array-refs with two values for these parameters. Also, C's treatment of labels is more DWIM, with C<{> etc being transparently quoted. =head4 directed => $Boolean This option affects the content of the output stream. directed => 1 outputs 'digraph name {...}', while directed => 0 outputs 'graph name {...}'. At the Perl level, directed graphs have edges with arrow heads, such as '->', while undirected graphs have unadorned edges, such as '--'. The default is 0. This key is optional. =head4 driver => $program_name This option specifies which external program to run to process the output stream. The default is to use L's which() method to find the 'dot' program. This key is optional. =head4 format => $string This option specifies what type of output file to create. The default is 'svg'. Output formats of the form 'png:gd' etc are also supported, but only the component before the first ':' is validated by L. This key is optional. =head4 label => $string This option specifies what an edge looks like: '->' for directed graphs and '--' for undirected graphs. You wouldn't normally need to use this option. The default is '->' if directed is 1, and '--' if directed is 0. This key is optional. =head4 name => $string This option affects the content of the output stream. name => 'G666' outputs 'digraph G666 {...}'. The default is 'Perl' :-). This key is optional. =head4 record_shape => /^(?:M?record)$/ This option affects the shape of records. The value must be 'Mrecord' or 'record'. Mrecords have nice, rounded corners, whereas plain old records have square corners. The default is 'Mrecord'. See L for details. =head4 strict => $Boolean This option affects the content of the output stream. strict => 1 outputs 'strict digraph name {...}', while strict => 0 outputs 'digraph name {...}'. The default is 0. This key is optional. =head4 timeout => $integer This option specifies how long to wait for the external program before exiting with an error. The default is 10 (seconds). This key is optional. =head3 graph => $hashref The I key points to a hashref which is used to set default attributes for graphs. Hence, allowable keys and values within that hashref are anything supported by L. The default is {}. This key is optional. =head3 logger => $logger_object Provides a logger object so $logger_object -> $level($message) can be called at certain times. Any object with C and C methods will do, since these are the only levels emitted by this module. One option is a L object. Retrieve and update the value with the logger() method. By default (i.e. without a logger object), L prints warning and debug messages to STDOUT, and dies upon errors. However, by supplying a log object, you can capture these events. Not only that, you can change the behaviour of your log object at any time, by calling L. See also the verbose option, which can interact with the logger option. This key is optional. =head3 node => $hashref The I key points to a hashref which is used to set default attributes for nodes. Hence, allowable keys and values within that hashref are anything supported by L. The default is {}. This key is optional. =head3 subgraph => $hashref The I key points to a hashref which is used to set attributes for all subgraphs, unless overridden for specific subgraphs in a call of the form push_subgraph(subgraph => {$attribute => $string}). Valid keys within this hashref are: =over 4 =item * rank => $string This option affects the content of all subgraphs, unless overridden later. A typical usage would be new(subgraph => {rank => 'same'}) so that all nodes mentioned within each subgraph are constrained to be horizontally aligned. See scripts/rank.sub.graph.1.pl for sample code. Possible values for $string are: max, min, same, sink and source. See the L for details. =back The default is {}. This key is optional. =head3 verbose => $Boolean Provides a way to control the amount of output when a logger is not specified. Setting verbose to 0 means print nothing. Setting verbose to 1 means print the log level and the message to STDOUT, when a logger is not specified. Retrieve and update the value with the verbose() method. The default is 0. See also the logger option, which can interact with the verbose option. This key is optional. =head2 Validating Parameters The secondary keys (under the primary keys 'edge|graph|node') are checked against lists of valid attributes (stored at the end of this module, after the __DATA__ token, and made available using L). This mechanism has the effect of hard-coding L options in the source code of L. Nevertheless, the implementation of these lists is handled differently from the way it was done in V 2. V 2 ships with a set of scripts, scripts/extract.*.pl, which retrieve pages from the L web site and extract the current lists of valid attributes. These are then copied manually into the source code of L, meaning any time those lists change on the L web site, it's a trivial matter to update the lists stored within this module. See L. =head2 Alternate constructor and object method =head3 from_graph my $gv = GraphViz2->from_graph($g); # alternatively my $gv = GraphViz2->new; $gv->from_graph($g); # for handy debugging of arbitrary graphs: GraphViz2->from_graph($g)->run(format => 'svg', output_file => 'output.svg'); Takes a L object. This module will figure out various defaults from it, including whether it is directed or not. Will also use any node-, edge-, and graph-level attributes named C as a hash-ref for setting attributes on the corresponding entities in the constructed GraphViz2 object. These will override the figured-out defaults referred to above. For a C graph, will only create one node per vertex, but will search all the multi-IDs for a C attribute, taking the first one it finds (sorted alphabetically). For a C graph, will create one edge per multi-edge. Will only set the C attribute if called as a constructor. This will be dropped from any passed-in graph-level C attribute when called as an object method. A special graph-level attribute (under C) called C will be given further special meaning: it is an array-ref of hash-refs. Those will have keys, used to create subgraphs: =over =item * attributes Hash-ref of arguments to supply to C for this subgraph. =item * nodes Array-ref of node names to put in this subgraph. =back Example: $g->set_graph_attribute(graphviz => { groups => [ {nodes => [1, 2], attributes => {subgraph=>{rank => 'same'}}}, ], # other graph-level attributes... }); =head1 Attribute Scope =head2 Graph Scope The graphical elements graph, node and edge, have attributes. Attributes can be set when calling new(). Within new(), the defaults are graph => {}, node => {}, and edge => {}. You override these with code such as new(edge => {color => 'red'}). These attributes are pushed onto a scope stack during new()'s processing of its parameters, and they apply thereafter until changed. They are the 'current' attributes. They live at scope level 0 (zero). You change the 'current' attributes by calling any of the methods default_edge(%hash), default_graph(%hash) and default_node(%hash). See scripts/trivial.pl (L) for an example. =head2 Subgraph Scope When you wish to create a subgraph, you call push_subgraph(%hash). The word push emphasises that you are moving into a new scope, and that the default attributes for the new scope are pushed onto the scope stack. This module, as with L, defaults to using inheritance of attributes. That means the parent's 'current' attributes are combined with the parameters to push_subgraph(%hash) to generate a new set of 'current' attributes for each of the graphical elements, graph, node and edge. After a single call to push_subgraph(%hash), these 'current' attributes will live a level 1 in the scope stack. See scripts/sub.graph.pl (L) for an example. Another call to push_subgraph(%hash), I an intervening call to pop_subgraph(), will repeat the process, leaving you with a set of attributes at level 2 in the scope stack. Both L and L handle this situation properly. See scripts/sub.sub.graph.pl (L) for an example. At the moment, due to design defects (IMHO) in the underlying L logic, there are some tiny problems with this: =over 4 =item * A global frame I can't see how to make the graph as a whole (at level 0 in the scope stack) have a frame. =item * Frame color When you specify graph => {color => 'red'} at the parent level, the subgraph has a red frame. I think a subgraph should control its own frame. =item * Parent and child frames When you specify graph => {color => 'red'} at the subgraph level, both that subgraph and it children have red frames. This contradicts what happens at the global level, in that specifying color there does not given the whole graph a frame. =item * Frame visibility A subgraph whose name starts with 'cluster' is currently forced to have a frame, unless you rig it by specifying a color the same as the background. For sample code, see scripts/sub.graph.frames.pl. =back Also, check L for how the color of the frame is chosen by cascading thru a set of options. I've posted an email to the L mailing list suggesting a new option, framecolor, so deal with this issue, including a special color of 'invisible'. =head1 Image Maps As of V 2.43, C supports image maps, both client and server side. For web use, note that these options also take effect when generating SVGs, for a much lighter-weight solution to hyperlinking graph nodes and edges. =head2 The Default URL See the L. Their sample code has a dot file - x.gv - containing this line: URL="http://www.research.att.com/base.html"; The way you set such a url in C is via a new parameter to C. This parameter is called C and it takes a hashref as a value. Currently the only key used within that hashref is the case-sensitive C. Thus you must do this to set a URL: my($graph) = GraphViz2 -> new ( ... im_meta => { URL => 'http://savage.net.au/maps/demo.3.1.html', # Note: URL must be in caps. }, ); See maps/demo.3.pl and maps/demo.4.pl for sample code. =head2 Typical Code Normally you would call C as: $graph -> run ( format => $format, output_file => $output_file ); That line was copied from scripts/cluster.pl. To trigger image map processing, you must include 2 new parameters: $graph -> run ( format => $format, output_file => $output_file, im_format => $im_format, im_output_file => $im_output_file ); That line was copied from maps/demo.3.pl, and there is an identical line in maps/demo.4.pl. =head2 The New Parameters to run() =over 4 =item * im_format => $str Expected values: 'imap' (server-side) and 'cmapx' (client-side). Default value: 'cmapx'. =item * im_output_file => $file_name The name of the output map file. Default: ''. If you do not set it to anything, the new image maps code is ignored. =back =head2 Sample Code Various demos are shipped in the new maps/ directory: Each demo, when FTPed to your web server displays some text with an image in the middle. In each case you can click on the upper oval to jump to one page, or click on the lower oval to jump to a different page, or click anywhere else in the image to jump to a third page. =over 4 =item * demo.1.* This set demonstrates a server-side image map but does not use C. You have to run demo.1.sh which generates demo.1.map, and then you FTP the whole dir maps/ to your web server. URL: your.domain.name/maps/demo.1.html. =item * demo.2.* This set demonstrates a client-side image map but does not use C. You have to run demo.2.sh which generates demo.2.map, and then you manually copy demo.2.map into demo.2.html, replacing any version of the map already present. After that you FTP the whole dir maps/ to your web server. URL: your.domain.name/maps/demo.2.html. =item * demo.3.* This set demonstrates a server-side image map using C via demo.3.pl. Note line 54 of demo.3.pl which sets the default C to 'imap'. URL: your.domain.name/maps/demo.3.html. =item * demo.4.* This set demonstrates a client-side image map using C via demo.4.pl. As with demo.2.* there is some manually editing to be done. Note line 54 of demo.4.pl which sets the default C to 'cmapx'. This is the only important difference between this demo and the previous one. There are other minor differences, in that one uses 'svg' and the other 'png'. And of course the urls of the web pages embedded in the code and in those web pages differs, just to demonstate that the maps do indeed lead to different pages. URL: your.domain.name/maps/demo.4.html. =back =head1 Methods =head2 add_edge(from => $from_node_name, to => $to_node_name, [label => $label, %hash]) Adds an edge to the graph. Returns $self to allow method chaining. Here, [] indicate optional parameters. Add a edge from 1 node to another. $from_node_name and $to_node_name default to ''. %hash is any edge attributes accepted as L. These are validated in exactly the same way as the edge parameters in the calls to default_edge(%hash), new(edge => {}) and push_subgraph(edge => {}). To make the edge start or finish on a port, see L. =head2 add_node(name => $node_name, [%hash]) my $graph = GraphViz2->new(global => {combine_node_and_port => 0}); $graph->add_node(name => 'struct3', shape => 'record', label => [ { text => "hello\\nworld" }, [ { text => 'b' }, [ { text => 'c{}' }, # reproduced literally { text => 'd', port => 'here' }, { text => 'e' }, ] { text => 'f' }, ], { text => 'g' }, { text => 'h' }, ]); Adds a node to the graph. Returns $self to allow method chaining. If you want to embed newlines or double-quotes in node names or labels, see scripts/quote.pl in L. If you want anonymous nodes, see scripts/anonymous.pl in L. Here, [] indicates an optional parameter. %hash is any node attributes accepted as L. These are validated in exactly the same way as the node parameters in the calls to default_node(%hash), new(node => {}) and push_subgraph(node => {}). The attribute name 'label' may point to a string or an arrayref. =head3 If it is a string... The string is the label. If the C is a record, you can give any text and it will be passed for interpretation by Graphviz. This means you will need to quote E and E (port specifiers), C<|> (cell separator) and C<{> C<}> (structure depth) with C<\> to make them appear literally. For records, the cells start horizontal. Each additional layer of structure will switch the orientation between horizontal and vertical. =head3 If it is an arrayref of strings... =over 4 =item * The node is forced to be a record The actual shape, 'record' or 'Mrecord', is set globally, with: my($graph) = GraphViz2 -> new ( global => {record_shape => 'record'}, # Override default 'Mrecord'. ... ); Or set locally with: $graph -> add_node(name => 'Three', label => ['Good', 'Bad'], shape => 'record'); =item * Each element in the array defines a field in the record These fields are combined into a single node =item * Each element is treated as a label =item * Each label is given a port name (1 .. N) of the form "port$port_count" =item * Judicious use of '{' and '}' in the label can make this record appear horizontally or vertically, and even nested =back =head3 If it is an arrayref of hashrefs... =over 4 =item * The node is forced to be a record The actual shape, 'record' or 'Mrecord', can be set globally or locally, as explained just above. =item * Each element in the array defines a field in the record =item * Each element is treated as a hashref with keys 'text' and 'port' The 'port' key is optional. =item * The value of the 'text' key is the label =item * The value of the 'port' key is the port =item * Judicious use of '{' and '}' in the label can make this record appear horizontally or vertically, and even nested =back See scripts/html.labels.*.pl and scripts/record.*.pl for sample code. See also L. For more details on this complex topic, see L and L. =head2 default_edge(%hash) Sets defaults attributes for edges added subsequently. Returns $self to allow method chaining. %hash is any edge attributes accepted as L. These are validated in exactly the same way as the edge parameters in the calls to new(edge => {}) and push_subgraph(edge => {}). =head2 default_graph(%hash) Sets defaults attributes for the graph. Returns $self to allow method chaining. %hash is any graph attributes accepted as L. These are validated in exactly the same way as the graph parameter in the calls to new(graph => {}) and push_subgraph(graph => {}). =head2 default_node(%hash) Sets defaults attributes for nodes added subsequently. Returns $self to allow method chaining. %hash is any node attributes accepted as L. These are validated in exactly the same way as the node parameters in the calls to new(node => {}) and push_subgraph(node => {}). =head2 default_subgraph(%hash) Sets defaults attributes for clusters and subgraphs. Returns $self to allow method chaining. %hash is any cluster or subgraph attribute accepted as L. These are validated in exactly the same way as the subgraph parameter in the calls to new(subgraph => {}) and push_subgraph(subgraph => {}). =head2 dot_input() Returns the output stream, formatted nicely, to be passed to the external program (e.g. dot). =head2 dot_output() Returns the output from calling the external program (e.g. dot). You I call run() before calling dot_output(), since it is only during the call to run() that the output of the external program is stored in the buffer controlled by dot_output(). This output is available even if run() does not write the output to a file. =head2 edge_hash() Returns, at the end of the run, a hashref keyed by node name, specifically the node at the arrowI end of the hash, i.e. where the edge starts from. Use this to get a list of all nodes and the edges which leave those nodes, the corresponding destination nodes, and the attributes of each edge. my($node_hash) = $graph -> node_hash; my($edge_hash) = $graph -> edge_hash; for my $from (sort keys %$node_hash) { my($attr) = $$node_hash{$from}{attributes}; my($s) = join(', ', map{"$_ => $$attr{$_}"} sort keys %$attr); print "Node: $from\n"; print "\tAttributes: $s\n"; for my $to (sort keys %{$$edge_hash{$from} }) { for my $edge (@{$$edge_hash{$from}{$to} }) { $attr = $$edge{attributes}; $s = join(', ', map{"$_ => $$attr{$_}"} sort keys %$attr); print "\tEdge: $from$$edge{from_port} -> $to$$edge{to_port}\n"; print "\t\tAttributes: $s\n"; } } } If the caller adds the same edge two (or more) times, the attributes from each call are I coalesced (unlike L), but rather the attributes from each call are stored separately in an arrayref. A bit more formally then, $$edge_hash{$from_node}{$to_node} is an arrayref where each element describes one edge, and which defaults to: { attributes => {}, from_port => $from_port, to_port => $to_port, } If I is not provided by the caller, it defaults to '' (the empty string). If it is provided, it contains a leading ':'. Likewise for I. See scripts/report.nodes.and.edges.pl (a version of scripts/html.labels.1.pl) for a complete example. =head2 log([$level, $message]) Logs the message at the given log level. Returns $self to allow method chaining. Here, [] indicate optional parameters. $level defaults to 'debug', and $message defaults to ''. If called with $level eq 'error', it dies with $message. =head2 logger($logger_object) Gets or sets the log object. Here, [] indicates an optional parameter. =head2 node_hash() Returns, at the end of the run, a hashref keyed by node name. Use this to get a list of all nodes and their attributes. my($node_hash) = $graph -> node_hash; for my $name (sort keys %$node_hash) { my($attr) = $$node_hash{$name}{attributes}; my($s) = join(', ', map{"$_ => $$attr{$_}"} sort keys %$attr); print "Node: $name\n"; print "\tAttributes: $s\n"; } If the caller adds the same node two (or more) times, the attributes from each call are I (unlike L), meaning all attributes from all calls are combined under the I sub-key. A bit more formally then, $$node_hash{$node_name} is a hashref where each element describes one node, and which defaults to: { attributes => {}, } See scripts/report.nodes.and.edges.pl (a version of scripts/html.labels.1.pl) for a complete example, including usage of the corresponding L method. =head2 pop_subgraph() Pop off and discard the top element of the scope stack. Returns $self to allow method chaining. =head2 push_subgraph([name => $name, edge => {...}, graph => {...}, node => {...}, subgraph => {...}]) Sets up a new subgraph environment. Returns $self to allow method chaining. Here, [] indicate optional parameters. name => $name is the name to assign to the subgraph. Name defaults to ''. So, without $name, 'subgraph {' is written to the output stream. With $name, 'subgraph "$name" {' is written to the output stream. Note that subgraph names beginning with 'cluster' L. See scripts/rank.sub.graph.[1234].pl for the effect of various values for $name. edge => {...} is any edge attributes accepted as L. These are validated in exactly the same way as the edge parameters in the calls to default_edge(%hash), new(edge => {}) and push_subgraph(edge => {}). graph => {...} is any graph attributes accepted as L. These are validated in exactly the same way as the graph parameters in the calls to default_graph(%hash), new(graph => {}) and push_subgraph(graph => {}). node => {...} is any node attributes accepted as L. These are validated in exactly the same way as the node parameters in the calls to default_node(%hash), new(node => {}) and push_subgraph(node => {}). subgraph => {..} is for setting attributes applicable to clusters and subgraphs. Currently the only subgraph attribute is C, but clusters have many attributes available. See the second column of the L for details. A typical usage would be push_subgraph(subgraph => {rank => 'same'}) so that all nodes mentioned within the subgraph are constrained to be horizontally aligned. See scripts/rank.sub.graph.[12].pl and scripts/sub.graph.frames.pl for sample code. =head2 valid_attributes() Returns a hashref of all attributes known to this module, keyed by type to hashrefs to true values. Stored in this module, using L. These attributes are used to validate attributes in many situations. You wouldn't normally need to use this method. See scripts/report.valid.attributes.pl. See L. =head2 run([driver => $exe, format => $string, timeout => $integer, output_file => $output_file]) Runs the given program to process the output stream. Returns $self to allow method chaining. Here, [] indicate optional parameters. $driver is the name of the external program to run. It defaults to the value supplied in the call to new(global => {driver => '...'}), which in turn defaults to L's which('dot') return value. $format is the type of output file to write. It defaults to the value supplied in the call to new(global => {format => '...'}), which in turn defaults to 'svg'. $timeout is the time in seconds to wait while the external program runs, before dieing with an error. It defaults to the value supplied in the call to new(global => {timeout => '...'}), which in turn defaults to 10. $output_file is the name of the file into which the output from the external program is written. There is no default value for $output_file. If a value is not supplied for $output_file, the only way to recover the output of the external program is to call dot_output(). This method performs a series of tasks: =over 4 =item * Run the chosen external program on the L =item * Capture STDOUT and STDERR from that program =item * Die if STDERR contains anything =item * Copies STDOUT to the buffer controlled by the dot_output() method =item * Write the captured contents of STDOUT to $output_file, if $output_file has a value =back =head2 stringify_attributes($context, $option) Returns a string suitable to writing to the output stream. $context is one of 'edge', 'graph', 'node', or a special string. See the code for details. You wouldn't normally need to use this method. =head2 validate_params($context, \%attributes) Validate the given attributes within the given context. Also, if $context is 'subgraph', attributes are allowed to be in the 'cluster' context. Returns $self to allow method chaining. $context is one of 'edge', 'global', 'graph', or 'node'. You wouldn't normally need to use this method. =head2 verbose([$integer]) Gets or sets the verbosity level, for when a logging object is not used. Here, [] indicates an optional parameter. =head1 MISC =head2 Graphviz version supported GraphViz2 targets V 2.34.0 of L. This affects the list of available attributes per graph item (node, edge, cluster, etc) available. See the second column of the L for details. =head2 Supported file formats Parses the output of C, so depends on local installation. =head2 Special characters in node names and labels L escapes these 2 characters in those contexts: []. Escaping the 2 chars [] started with V 2.10. Previously, all of []{} were escaped, but {} are used in records to control the orientation of fields, so they should not have been escaped in the first place. It would be nice to also escape | and <, but these characters are used in specifying fields and ports in records. See the next couple of points for details. =head2 Ports Ports are what L calls those places on the outline of a node where edges leave and terminate. The L syntax for ports is a bit unusual: =over 4 =item * This works: "node_name":port5 =item * This doesn't: "node_name:port5" =back Let me repeat - that is Graphviz syntax, not GraphViz2 syntax. In Perl, you must do this: $graph -> add_edge(from => 'struct1:f1', to => 'struct2:f0', color => 'blue'); You don't have to quote all node names in L, but some, such as digits, must be quoted, so I've decided to quote them all. =head2 How labels interact with ports You can specify labels with ports in these ways: =over 4 =item * As a string $graph -> add_node(name => 'struct3', label => "hello\nworld |{ b |{c| d|e}| f}| g | h"); Here, the string contains a port (), field markers (|), and orientation markers ({}). Clearly, you must specify the field separator character '|' explicitly. In the next 2 cases, it is implicit. Then you use $graph -> add_edge(...) to refer to those ports, if desired: $graph -> add_edge(from => 'struct1:f2', to => 'struct3:here', color => 'red'); The same label is specified in the next case. =item * As an arrayref of hashrefs From scripts/record.2.pl: $graph -> add_node(name => 'struct3', label => [ { text => "hello\nworld", }, { text => '{b', }, { text => '{c', }, { port => '', text => 'd', }, { text => 'e}', }, { text => 'f}', }, { text => 'g', }, { text => 'h', }, ]); Each hashref is a field, and hence you do not specify the field separator character '|'. Then you use $graph -> add_edge(...) to refer to those ports, if desired. Again, from scripts/record.2.pl: $graph -> add_edge(from => 'struct1:f2', to => 'struct3:here', color => 'red'); The same label is specified in the previous case. =item * As an arrayref of strings From scripts/html.labels.1.pl: $graph -> add_node(name => 'Oakleigh', shape => 'record', color => 'blue', label => ['West Oakleigh', 'East Oakleigh']); Here, again, you do not specify the field separator character '|'. What happens is that each string is taken to be the label of a field, and each field is given an auto-generated port name of the form "", where $n starts from 1. Here's how you refer to those ports, again from scripts/html.labels.1.pl: $graph -> add_edge(from => 'Murrumbeena', to => 'Oakleigh:port2', color => 'green', label => 'Run
Sprint>'); =back See also the docs for the C<< add_node(name => $node_name, [%hash]) >> method. =head2 Attributes for clusters Just use subgraph => {...}, because the code (as of V 2.22) accepts attributes belonging to either clusters or subgraphs. An example attribute is C, which is used for clusters but not for subgraphs: $graph->push_subgraph( graph => {label => 'Child the Second'}, name => 'cluster Second subgraph', node => {color => 'magenta', shape => 'diamond'}, subgraph => {pencolor => 'white'}, # White hides the cluster's frame. ); # other nodes or edges can be added within it... $graph->pop_subgraph; =head1 TODO =over 4 =item * Handle edges such as 1 -> 2 -> {A B}, as seen in L's graphs/directed/switch.gv But how? =item * Validate parameters more carefully, e.g. to reject non-hashref arguments where appropriate Some method parameter lists take keys whose value must be a hashref. =back =head1 A Extremely Short List of Other Graphing Software L. L. Read more on that L. L. =head1 Thanks Many thanks are due to the people who chose to make L Open Source. And thanks to L, who wrote L, and kindly gave me co-maint of the module. =head1 Version Numbers Version numbers < 1.00 represent development versions. From 1.00 up, they are production versions. =head1 Repository L =head1 Author L was written by Ron Savage Iron@savage.net.auE> in 2011. Home page: L. =head1 Copyright Australian copyright (c) 2011, Ron Savage. All Programs of mine are 'OSI Certified Open Source Software'; you can redistribute them and/or modify them under the terms of The Perl License, a copy of which is available at: http://dev.perl.org/licenses/ =cut __DATA__ @@ arrow_modifier l o r @@ arrow box crow curve diamond dot icurve inv none normal tee vee @@ common_attribute _background => graph area => node, cluster arrowhead => edge arrowsize => edge arrowtail => edge bb => graph bgcolor => graph, cluster center => graph charset => graph class => edge, node, cluster, graph clusterrank => graph color => edge, node, cluster colorscheme => edge, node, cluster, graph comment => edge, node, graph compound => graph concentrate => graph constraint => edge Damping => graph decorate => edge defaultdist => graph dim => graph dimen => graph dir => edge diredgeconstraints => graph distortion => node dpi => graph edgehref => edge edgetarget => edge edgetooltip => edge edgeURL => edge epsilon => graph esep => graph fillcolor => node, edge, cluster fixedsize => node fontcolor => edge, node, graph, cluster fontname => edge, node, graph, cluster fontnames => graph fontpath => graph fontsize => edge, node, graph, cluster forcelabels => graph gradientangle => node, cluster, graph group => node head_lp => edge headclip => edge headhref => edge headlabel => edge headport => edge headtarget => edge headtooltip => edge headURL => edge height => node href => graph, cluster, node, edge id => graph, cluster, node, edge image => node imagepath => graph imagepos => node imagescale => node inputscale => graph K => graph, cluster label => edge, node, graph, cluster label_scheme => graph labelangle => edge labeldistance => edge labelfloat => edge labelfontcolor => edge labelfontname => edge labelfontsize => edge labelhref => edge labeljust => graph, cluster labelloc => node, graph, cluster labeltarget => edge labeltooltip => edge labelURL => edge landscape => graph layer => edge, node, cluster layerlistsep => graph layers => graph layerselect => graph layersep => graph layout => graph len => edge levels => graph levelsgap => graph lhead => edge lheight => graph, cluster lp => edge, graph, cluster ltail => edge lwidth => graph, cluster margin => node, cluster, graph maxiter => graph mclimit => graph mindist => graph minlen => edge mode => graph model => graph mosek => graph newrank => graph nodesep => graph nojustify => graph, cluster, node, edge normalize => graph notranslate => graph nslimit => graph nslimit1 => graph ordering => graph, node orientation => node, graph outputorder => graph overlap => graph overlap_scaling => graph overlap_shrink => graph pack => graph packmode => graph pad => graph page => graph pagedir => graph pencolor => cluster penwidth => cluster, node, edge peripheries => node, cluster pin => node pos => edge, node quadtree => graph quantum => graph rank => subgraph rankdir => graph ranksep => graph ratio => graph rects => node regular => node remincross => graph repulsiveforce => graph resolution => graph root => graph, node rotate => graph rotation => graph samehead => edge sametail => edge samplepoints => node scale => graph searchsize => graph sep => graph shape => node shapefile => node showboxes => edge, node, graph sides => node size => graph skew => node smoothing => graph sortv => graph, cluster, node splines => graph start => graph style => edge, node, cluster, graph stylesheet => graph tail_lp => edge tailclip => edge tailhref => edge taillabel => edge tailport => edge tailtarget => edge tailtooltip => edge tailURL => edge target => edge, node, graph, cluster tooltip => node, edge, cluster, graph truecolor => graph URL => edge, node, graph, cluster vertices => node viewport => graph voro_margin => graph weight => edge width => node xdotversion => graph xlabel => edge, node xlp => node, edge z => node @@ global combine_node_and_port directed driver format im_format label name record_shape strict timeout @@ im_meta URL @@ node Mcircle Mdiamond Msquare assembly box box3d cds circle component cylinder diamond doublecircle doubleoctagon egg ellipse fivepoverhang folder hexagon house insulator invhouse invtrapezium invtriangle larrow lpromoter none note noverhang octagon oval parallelogram pentagon plain plaintext point polygon primersite promoter proteasesite proteinstab rarrow rect rectangle restrictionsite ribosite rnastab rpromoter septagon signature square star tab terminator threepoverhang trapezium triangle tripleoctagon underline utr GraphViz2-2.67/xt/0000755000175000017500000000000014266245444013660 5ustar osboxesosboxesGraphViz2-2.67/xt/author/0000755000175000017500000000000014266245444015162 5ustar osboxesosboxesGraphViz2-2.67/xt/author/pod.t0000644000175000017500000000020413730646160016120 0ustar osboxesosboxesuse Test::More; eval "use Test::Pod 1.45"; plan skip_all => "Test::Pod 1.45 required for testing POD" if $@; all_pod_files_ok(); GraphViz2-2.67/xt/manifest.t0000644000175000017500000000047213734121747015654 0ustar osboxesosboxesuse strict; use warnings; use Test::More; use ExtUtils::Manifest; unless ( $ENV{RELEASE_TESTING} ) { plan( skip_all => "Author tests not required for installation" ); } plan tests => 2; is_deeply [ ExtUtils::Manifest::manicheck() ], [], 'missing'; is_deeply [ ExtUtils::Manifest::filecheck() ], [], 'extra'; GraphViz2-2.67/MANIFEST0000644000175000017500000000522614266245445014364 0ustar osboxesosboxesChanges lib/GraphViz2.pm lib/GraphViz2/Parse/Regexp.pm lib/GraphViz2/Parse/STT.pm lib/GraphViz2/Parse/Yacc.pm lib/GraphViz2/Parse/Yapp.pm LICENSE Makefile.PL MANIFEST This list of files MANIFEST.SKIP README.md t/calc.input t/calc.output t/calc3.output t/calc3.y t/gen.anonymous.t t/gen.circo.t t/gen.class.t t/gen.cluster.t t/gen.Heawood.t t/gen.html.labels.1.t t/gen.html.labels.2.t t/gen.html.labels.3.t t/gen.jointed.edges.t t/gen.macro.1.t t/gen.macro.2.t t/gen.macro.3.t t/gen.macro.4.t t/gen.macro.5.t t/gen.map.3.t t/gen.map.4.t t/gen.parse.regexp.t t/gen.parse.stt.t t/gen.parse.yacc.t t/gen.parse.yapp.t t/gen.plaintext.t t/gen.quote.t t/gen.rank.sub.graph.1.t t/gen.record.1.t t/gen.record.2.t t/gen.record.3.t t/gen.record.4.t t/gen.sub.graph.frames.t t/gen.sub.graph.t t/gen.sub.sub.graph.t t/gen.trivial.t t/gen.unnamed.sub.graph.t t/gen.utf8.1.t t/methods.t t/record.old.t t/sample.stt.1.dat t/sample.stt.2.dat t/snapshots/gen_anonymous_t/dot_file t/snapshots/gen_circo_t/dot_file t/snapshots/gen_class_t/dot_file t/snapshots/gen_cluster_t/dot_file t/snapshots/gen_Heawood_t/dot_file t/snapshots/gen_html_labels_1_t/dot_file t/snapshots/gen_html_labels_2_t/dot_file t/snapshots/gen_html_labels_3_t/dot_file t/snapshots/gen_jointed_edges_t/dot_file t/snapshots/gen_macro_1_t/dot_file t/snapshots/gen_macro_2_t/dot_file t/snapshots/gen_macro_3_t/dot_file t/snapshots/gen_macro_4_t/dot_file t/snapshots/gen_macro_5_t/dot_file t/snapshots/gen_map_3_t/dot_file t/snapshots/gen_map_4_t/dot_file t/snapshots/gen_parse_regexp_t/dot_file t/snapshots/gen_parse_stt_t/dot_file t/snapshots/gen_parse_yacc_t/dot_file t/snapshots/gen_parse_yapp_t/dot_file t/snapshots/gen_plaintext_t/dot_file t/snapshots/gen_quote_t/dot_file t/snapshots/gen_rank_sub_graph_1_t/dot_file t/snapshots/gen_record_1_t/dot_file t/snapshots/gen_record_2_t/dot_file t/snapshots/gen_record_3_t/dot_file t/snapshots/gen_record_4_t/dot_file t/snapshots/gen_sub_graph_frames_t/dot_file t/snapshots/gen_sub_graph_t/dot_file t/snapshots/gen_sub_sub_graph_t/dot_file t/snapshots/gen_trivial_t/dot_file t/snapshots/gen_unnamed_sub_graph_t/dot_file t/snapshots/gen_utf8_1_t/dot_file t/snapshots/methods_t/mve_edges_0_0 t/snapshots/methods_t/mve_edges_0_1 t/snapshots/methods_t/mve_edges_1_0 t/snapshots/methods_t/mve_edges_1_1 t/snapshots/methods_t/mve_nodes_0_0 t/snapshots/methods_t/mve_nodes_0_1 t/snapshots/methods_t/mve_nodes_1_0 t/snapshots/methods_t/mve_nodes_1_1 t/snapshots/methods_t/undir_dot_input t/snapshots/record_old_t/dot_file xt/author/pod.t xt/manifest.t META.yml Module YAML meta-data (added by MakeMaker) META.json Module JSON meta-data (added by MakeMaker) GraphViz2-2.67/Makefile.PL0000644000175000017500000000460413776430666015212 0ustar osboxesosboxesuse strict; use warnings; use ExtUtils::MakeMaker; use File::Spec; use File::Temp; sub is_dot_installed { # 1: Create a temp file containing DOT commands. # The EXLOCK option is for BSD-based systems. # newdir() croaks() if it fails, which is what we want. my($temp_dir) = File::Temp -> newdir('temp.XXXX', CLEANUP => 1, EXLOCK => 0, TMPDIR => 1); my($gv_file) = File::Spec -> catfile($temp_dir, 'test.gv'); open my $fh, ">", $gv_file or die "Can't create temp file: $!\n"; print $fh "digraph graph_14 {node_14}\n" or die "Can't write to temp file: $!\n"; close $fh or die "Can't close temp file: $!\n"; # 2: Run dot to create an SVG file. my $stdout = `dot -Tsvg $gv_file`; # 3: If that failed, we die. die "Please install Graphviz from http://www.graphviz.org/\n" if ($stdout !~ m||); } # End of is_dot_installed. is_dot_installed(); WriteMakefile( AUTHOR => 'Ron Savage ', ABSTRACT => "A wrapper for AT&T's Graphviz", MIN_PERL_VERSION => 5.008008, NAME => 'GraphViz2', PREREQ_PM => { 'Data::Section::Simple' => 0.02, 'File::Which' => 1.21, 'IPC::Run3' => 0.048, 'Moo' => 2.001001, 'Types::Standard' => 1.000005, 'Graph' => '0.9716', }, TEST_REQUIRES => { 'Test::More' => '0.88', # done_testing 'Test::Snapshot' => '0.06', }, VERSION_FROM => 'lib/GraphViz2.pm', LICENSE => 'perl', META_MERGE => { 'meta-spec' => { version => 2, }, dynamic_config => 0, resources => { bugtracker => { web => 'https://github.com/graphviz-perl/GraphViz2/issues' }, license => 'http://dev.perl.org/licenses/', repository => { type => 'git', url => 'https://github.com/graphviz-perl/GraphViz2.git', web => 'https://github.com/graphviz-perl/GraphViz2', }, }, prereqs => { develop => { requires => { 'Test::Pod' => '1.48', 'Pod::Markdown' => 0, }, suggests => { # for generating the website stuff 'Text::Xslate' => 1.2000, 'HTTP::Tiny' => 0.012, 'HTML::TreeBuilder' => 4.2, 'Date::Simple' => 3.03, 'Config::Tiny' => 2.16, 'File::HomeDir' => 0.99, }, }, }, }, ); sub MY::postamble { return '' if !-e '.git'; <\$\@ EOF } GraphViz2-2.67/LICENSE0000644000175000017500000004740713730646160014241 0ustar osboxesosboxesTerms of Perl itself a) the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version, or b) the "Artistic License" ---------------------------------------------------------------------------- The General Public License (GPL) Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS ---------------------------------------------------------------------------- The Artistic License Preamble The intent of this document is to state the conditions under which a Package may be copied, such that the Copyright Holder maintains some semblance of artistic control over the development of the package, while giving the users of the package the right to use and distribute the Package in a more-or-less customary fashion, plus the right to make reasonable modifications. Definitions: - "Package" refers to the collection of files distributed by the Copyright Holder, and derivatives of that collection of files created through textual modification. - "Standard Version" refers to such a Package if it has not been modified, or has been modified in accordance with the wishes of the Copyright Holder. - "Copyright Holder" is whoever is named in the copyright or copyrights for the package. - "You" is you, if you're thinking about copying or distributing this Package. - "Reasonable copying fee" is whatever you can justify on the basis of media cost, duplication charges, time of people involved, and so on. (You will not be required to justify it to the Copyright Holder, but only to the computing community at large as a market that must bear the fee.) - "Freely Available" means that no fee is charged for the item itself, though there may be fees involved in handling the item. It also means that recipients of the item may redistribute it under the same conditions they received it. 1. You may make and give away verbatim copies of the source form of the Standard Version of this Package without restriction, provided that you duplicate all of the original copyright notices and associated disclaimers. 2. You may apply bug fixes, portability fixes and other modifications derived from the Public Domain or from the Copyright Holder. A Package modified in such a way shall still be considered the Standard Version. 3. You may otherwise modify your copy of this Package in any way, provided that you insert a prominent notice in each changed file stating how and when you changed that file, and provided that you do at least ONE of the following: a) place your modifications in the Public Domain or otherwise make them Freely Available, such as by posting said modifications to Usenet or an equivalent medium, or placing the modifications on a major archive site such as ftp.uu.net, or by allowing the Copyright Holder to include your modifications in the Standard Version of the Package. b) use the modified Package only within your corporation or organization. c) rename any non-standard executables so the names do not conflict with standard executables, which must also be provided, and provide a separate manual page for each non-standard executable that clearly documents how it differs from the Standard Version. d) make other distribution arrangements with the Copyright Holder. 4. You may distribute the programs of this Package in object code or executable form, provided that you do at least ONE of the following: a) distribute a Standard Version of the executables and library files, together with instructions (in the manual page or equivalent) on where to get the Standard Version. b) accompany the distribution with the machine-readable source of the Package with your modifications. c) accompany any non-standard executables with their corresponding Standard Version executables, giving the non-standard executables non-standard names, and clearly documenting the differences in manual pages (or equivalent), together with instructions on where to get the Standard Version. d) make other distribution arrangements with the Copyright Holder. 5. You may charge a reasonable copying fee for any distribution of this Package. You may charge any fee you choose for support of this Package. You may not charge a fee for this Package itself. However, you may distribute this Package in aggregate with other (possibly commercial) programs as part of a larger (possibly commercial) software distribution provided that you do not advertise this Package as a product of your own. 6. The scripts and library files supplied as input to or produced as output from the programs of this Package do not automatically fall under the copyright of this Package, but belong to whomever generated them, and may be sold commercially, and may be aggregated with this Package. 7. C or perl subroutines supplied by you and linked into this Package shall not be considered part of this Package. 8. The name of the Copyright Holder may not be used to endorse or promote products derived from this software without specific prior written permission. 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. The End