]> git.vanrenterghem.biz Git - git.ikiwiki.info.git/blobdiff - IkiWiki/Plugin/sparkline.pm
* Add a sparline plugin.
[git.ikiwiki.info.git] / IkiWiki / Plugin / sparkline.pm
diff --git a/IkiWiki/Plugin/sparkline.pm b/IkiWiki/Plugin/sparkline.pm
new file mode 100644 (file)
index 0000000..27e4ff1
--- /dev/null
@@ -0,0 +1,159 @@
+#!/usr/bin/perl
+package IkiWiki::Plugin::sparkline;
+
+use warnings;
+use strict;
+use IkiWiki;
+use IPC::Open2;
+
+my $match_num=qr/[-+]?[0-9]+(?:\.[0-9]+)?/;
+my %locmap=(
+       top => 'TEXT_TOP',
+       right => 'TEXT_RIGHT',
+       bottom => 'TEXT_BOTTOM',
+       left => 'TEXT_LEFT',
+);
+
+sub import { #{{{
+       hook(type => "preprocess", id => "sparkline", call => \&preprocess);
+} # }}}
+
+sub preprocess (@) { #{{{
+       my %params=@_;
+
+       my $php;
+
+       my $style=(exists $params{style} && $params{style} eq "bar") ? "Bar" : "Line";
+       $php=qq{<?php
+               require_once('sparkline/Sparkline_$style.php');
+               \$sparkline = new Sparkline_$style();
+               \$sparkline->SetDebugLevel(DEBUG_NONE);
+       };
+
+       foreach my $param (qw{BarWidth BarSpacing YMin YMaz}) {
+               if (exists $params{lc($param)}) {
+                       $php.=qq{\$sparkline->Set$param(}.int($params{lc($param)}).qq{);\n};
+               }
+       }
+
+       my $c=0;
+       while (@_) {
+               my $key=shift;
+               my $value=shift;
+
+               if ($key=~/^($match_num)(?:,($match_num))?(?:\(([a-z]+)\))?$/) {
+                       $c++;
+                       my ($x, $y);
+                       if (defined $2) {
+                               $x=$1;
+                               $y=$2;
+                       }
+                       else {
+                               $x=$c;
+                               $y=$1;
+                       }
+                       if ($style eq "Bar" && defined $3) {
+                               $php.=qq{\$sparkline->SetData($x, $y, '$3');\n};
+                       }
+                       else {
+                               $php.=qq{\$sparkline->SetData($x, $y);\n};
+                       }
+               }
+               elsif (! length $value) {
+                       return "[[sparkline parse error \"$key\"]]";
+               }
+               elsif ($key eq 'featurepoint') {
+                       my ($x, $y, $color, $diameter, $text, $location)=
+                               split(/\s*,\s*/, $value);
+                       if (! defined $diameter || $diameter < 0) {
+                               return "[[sparkline bad featurepoint diameter]]";
+                       }
+                       $x=int($x);
+                       $y=int($y);
+                       $color=~s/[^a-z]+//g;
+                       $diameter=int($diameter);
+                       $text=~s/[^-a-zA-Z0-9]+//g if defined $text;
+                       if (defined $location) {
+                               $location=$locmap{$location};
+                               if (! defined $location) {
+                                       return "[[sparkline bad featurepoint location]]";
+                               }
+                       }
+                       $php.=qq{\$sparkline->SetFeaturePoint($x, $y, '$color', $diameter};
+                       $php.=qq{, '$text'} if defined $text;
+                       $php.=qq{, $location} if defined $location;
+                       $php.=qq{);\n};
+               }
+       }
+
+       if ($c eq 0) {
+               return "[[sparkline missing values]]";
+       }
+
+       my $height=int($params{height} || 20);
+       if ($height < 2 || $height > 100) {
+               return "[[sparkline bad height value]]";
+       }
+       if ($style eq "Bar") {
+               $php.=qq{\$sparkline->Render($height);\n};
+       }
+       else {
+               if (! exists $params{width}) {
+                       return "[[sparkline missing width parameter]]";
+               }
+               my $width=int($params{width});
+               if ($width < 2 || $width > 1024) {
+                       return "[[sparkline bad width value]]";
+               }
+               $php.=qq{\$sparkline->RenderResampled($width, $height);\n};
+       }
+       
+       if ($params{preview}) {
+               return "[[sparkline previewing not implemented]]";
+       }
+       
+       $php.=qq{\$sparkline->Output();\n?>\n};
+
+       # Use the sha1 of the php code that generates the sparkline as
+       # the base for its filename.
+       eval q{use Digest::SHA1};
+        error($@) if $@;
+       my $fn=$params{page}."/sparkline-".
+               IkiWiki::possibly_foolish_untaint(Digest::SHA1::sha1_hex($php)).
+               ".png";
+       will_render($params{page}, $fn);
+
+       if (! -e "$config{destdir}/$fn") {
+               my $pid;
+               my $sigpipe=0;;
+               $SIG{PIPE}=sub { $sigpipe=1 };
+               $pid=open2(*IN, *OUT, "php");
+
+               # open2 doesn't respect "use open ':utf8'"
+               binmode (OUT, ':utf8');
+
+               print OUT $php;
+               close OUT;
+
+               my $png;
+               {
+                       local $/=undef;
+                       $png=<IN>;
+               }
+               close IN;
+
+               waitpid $pid, 0;
+               $SIG{PIPE}="DEFAULT";
+               if ($sigpipe) {
+                       return  "[[".gettext("sparkline failed to run php")."]]";
+               }
+
+               writefile($fn, $config{destdir}, $png, 1);
+       }
+
+       return '<img src="'.
+               IkiWiki::abs2rel($fn, IkiWiki::dirname($params{destpage})).
+               '" alt="graph" />';
+} # }}}
+
+1