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