X-Git-Url: http://git.vanrenterghem.biz/git.ikiwiki.info.git/blobdiff_plain/914c839ceb02fdbb36a49aa6548dc95beaf59ea4..d8ab913061a60be86ca7ee8f3f7ffb76cb712377:/IkiWiki/Plugin/graphviz.pm

diff --git a/IkiWiki/Plugin/graphviz.pm b/IkiWiki/Plugin/graphviz.pm
index b43e96a73..b9f997e04 100644
--- a/IkiWiki/Plugin/graphviz.pm
+++ b/IkiWiki/Plugin/graphviz.pm
@@ -10,7 +10,8 @@ use IPC::Open2;
 
 sub import {
 	hook(type => "getsetup", id => "graphviz", call => \&getsetup);
-	hook(type => "preprocess", id => "graph", call => \&graph);
+	hook(type => "needsbuild", id => "version", call => \&needsbuild);
+	hook(type => "preprocess", id => "graph", call => \&graph, scan => 1);
 }
 
 sub getsetup () {
@@ -18,6 +19,7 @@ sub getsetup () {
 		plugin => {
 			safe => 1,
 			rebuild => undef,
+			section => "widget",
 		},
 }
 
@@ -25,70 +27,117 @@ my %graphviz_programs = (
 	"dot" => 1, "neato" => 1, "fdp" => 1, "twopi" => 1, "circo" => 1
 );
 
+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;
+}
+
 sub render_graph (\%) {
 	my %params = %{(shift)};
-
-	my $src = "$params{type} g {\n";
-	$src .= "charset=\"utf-8\";\n";
+	
+	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 = <IN>;
-		}
+		local $/ = undef;
+		$map=$pagestate{$params{destpage}}{graph}{$sha}=<IN>;
 		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 "<img src=\"data:image/png;base64,".
-				encode_base64($png)."\" />";
-		}
+		error gettext("failed to run graphviz") if ($sigpipe || $?);
 	}
 
-	if ($params{preview}) {
-		return "<img src=\"".urlto($dest, "")."\" />\n";
-	}
-	else {
-		return "<img src=\"".urlto($dest, $params{destpage})."\" />\n";
-	}
+	return "<img src=\"".urlto($dest, $params{destpage}).
+		"\" usemap=\"#graph$sha\" />\n".
+		$map;
 }
 
 sub graph (@) {
 	my %params=@_;
-	$params{src} = "" unless defined $params{src};
+
+	# 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;
+		$params{src}=$s;
+	}
+	else {
+		$params{src}=$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}};