summaryrefslogtreecommitdiff
path: root/IkiWiki/Plugin
diff options
context:
space:
mode:
Diffstat (limited to 'IkiWiki/Plugin')
-rw-r--r--IkiWiki/Plugin/sparkline.pm159
1 files changed, 159 insertions, 0 deletions
diff --git a/IkiWiki/Plugin/sparkline.pm b/IkiWiki/Plugin/sparkline.pm
new file mode 100644
index 000000000..27e4ff154
--- /dev/null
+++ b/IkiWiki/Plugin/sparkline.pm
@@ -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