X-Git-Url: http://git.vanrenterghem.biz/git.ikiwiki.info.git/blobdiff_plain/2c05a34be17c805d929f0ad563acf857eba1d46b..8d4342183b1c3a96797def6fff96feebacb90db6:/IkiWiki/Plugin/graphviz.pm diff --git a/IkiWiki/Plugin/graphviz.pm b/IkiWiki/Plugin/graphviz.pm index b13d15fa6..d4018edaa 100644 --- a/IkiWiki/Plugin/graphviz.pm +++ b/IkiWiki/Plugin/graphviz.pm @@ -5,86 +5,145 @@ package IkiWiki::Plugin::graphviz; use warnings; use strict; -use IkiWiki 2.00; +use IkiWiki 3.00; use IPC::Open2; -sub import { #{{{ - hook(type => "preprocess", id => "graph", call => \&graph); -} # }}} +sub import { + hook(type => "getsetup", id => "graphviz", call => \&getsetup); + hook(type => "needsbuild", id => "version", call => \&needsbuild); + hook(type => "preprocess", id => "graph", call => \&graph, scan => 1); +} + +sub getsetup () { + return + plugin => { + safe => 1, + rebuild => undef, + section => "widget", + }, +} my %graphviz_programs = ( "dot" => 1, "neato" => 1, "fdp" => 1, "twopi" => 1, "circo" => 1 ); -sub render_graph (\%) { #{{{ - my %params = %{(shift)}; +sub needsbuild { + my $needsbuild=shift; + foreach my $page (keys %pagestate) { + if (exists $pagestate{$page}{graph} && + exists $pagesources{$page} && + grep { $_ eq $pagesources{$page} } @$needsbuild) { + # remove state, will be re-added if + # the graph is still there during the rebuild + delete $pagestate{$page}{graph}; + } + } + return $needsbuild; +} - my $src = "$params{type} g {\n"; - $src .= "charset=\"utf-8\";\n"; +sub render_graph (\%) { + my %params = %{(shift)}; + + my $src = "charset=\"utf-8\";\n"; $src .= "ratio=compress;\nsize=\"".($params{width}+0).", ".($params{height}+0)."\";\n" if defined $params{width} and defined $params{height}; $src .= $params{src}; $src .= "}\n"; - - # Use the sha1 of the graphviz code as part of its filename. - eval q{use Digest::SHA1}; + + # Use the sha1 of the graphviz code as part of its filename, + # and as a unique identifier for its imagemap. + eval q{use Digest::SHA}; error($@) if $@; - my $dest=$params{page}."/graph-". - IkiWiki::possibly_foolish_untaint(Digest::SHA1::sha1_hex($src)). - ".png"; + my $sha=IkiWiki::possibly_foolish_untaint(Digest::SHA::sha1_hex($params{type}.$src)); + $src = "$params{type} graph$sha {\n".$src; + + my $dest=$params{page}."/graph-".$sha.".png"; will_render($params{page}, $dest); - if (! -e "$config{destdir}/$dest") { + my $map=$pagestate{$params{destpage}}{graph}{$sha}; + if (! -e "$config{destdir}/$dest" || ! defined $map) { + # Use ikiwiki's function to create the image file, this makes + # sure needed subdirs are there and does some sanity checking. + writefile($dest, $config{destdir}, ""); + my $pid; - my $sigpipe=0;; + my $sigpipe=0; $SIG{PIPE}=sub { $sigpipe=1 }; - $pid=open2(*IN, *OUT, "$params{prog} -Tpng"); + $pid=open2(*IN, *OUT, "$params{prog} -Tpng -o '$config{destdir}/$dest' -Tcmapx"); # open2 doesn't respect "use open ':utf8'" + binmode (IN, ':utf8'); binmode (OUT, ':utf8'); print OUT $src; close OUT; - my $png; - { - local $/ = undef; - $png = ; - } + local $/ = undef; + $map=$pagestate{$params{destpage}}{graph}{$sha}=; close IN; waitpid $pid, 0; $SIG{PIPE}="DEFAULT"; - error gettext("failed to run graphviz") if $sigpipe; - - if (! $params{preview}) { - writefile($dest, $config{destdir}, $png, 1); - } - else { - # can't write the file, so embed it in a data uri - eval q{use MIME::Base64}; - error($@) if $@; - return ""; - } + error gettext("failed to run graphviz") if ($sigpipe || $?); } - if ($params{preview}) { - return "\n"; + return "\n". + $map; +} + +sub graph (@) { + my %params=@_; + + # Support wikilinks in the graph source. + my $src=$params{src}; + $src="" unless defined $src; + $src=IkiWiki::linkify($params{page}, $params{destpage}, $params{src}); + return unless defined wantarray; # scan mode short-circuit + if ($src ne $params{src}) { + # linkify makes html links, but graphviz wants plain + # urls. This is, frankly a hack: Process source as html, + # throw out everything inside tags that is not a href. + my $s; + my $nested=0; + use HTML::Parser; + error $@ if $@; + my $p=HTML::Parser->new(api_version => 3); + $p->handler(start => sub { + my %attrs=%{shift()}; + if (exists $attrs{href}) { + if ($s=~/href\s*=\s*"$/) { + $s.=$attrs{href}; + } + elsif ($s=~/href\s*=\s*$/) { + $s.="\"$attrs{href}\""; + } + else { + $s.="href=\"$attrs{href}\""; + } + } + $nested++; + }, "attr"); + $p->handler(end => sub { + $nested--; + }); + $p->handler(default => sub { + $s.=join("", @_) unless $nested; + }, "text"); + $p->parse($src); + $p->eof; + $s=~s/\[ href= \]//g; # handle self-links + $params{src}=$s; } else { - return "\n"; + $params{src}=$src; } -} #}}} -sub graph (@) { #{{{ - my %params=@_; - $params{src} = "" unless defined $params{src}; $params{type} = "digraph" unless defined $params{type}; $params{prog} = "dot" unless defined $params{prog}; error gettext("prog not a valid graphviz program") unless $graphviz_programs{$params{prog}}; return render_graph(%params); -} # }}} +} 1