highcharts-rails 3.0.1 → 3.0.1.5.pre1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.markdown CHANGED
@@ -1,3 +1,7 @@
1
+ # 3.0.1.5 / unreleased
2
+
3
+ * Add a Rack endpoint for exporting charts to image files
4
+
1
5
  # 3.0.1 / 2013-04-09
2
6
 
3
7
  * Updated Highcharts to 3.0.1
data/README.markdown CHANGED
@@ -45,6 +45,54 @@ Or one of the themes
45
45
 
46
46
  Other than that, refer to the [Highcharts documentation](http://highcharts.com/documentation/how-to-use)
47
47
 
48
+ ## Export endpoint
49
+
50
+ This gem contains a Rack endpoint for converting the chart to a downloadable file.
51
+ It is not required by default, so to use it, `require
52
+ 'highcharts/export_endpoint'`
53
+
54
+ The endpoint is basically a port of the [PHP version made available](https://github.com/highslide-software/highcharts.com/blob/master/exporting-server/php/php-batik/index.php).
55
+ It currently needs a lot of cleanup, but it is working fine for me. Your milage
56
+ may vary.
57
+
58
+ It uses [Apache Batik](http://xmlgraphics.apache.org/batik/) for the conversion, which must be
59
+ installed separately, as well as a Java Runtime Environment.
60
+
61
+ It expects to find a JRE in `/usr/bin/java`, and Batik in
62
+ `/usr/share/java/batik-rasterizer.jar`, but both paths are configurable.
63
+
64
+ Example usage in Rails:
65
+
66
+ # config/routes.rb
67
+ require 'highcharts/export_endpoint'
68
+
69
+ MyRailsApp::Application.routes.draw do
70
+ ...
71
+ mount Highcharts::ExportEndpoint.new({
72
+ java_path: "/usr/bin/java",
73
+ batik: "/usr/share/java/batik-rasterizer.jar"
74
+ }), at: "highcharts-export"
75
+ ...
76
+ end
77
+
78
+ # When rendering the chart
79
+ new Highcharts.Chart({
80
+ ...
81
+ exporting: {
82
+ url: "/highcharts-export",
83
+ ...
84
+ }
85
+ })
86
+
87
+ ### Cocaine
88
+
89
+ The exporting endpoint uses [Cocaine](https://github.com/thoughtbot/cocaine) for
90
+ handling the command lines and arguments and so on.
91
+
92
+ I don't know a way to get optional dependencies in the gemspec, so for now
93
+ that gets added whether you want it or not. I'd like to get this fixed,
94
+ but would also like to avoid `begin; require 'cocaine'; rescue LoadError; ...; end` and similar hacks.
95
+
48
96
  ## Licensing
49
97
 
50
98
  Highcharts, which makes up the majority of this gem, has [its own, separate licensing](http://highcharts.com/license).
@@ -17,6 +17,7 @@ Gem::Specification.new do |s|
17
17
  s.require_paths = ["lib"]
18
18
 
19
19
  s.add_dependency "railties", ">= 3.1"
20
+ s.add_dependency "cocaine", "~> 0.4.0"
20
21
  s.add_development_dependency "bundler", "~> 1.0"
21
22
  s.add_development_dependency "rails", ">= 3.1"
22
23
  end
@@ -1,2 +1,2 @@
1
1
  require 'highcharts/version'
2
- require 'highcharts/rails'
2
+ require 'highcharts/rails'
@@ -0,0 +1,122 @@
1
+ # encoding: utf-8
2
+ require 'cocaine'
3
+
4
+ module Highcharts
5
+ class ExportEndpoint
6
+ class InsecureSVGError < ArgumentError; end
7
+ class MissingLibrary < RuntimeError; end
8
+ class FailedToGenerateChart < RuntimeError; end
9
+ class InvalidType < ArgumentError; end
10
+
11
+ attr_reader :output, :options
12
+
13
+ def initialize(options = {})
14
+ @options = default_options.merge(options)
15
+ end
16
+
17
+ def default_options
18
+ {
19
+ java_path: "/usr/bin/java",
20
+ batik: "/usr/share/java/batik-rasterizer.jar",
21
+ }
22
+ end
23
+
24
+ def call(env)
25
+ dup._call(env)
26
+ end
27
+
28
+ def _call(env)
29
+ with_rescues do
30
+ raise MissingLibrary.new("Could not find batik-rasterizer.jar in #{options[:batik].inspect}") unless File.exists?(options[:batik].to_s)
31
+
32
+ request = Rack::Request.new(env)
33
+
34
+ filename = request.params["filename"].to_s
35
+ filename = "chart" if filename.blank? || filename !~ /\A[A-Za-z0-9\-_ ]+\Z/
36
+
37
+ type = request.params["type"].to_s
38
+ width = request.params["width"].to_i
39
+ svg = request.params["svg"].to_s
40
+
41
+ raise InsecureSVGError.new if svg.index("<!ENTITY") || svg.index("<!DOCTYPE")
42
+
43
+ if type == "image/svg+xml"
44
+ # We were sent SVG from the client, so can just render that back
45
+ return [200, {
46
+ 'Content-Disposition' => "attachment; filename=\"#{filename}.svg\"",
47
+ 'Content-Type' => 'image/svg+xml'
48
+ }, [svg]]
49
+ end
50
+
51
+ width = width > 0 ? width.to_s : "600"
52
+ extension = case type
53
+ when "image/png" then "png"
54
+ when "image/jpeg" then "jpg"
55
+ when "application/pdf" then "pdf"
56
+ when "image/svg+xml" then "svg"
57
+ else raise InvalidType.new("#{type} is not a valid type.")
58
+ end
59
+
60
+ input = write_svg_to_file(svg)
61
+ @output = create_output_file(extension)
62
+
63
+ command.run(batik: options[:batik], outfile: output.path, type: type, width: width, infile: input.path)
64
+ input.close
65
+ content_length = output.size
66
+ output.rewind
67
+
68
+ raise FailedToGenerateChart.new("Nothing written to file") if !File.exists?(output.path) || content_length < 10
69
+
70
+ Rack::Response.new(self, 200, {
71
+ 'Content-Disposition' => "attachment; filename=\"#{filename}.#{extension}\"",
72
+ 'Content-Type' => type
73
+ }).finish
74
+ end
75
+ end
76
+
77
+ # Pass the block along to the output file, and
78
+ # make sure to close the file afterwards
79
+ def each(&block)
80
+ output.each(&block)
81
+ ensure
82
+ output.close
83
+ end
84
+
85
+ def command
86
+ Cocaine::CommandLine.new(options[:java_path], "-jar :batik -m :type -d :outfile -w :width :infile")
87
+ end
88
+
89
+ def write_svg_to_file(contents)
90
+ file = ::Tempfile.new(["highcharts-input", ".svg"], Dir.tmpdir, encoding: 'utf-8')
91
+ file.puts contents
92
+ file.flush
93
+ file
94
+ end
95
+
96
+ def create_output_file(extension)
97
+ file = ::Tempfile.new(["highcharts-chart", ".#{extension}"])
98
+ file.binmode
99
+ file
100
+ end
101
+
102
+ def with_rescues
103
+ yield
104
+ rescue Highcharts::ExportEndpoint::InsecureSVGError => e
105
+ [400, {'Content-Type' => 'text/plain'}, ["The posted SVG could contain code for a malicious attack"]]
106
+ rescue Highcharts::ExportEndpoint::InvalidType => e
107
+ [400, {'Content-Type' => 'text/plain'}, [e]]
108
+ rescue Cocaine::CommandNotFoundError => e
109
+ [503, {'Content-Type' => 'text/plain'}, ["Unable to find required binary. #{e}"]]
110
+ rescue Highcharts::ExportEndpoint::MissingLibrary => e
111
+ [503, {'Content-Type' => 'text/plain'}, ["Unable to find required library. #{e}"]]
112
+ rescue Highcharts::ExportEndpoint::FailedToGenerateChart => e
113
+ [500, {'Content-Type' => 'text/plain'}, ["Failed to generate chart. More details may be available in the server logs."]]
114
+ rescue => e
115
+ [500, {'Content-Type' => 'text/plain'}, ["Something went wrong. More details may be available in the server logs."]]
116
+ end
117
+
118
+ def error(code, message)
119
+ [code, {}, [message].flatten]
120
+ end
121
+ end
122
+ end
@@ -1,3 +1,3 @@
1
1
  module Highcharts
2
- VERSION = "3.0.1"
2
+ VERSION = "3.0.1.5.pre1"
3
3
  end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: highcharts-rails
3
3
  version: !ruby/object:Gem::Version
4
- prerelease:
5
- version: 3.0.1
4
+ prerelease: 8
5
+ version: 3.0.1.5.pre1
6
6
  platform: ruby
7
7
  authors:
8
8
  - Per Christian B. Viken
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-04-09 00:00:00.000000000 Z
12
+ date: 2013-04-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: railties
@@ -27,6 +27,22 @@ dependencies:
27
27
  - - ! '>='
28
28
  - !ruby/object:Gem::Version
29
29
  version: '3.1'
30
+ - !ruby/object:Gem::Dependency
31
+ name: cocaine
32
+ type: :runtime
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ~>
37
+ - !ruby/object:Gem::Version
38
+ version: 0.4.0
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 0.4.0
30
46
  - !ruby/object:Gem::Dependency
31
47
  name: bundler
32
48
  type: :development
@@ -91,6 +107,7 @@ files:
91
107
  - app/assets/javascripts/highcharts/themes/skies.js
92
108
  - highcharts-rails.gemspec
93
109
  - lib/highcharts-rails.rb
110
+ - lib/highcharts/export_endpoint.rb
94
111
  - lib/highcharts/rails.rb
95
112
  - lib/highcharts/version.rb
96
113
  homepage: http://northblue.org/
@@ -108,9 +125,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
108
125
  required_rubygems_version: !ruby/object:Gem::Requirement
109
126
  none: false
110
127
  requirements:
111
- - - ! '>='
128
+ - - ! '>'
112
129
  - !ruby/object:Gem::Version
113
- version: '0'
130
+ version: 1.3.1
114
131
  requirements: []
115
132
  rubyforge_project:
116
133
  rubygems_version: 1.8.24
@@ -118,3 +135,4 @@ signing_key:
118
135
  specification_version: 3
119
136
  summary: Gem for easily adding Highcharts to the Rails Asset Pipeline
120
137
  test_files: []
138
+ has_rdoc:
data.tar.gz.asc DELETED
@@ -1,18 +0,0 @@
1
- -----BEGIN PGP SIGNATURE-----
2
- Version: GnuPG/MacGPG2 v2.0.19 (Darwin)
3
- Comment: GPGTools - http://gpgtools.org
4
-
5
- iQIcBAABAgAGBQJRZCX4AAoJEH1ncb0Txu7XWUgQAKSwMYAj6ECNovM5FQnW9Xrs
6
- 84xcCzwhur/YD5/jOVxl85tTVmboBRdVPp53CmsuquGYUMRrrXRobt6C6/hMp4DP
7
- imZ72AL1ZjYjFPCjHXuJzVXsiwGKLE7SdhGo01l3+69rhGwurpA3fZ9d1DaO+YZ6
8
- BlwHypnXlQGOPapB7Eh2V/U5UbZi3V1kLTlUaJyUu914p99Jo6MutHobM7aShMOx
9
- hdqn+ZqN0ha2ourKFk+wJKWTMKe4jvlp9SDrOLe9JY+AJ9zfCns+QFwFJQaC6+VE
10
- DlxQVt33q2nl+W7DdQr23kBM9/Vr9tQzNPE4E17NvGGZBwTZGxPSwPVzoO1ajRVH
11
- MwzbcKrqo1AkRDY5th1Hf2WAyruQ62DlbHF/5luJo1ezsuakus74/JWIo36BXKdp
12
- R20yR4hnQflxGj9BwOUZ5oroG4+PvIBHEfy9no7R600u5Q/RzCj+C3MCekYTMyKV
13
- KurzDyFfanNU1RNVkV2rJAAB2373cCZRiORqi7HNfdjkzzJiuFCTIWNkEo2eLPx4
14
- KlCZL5czNj1fgiEZclMGqv7Le5RXR3SuQXzxTw/cpEbvy5Zwm1zUGWankUQb+TEg
15
- 7hP/wQD5E8zJ/ZYbYjXCnS2K8e59+EOc5DcGpaPT9ELSmaCDlG3AvNnKuFS3g+nj
16
- pm1NAKtYl8Y62Yf35hba
17
- =qJvF
18
- -----END PGP SIGNATURE-----
metadata.gz.asc DELETED
@@ -1,18 +0,0 @@
1
- -----BEGIN PGP SIGNATURE-----
2
- Version: GnuPG/MacGPG2 v2.0.19 (Darwin)
3
- Comment: GPGTools - http://gpgtools.org
4
-
5
- iQIbBAABAgAGBQJRZCX4AAoJEH1ncb0Txu7X/t4P9R5ENkevewpnG4BmYF2THRJd
6
- bws9bcN4bPeSYpceQBphAnFEVQfL+QAXzTweHHw5nk/mGg1iPl18ySyWWeWdSq+G
7
- bbjIAqcG9IdeX/ItzzWXn47WvXhjsu7s6LZ3vP57Y7q0+AcHW45Tl90p1XYSFI3D
8
- 1Cx1lh6ljZ7JtsNe8j3lnxHq/gL29RA+f4u+k8u+jdas69I+dpcc5EiuI+9C2AnB
9
- usTAafb8Q7lVwsiv9Mkgufai5c3J9LkSZOjLF8GhoXSVE/xybAFpFs4SzyoGKm6K
10
- C/xXtZV+78/sv5WUm1UK//T/9LXO0jebYiE5/WZgJZDnt/zDVJDYU5Nmsr9IF1ty
11
- QYRHUO3iMZODpDyeRxKDEL1DMYoKrZJbvZqbJx+0FmYSfvxqcyX0cKoIFYOLhZUo
12
- rxWKoEw3otoWiJhKJPC6A+CkYS3NxOjHjixhRs6T59Dxo5Hb5dYO9cF823feCIGU
13
- ge/FlB4wBTJeFrSSMTjl/ABU5Vrdfx2/Ng5e/nyAwFaki8p4n8CRNnfYvsJ8SvQC
14
- 8jAspjcyeiXzQx27ujgny45NTYNMkfrl01OLHsRjVaXnTw97z8+96QQaCV/5M0tA
15
- ZZtWxDI2BTWXIkzBp0BzyAq1utDiS++CE/p3nEhlDGRMwj49iAg3JD3mfS0/OfmB
16
- cHbhD6RW3WPUEGUKW4M=
17
- =LkjG
18
- -----END PGP SIGNATURE-----