- #!/usr/bin/perl
- package IkiWiki::Plugin::sparkline;
- use warnings;
- use strict;
- use IkiWiki 3.00;
- 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 => "getsetup", id => "sparkline", call => \&getsetup);
- hook(type => "preprocess", id => "sparkline", call => \&preprocess);
- }
- sub getsetup () {
- return
- plugin => {
- safe => 1,
- rebuild => undef,
- section => "widget",
- },
- }
- 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) {
- error gettext("parse error")." \"$key\"";
- }
- elsif ($key eq 'featurepoint') {
- my ($x, $y, $color, $diameter, $text, $location)=
- split(/\s*,\s*/, $value);
- if (! defined $diameter || $diameter < 0) {
- error gettext("invalid 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) {
- error gettext("invalid 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) {
- error gettext("missing values");
- }
- my $height=int($params{height} || 20);
- if ($height < 2 || $height > 100) {
- error gettext("invalid height value");
- }
- if ($style eq "Bar") {
- $php.=qq{\$sparkline->Render($height);\n};
- }
- else {
- if (! exists $params{width}) {
- error gettext("missing width parameter");
- }
- my $width=int($params{width});
- if ($width < 2 || $width > 1024) {
- error gettext("invalid width value");
- }
- $php.=qq{\$sparkline->RenderResampled($width, $height);\n};
- }
-
- $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::SHA};
- error($@) if $@;
- my $fn=$params{page}."/sparkline-".
- IkiWiki::possibly_foolish_untaint(Digest::SHA::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 || ! defined $png) {
- error gettext("failed to run php");
- }
- if (! $params{preview}) {
- writefile($fn, $config{destdir}, $png, 1);
- }
- else {
- # in preview mode, embed the image in a data uri
- # to avoid temp file clutter
- eval q{use MIME::Base64};
- error($@) if $@;
- return "<img src=\"data:image/png;base64,".
- encode_base64($png)."\" />";
- }
- }
- return '<img src="'.urlto($fn, $params{destpage}).'" alt="graph" />';
- }
- 1
|