Merge commit 'upstream/master' into prv/po
[ikiwiki] / IkiWiki / Plugin / graphviz.pm
1 #!/usr/bin/perl
2 # graphviz plugin for ikiwiki: render graphviz source as an image.
3 # Josh Triplett
4 package IkiWiki::Plugin::graphviz;
5
6 use warnings;
7 use strict;
8 use IkiWiki 2.00;
9 use IPC::Open2;
10
11 sub import { #{{{
12         hook(type => "getsetup", id => "graphviz", call => \&getsetup);
13         hook(type => "preprocess", id => "graph", call => \&graph);
14 } # }}}
15
16 sub getsetup () { #{{{
17         return
18                 plugin => {
19                         safe => 1,
20                         rebuild => undef,
21                 },
22 } #}}}
23
24 my %graphviz_programs = (
25         "dot" => 1, "neato" => 1, "fdp" => 1, "twopi" => 1, "circo" => 1
26 );
27
28 sub render_graph (\%) { #{{{
29         my %params = %{(shift)};
30
31         my $src = "$params{type} g {\n";
32         $src .= "charset=\"utf-8\";\n";
33         $src .= "ratio=compress;\nsize=\"".($params{width}+0).", ".($params{height}+0)."\";\n"
34                 if defined $params{width} and defined $params{height};
35         $src .= $params{src};
36         $src .= "}\n";
37
38         # Use the sha1 of the graphviz code as part of its filename.
39         eval q{use Digest::SHA1};
40         error($@) if $@;
41         my $dest=$params{page}."/graph-".
42                 IkiWiki::possibly_foolish_untaint(Digest::SHA1::sha1_hex($src)).
43                 ".png";
44         will_render($params{page}, $dest);
45
46         if (! -e "$config{destdir}/$dest") {
47                 my $pid;
48                 my $sigpipe=0;;
49                 $SIG{PIPE}=sub { $sigpipe=1 };
50                 $pid=open2(*IN, *OUT, "$params{prog} -Tpng");
51
52                 # open2 doesn't respect "use open ':utf8'"
53                 binmode (OUT, ':utf8');
54
55                 print OUT $src;
56                 close OUT;
57
58                 my $png;
59                 {
60                         local $/ = undef;
61                         $png = <IN>;
62                 }
63                 close IN;
64
65                 waitpid $pid, 0;
66                 $SIG{PIPE}="DEFAULT";
67                 error gettext("failed to run graphviz") if $sigpipe;
68
69                 if (! $params{preview}) {
70                         writefile($dest, $config{destdir}, $png, 1);
71                 }
72                 else {
73                         # can't write the file, so embed it in a data uri
74                         eval q{use MIME::Base64};
75                         error($@) if $@;
76                         return "<img src=\"data:image/png;base64,".
77                                 encode_base64($png)."\" />";
78                 }
79         }
80
81         if ($params{preview}) {
82                 return "<img src=\"".urlto($dest, "")."\" />\n";
83         }
84         else {
85                 return "<img src=\"".urlto($dest, $params{destpage})."\" />\n";
86         }
87 } #}}}
88
89 sub graph (@) { #{{{
90         my %params=@_;
91         $params{src} = "" unless defined $params{src};
92         $params{type} = "digraph" unless defined $params{type};
93         $params{prog} = "dot" unless defined $params{prog};
94         error gettext("prog not a valid graphviz program") unless $graphviz_programs{$params{prog}};
95
96         return render_graph(%params);
97 } # }}}
98
99 1