#!/usr/bin/perl
package IkiWiki::Plugin::linkmap;

use warnings;
use strict;
use IkiWiki 3.00;
use IPC::Open2;
use HTML::Entities;

sub import {
	hook(type => "getsetup", id => "linkmap", call => \&getsetup);
	hook(type => "preprocess", id => "linkmap", call => \&preprocess);
}

sub getsetup () {
	return
		plugin => {
			safe => 1,
			rebuild => undef,
			section => "widget",
		},
}

my $mapnum=0;

sub pageescape {
	my $item = shift;
	# encoding explicitly in case ikiwiki is configured to accept <> or &
	# in file names
	my $title = pagetitle($item, 1);
	# it would not be necessary to encode *all* the html entities (<> would
	# be sufficient, &" probably a good idea), as dot accepts utf8, but it
	# isn't bad either
	$title = encode_entities($title);
	return("<$title>");
}

sub preprocess (@) {
	my %params=@_;

	$params{pages}="*" unless defined $params{pages};
	
	$mapnum++;
	my $connected=IkiWiki::yesno($params{connected});

	# Get all the items to map.
	my %mapitems = map { $_ => urlto($_, $params{destpage}) }
		pagespec_match_list($params{page}, $params{pages},
			# update when a page is added or removed, or its
			# links change
			deptype => deptype("presence", "links"));

	my $dest=$params{page}."/linkmap.png";

	# Use ikiwiki's function to create the file, this makes sure needed
	# subdirs are there and does some sanity checking.
	will_render($params{page}, $dest);
	writefile($dest, $config{destdir}, "");

	# Run dot to create the graphic and get the map data.
	my $pid;
	my $sigpipe=0;
	$SIG{PIPE}=sub { $sigpipe=1 };
	$pid=open2(*IN, *OUT, "dot -Tpng -o '$config{destdir}/$dest' -Tcmapx");
	
	# open2 doesn't respect "use open ':utf8'"
	binmode (IN, ':utf8'); 
	binmode (OUT, ':utf8'); 

	print OUT "digraph linkmap$mapnum {\n";
	print OUT "concentrate=true;\n";
	print OUT "charset=\"utf-8\";\n";
	print OUT "ratio=compress;\nsize=\"".($params{width}+0).", ".($params{height}+0)."\";\n"
		if defined $params{width} and defined $params{height};
	my %shown;
	my $show=sub {
		my $item=shift;
		if (! $shown{$item}) {
			print OUT pageescape($item)." [shape=box,href=\"$mapitems{$item}\"];\n";
			$shown{$item}=1;
		}
	};
	foreach my $item (keys %mapitems) {
		$show->($item) unless $connected;
		foreach my $link (map { bestlink($item, $_) } @{$links{$item}}) {
			next unless length $link and $mapitems{$link};
			foreach my $endpoint ($item, $link) {
				$show->($endpoint);
			}
			print OUT pageescape($item)." -> ".pageescape($link).";\n";
		}
	}
	print OUT "}\n";
	close OUT || error gettext("failed to run dot");

	local $/=undef;
	my $ret="<img src=\"".urlto($dest, $params{destpage}).
	       "\" alt=\"".gettext("linkmap").
	       "\" usemap=\"#linkmap$mapnum\" />\n".
	        <IN>;
	close IN || error gettext("failed to run dot");
	
	waitpid $pid, 0;
	if ($?) {
		error gettext("failed to run dot");
	}
	$SIG{PIPE}="DEFAULT";
	error gettext("failed to run dot") if $sigpipe;

	return $ret;
}

1
