clear sandbox/test commit
[ikiwiki.git] / IkiWiki / Plugin / sparkline.pm
blobe28d2605a2c1802e6afe798cab14548529d55936
1 #!/usr/bin/perl
2 package IkiWiki::Plugin::sparkline;
4 use warnings;
5 use strict;
6 use IkiWiki 3.00;
7 use IPC::Open2;
9 my $match_num=qr/[-+]?[0-9]+(?:\.[0-9]+)?/;
10 my %locmap=(
11 top => 'TEXT_TOP',
12 right => 'TEXT_RIGHT',
13 bottom => 'TEXT_BOTTOM',
14 left => 'TEXT_LEFT',
17 sub import {
18 hook(type => "getsetup", id => "sparkline", call => \&getsetup);
19 hook(type => "preprocess", id => "sparkline", call => \&preprocess);
22 sub getsetup () {
23 return
24 plugin => {
25 safe => 1,
26 rebuild => undef,
27 section => "widget",
31 sub preprocess (@) {
32 my %params=@_;
34 my $php;
36 my $style=(exists $params{style} && $params{style} eq "bar") ? "Bar" : "Line";
37 $php=qq{<?php
38 require_once('sparkline/Sparkline_$style.php');
39 \$sparkline = new Sparkline_$style();
40 \$sparkline->SetDebugLevel(DEBUG_NONE);
43 foreach my $param (qw{BarWidth BarSpacing YMin YMaz}) {
44 if (exists $params{lc($param)}) {
45 $php.=qq{\$sparkline->Set$param(}.int($params{lc($param)}).qq{);\n};
49 my $c=0;
50 while (@_) {
51 my $key=shift;
52 my $value=shift;
54 if ($key=~/^($match_num)(?:,($match_num))?(?:\(([a-z]+)\))?$/) {
55 $c++;
56 my ($x, $y);
57 if (defined $2) {
58 $x=$1;
59 $y=$2;
61 else {
62 $x=$c;
63 $y=$1;
65 if ($style eq "Bar" && defined $3) {
66 $php.=qq{\$sparkline->SetData($x, $y, '$3');\n};
68 else {
69 $php.=qq{\$sparkline->SetData($x, $y);\n};
72 elsif (! length $value) {
73 error gettext("parse error")." \"$key\"";
75 elsif ($key eq 'featurepoint') {
76 my ($x, $y, $color, $diameter, $text, $location)=
77 split(/\s*,\s*/, $value);
78 if (! defined $diameter || $diameter < 0) {
79 error gettext("invalid featurepoint diameter");
81 $x=int($x);
82 $y=int($y);
83 $color=~s/[^a-z]+//g;
84 $diameter=int($diameter);
85 $text=~s/[^-a-zA-Z0-9]+//g if defined $text;
86 if (defined $location) {
87 $location=$locmap{$location};
88 if (! defined $location) {
89 error gettext("invalid featurepoint location");
92 $php.=qq{\$sparkline->SetFeaturePoint($x, $y, '$color', $diameter};
93 $php.=qq{, '$text'} if defined $text;
94 $php.=qq{, $location} if defined $location;
95 $php.=qq{);\n};
99 if ($c eq 0) {
100 error gettext("missing values");
103 my $height=int($params{height} || 20);
104 if ($height < 2 || $height > 100) {
105 error gettext("invalid height value");
107 if ($style eq "Bar") {
108 $php.=qq{\$sparkline->Render($height);\n};
110 else {
111 if (! exists $params{width}) {
112 error gettext("missing width parameter");
114 my $width=int($params{width});
115 if ($width < 2 || $width > 1024) {
116 error gettext("invalid width value");
118 $php.=qq{\$sparkline->RenderResampled($width, $height);\n};
121 $php.=qq{\$sparkline->Output();\n?>\n};
123 # Use the sha1 of the php code that generates the sparkline as
124 # the base for its filename.
125 eval q{use Digest::SHA};
126 error($@) if $@;
127 my $fn=$params{page}."/sparkline-".
128 IkiWiki::possibly_foolish_untaint(Digest::SHA::sha1_hex($php)).
129 ".png";
130 will_render($params{page}, $fn);
132 if (! -e "$config{destdir}/$fn") {
133 my $pid;
134 my $sigpipe=0;
135 $SIG{PIPE}=sub { $sigpipe=1 };
136 $pid=open2(*IN, *OUT, "php");
138 # open2 doesn't respect "use open ':utf8'"
139 binmode (OUT, ':utf8');
141 print OUT $php;
142 close OUT;
144 my $png;
146 local $/=undef;
147 $png=<IN>;
149 close IN;
151 waitpid $pid, 0;
152 $SIG{PIPE}="DEFAULT";
153 if ($sigpipe || ! defined $png) {
154 error gettext("failed to run php");
157 if (! $params{preview}) {
158 writefile($fn, $config{destdir}, $png, 1);
160 else {
161 # in preview mode, embed the image in a data uri
162 # to avoid temp file clutter
163 eval q{use MIME::Base64};
164 error($@) if $@;
165 return "<img src=\"data:image/png;base64,".
166 encode_base64($png)."\" />";
170 return '<img src="'.urlto($fn, $params{destpage}).'" alt="graph" />';