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