Merge branch 'master' of ssh://git.ikiwiki.info/srv/git/ikiwiki.info
[ikiwiki] / IkiWiki / Plugin / sparkline.pm
CommitLineData
d4d535f1 1#!/usr/bin/perl
2package IkiWiki::Plugin::sparkline;
3
4use warnings;
5use strict;
ee1ad53c 6use IkiWiki 2.00;
d4d535f1 7use IPC::Open2;
8
9my $match_num=qr/[-+]?[0-9]+(?:\.[0-9]+)?/;
10my %locmap=(
11 top => 'TEXT_TOP',
12 right => 'TEXT_RIGHT',
13 bottom => 'TEXT_BOTTOM',
14 left => 'TEXT_LEFT',
15);
16
17sub import { #{{{
39056453 18 hook(type => "getsetup", id => "sparkline", call => \&getsetup);
d4d535f1 19 hook(type => "preprocess", id => "sparkline", call => \&preprocess);
20} # }}}
21
39056453
JH
22sub getsetup () { #{{{
23 return
24 plugin => {
25 safe => 1,
26 rebuild => undef,
27 },
28} #}}}
29
d4d535f1 30sub preprocess (@) { #{{{
31 my %params=@_;
32
33 my $php;
34
35 my $style=(exists $params{style} && $params{style} eq "bar") ? "Bar" : "Line";
36 $php=qq{<?php
37 require_once('sparkline/Sparkline_$style.php');
38 \$sparkline = new Sparkline_$style();
39 \$sparkline->SetDebugLevel(DEBUG_NONE);
40 };
41
42 foreach my $param (qw{BarWidth BarSpacing YMin YMaz}) {
43 if (exists $params{lc($param)}) {
44 $php.=qq{\$sparkline->Set$param(}.int($params{lc($param)}).qq{);\n};
45 }
46 }
47
48 my $c=0;
49 while (@_) {
50 my $key=shift;
51 my $value=shift;
52
53 if ($key=~/^($match_num)(?:,($match_num))?(?:\(([a-z]+)\))?$/) {
54 $c++;
55 my ($x, $y);
56 if (defined $2) {
57 $x=$1;
58 $y=$2;
59 }
60 else {
61 $x=$c;
62 $y=$1;
63 }
64 if ($style eq "Bar" && defined $3) {
65 $php.=qq{\$sparkline->SetData($x, $y, '$3');\n};
66 }
67 else {
68 $php.=qq{\$sparkline->SetData($x, $y);\n};
69 }
70 }
71 elsif (! length $value) {
ffc99f59 72 error gettext("parse error")." \"$key\"";
d4d535f1 73 }
74 elsif ($key eq 'featurepoint') {
75 my ($x, $y, $color, $diameter, $text, $location)=
76 split(/\s*,\s*/, $value);
77 if (! defined $diameter || $diameter < 0) {
ffc99f59 78 error gettext("bad featurepoint diameter");
d4d535f1 79 }
80 $x=int($x);
81 $y=int($y);
82 $color=~s/[^a-z]+//g;
83 $diameter=int($diameter);
84 $text=~s/[^-a-zA-Z0-9]+//g if defined $text;
85 if (defined $location) {
86 $location=$locmap{$location};
87 if (! defined $location) {
ffc99f59 88 error gettext("bad featurepoint location");
d4d535f1 89 }
90 }
91 $php.=qq{\$sparkline->SetFeaturePoint($x, $y, '$color', $diameter};
92 $php.=qq{, '$text'} if defined $text;
93 $php.=qq{, $location} if defined $location;
94 $php.=qq{);\n};
95 }
96 }
97
98 if ($c eq 0) {
ffc99f59 99 error gettext("missing values");
d4d535f1 100 }
101
102 my $height=int($params{height} || 20);
103 if ($height < 2 || $height > 100) {
ffc99f59 104 error gettext("bad height value");
d4d535f1 105 }
106 if ($style eq "Bar") {
107 $php.=qq{\$sparkline->Render($height);\n};
108 }
109 else {
110 if (! exists $params{width}) {
ffc99f59 111 error gettext("missing width parameter");
d4d535f1 112 }
113 my $width=int($params{width});
114 if ($width < 2 || $width > 1024) {
ffc99f59 115 error gettext("bad width value");
d4d535f1 116 }
117 $php.=qq{\$sparkline->RenderResampled($width, $height);\n};
118 }
119
d4d535f1 120 $php.=qq{\$sparkline->Output();\n?>\n};
121
122 # Use the sha1 of the php code that generates the sparkline as
123 # the base for its filename.
124 eval q{use Digest::SHA1};
125 error($@) if $@;
cb8d1c86 126 my $fn=$params{page}."/sparkline-".
d4d535f1 127 IkiWiki::possibly_foolish_untaint(Digest::SHA1::sha1_hex($php)).
128 ".png";
cb8d1c86 129 will_render($params{page}, $fn);
d4d535f1 130
131 if (! -e "$config{destdir}/$fn") {
132 my $pid;
133 my $sigpipe=0;;
134 $SIG{PIPE}=sub { $sigpipe=1 };
135 $pid=open2(*IN, *OUT, "php");
136
137 # open2 doesn't respect "use open ':utf8'"
138 binmode (OUT, ':utf8');
139
140 print OUT $php;
141 close OUT;
142
143 my $png;
144 {
145 local $/=undef;
146 $png=<IN>;
147 }
148 close IN;
149
150 waitpid $pid, 0;
151 $SIG{PIPE}="DEFAULT";
152 if ($sigpipe) {
ffc99f59 153 error gettext("failed to run php");
d4d535f1 154 }
155
4cbb1095 156 if (! $params{preview}) {
157 writefile($fn, $config{destdir}, $png, 1);
158 }
159 else {
160 # can't write the file, so embed it in a data uri
161 eval q{use MIME::Base64};
162 error($@) if $@;
163 return "<img src=\"data:image/png;base64,".
164 encode_base64($png)."\" />";
165 }
d4d535f1 166 }
167
6351ae2a 168 return '<img src="'.urlto($fn, $params{destpage}).'" alt="graph" />';
d4d535f1 169} # }}}
170
1711