diff options
Diffstat (limited to 'IkiWiki/Plugin')
-rw-r--r-- | IkiWiki/Plugin/sparkline.pm | 159 |
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 |