shrimp 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 1.9.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in shrimp.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 adeven GmbH Manuel Kniep
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,159 @@
1
+ # Shrimp
2
+
3
+ Creates PDFs from URLs using phantomjs
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'shrimp'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install shrimp
18
+
19
+
20
+ ### pantomjs
21
+
22
+ See http://phantomjs.org/download.html on how to install phatomjs
23
+
24
+ ## Usage
25
+
26
+ ```
27
+ url = 'http://www.google.com'
28
+ options = { :margin => "1cm"}
29
+ Phantomjs::Phantomjs.new(url, options).to_pdf("/output.pdf")
30
+ ```
31
+ ## Configuration
32
+
33
+ ```
34
+ Shrimp.configure do |config|
35
+ # The path to the phantomjs executable
36
+ # defaults to `where phantomjs`
37
+ # config.phantomjs = '/usr/local/bin/phantomjs'
38
+
39
+ # the default pdf output format
40
+ # e.g. "5in*7.5in", "10cm*20cm", "A4", "Letter"
41
+ # config.format = 'A4'
42
+
43
+ # the default margin
44
+ # config.margin = '1cm'
45
+ # the zoom factor
46
+
47
+ # config.zoom = 1
48
+
49
+ # the page orientation 'portrait' or 'landscape'
50
+ # config.orientation = 'portrait'
51
+
52
+ # a temporary dir used to store tempfiles
53
+ # config.tmpdir = Dir.tmpdir
54
+
55
+ # the timeout for phantomjs rendering process
56
+ # rendering_timeout = 90000
57
+
58
+ # the default rendering time
59
+ # config.rendering_time = 1000
60
+ end
61
+ ```
62
+
63
+ ## Middleware
64
+
65
+ Shrimp comes with a middleware that allows users to get a PDF view of any page on your site by appending .pdf to the URL.
66
+
67
+ ### Middleware Setup
68
+
69
+ **Non-Rails Rack apps**
70
+
71
+ # in config.ru
72
+ require 'shrimp'
73
+ use Shrimp::Middleware
74
+
75
+ **Rails apps**
76
+
77
+ # in application.rb(Rails3) or environment.rb(Rails2)
78
+ require 'shrimp'
79
+ config.middleware.use PDFKit::Middleware
80
+
81
+ **With Shrimp options**
82
+
83
+ # options will be passed to Shrimp::Phantom.new
84
+ config.middleware.use Shrimp::Middleware, :margin => '0.5cm', :format => 'Letter'
85
+
86
+ **With conditions to limit routes that can be generated in pdf**
87
+
88
+ # conditions can be regexps (either one or an array)
89
+ config.middleware.use Shrimp::Middleware, {}, :only => %r[^/public]
90
+ config.middleware.use Shrimp::Middleware, {}, :only => [%r[^/invoice], %r[^/public]]
91
+
92
+ # conditions can be strings (either one or an array)
93
+ config.middleware.use Shrimp::Middleware, {}, :only => '/public'
94
+ config.middleware.use Shrimp::Middleware, {}, :only => ['/invoice', '/public']
95
+
96
+ # conditions can be regexps (either one or an array)
97
+ config.middleware.use Shrimp::Middleware, {}, :except => [%r[^/prawn], %r[^/secret]]
98
+
99
+ # conditions can be strings (either one or an array)
100
+ config.middleware.use Shrimp::Middleware, {}, :except => ['/secret']
101
+
102
+
103
+ ### Polling
104
+
105
+ To avoid deadlocks Shrimp::Middleware renders the pdf in a separate process retuning a 503 Retry-After response Header.
106
+ you can setup the polling interval and the polling offset in seconds.
107
+
108
+ config.middleware.use Shrimp::Middleware, :polling_interval => 1, :polling_offset => 5
109
+
110
+ ### Caching
111
+
112
+ To avoid rendering the page on each request you can setup some the cache ttl on seconds
113
+
114
+ config.middleware.use Shrimp::Middleware, :cache_ttl => 3600 # one hour
115
+
116
+
117
+ ### Ajax requests
118
+
119
+ To include some fancy Ajax stuff with jquery
120
+
121
+ ```js
122
+
123
+ var url = '/my_page.pdf'
124
+ var statusCodes = {
125
+ 200: function() {
126
+ return window.location.assign(url);
127
+ },
128
+ 504: function() {
129
+ console.log("Shit's beeing wired"
130
+ },
131
+ 503: function(jqXHR, textStatus, errorThrown) {
132
+ var wait;
133
+ wait = parseInt(jqXHR.getResponseHeader('Retry-After'));
134
+ return setTimeout(function() {
135
+ return $.ajax({
136
+ url: url,
137
+ statusCode: statusCodes
138
+ });
139
+ }, wait * 1000);
140
+ }
141
+ }
142
+ $.ajax({
143
+ url: url,
144
+ statusCode: statusCodes
145
+ })
146
+
147
+ ```
148
+
149
+ ## Contributing
150
+
151
+ 1. Fork it
152
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
153
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
154
+ 4. Push to the branch (`git push origin my-new-feature`)
155
+ 5. Create new Pull Request
156
+
157
+ ## Copyright
158
+ Shrimp is Copyright © 2012 adeven (Manuel Kniep). It is free software, and may be redistributed under the terms
159
+ specified in the LICENSE file.
@@ -0,0 +1,5 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ task default: :spec
@@ -0,0 +1,5 @@
1
+ require 'shrimp/version'
2
+ require 'shrimp/source'
3
+ require 'shrimp/phantom'
4
+ require 'shrimp/middleware'
5
+ require 'shrimp/configuration'
@@ -0,0 +1,49 @@
1
+ module Shrimp
2
+ class Configuration
3
+ attr_accessor :default_options
4
+ attr_writer :phantomjs
5
+
6
+ [:format, :margin, :zoom, :orientation, :tmpdir, :rendering_timeout, :rendering_time].each do |m|
7
+ define_method("#{m}=") do |val|
8
+ @default_options[m]=val
9
+ end
10
+ end
11
+
12
+ def initialize
13
+ @default_options = {
14
+ :format => 'A4',
15
+ :margin => '1cm',
16
+ :zoom => 1,
17
+ :orientation => 'portrait',
18
+ :tmpdir => Dir.tmpdir,
19
+ :rendering_timeout => 90000,
20
+ :rendering_time => 1000
21
+ }
22
+ end
23
+
24
+ def phantomjs
25
+ @phantomjs ||= (defined?(Bundler::GemfileError) ? `bundle exec which phantomjs` : `which phantomjs`).chomp
26
+ end
27
+ end
28
+
29
+ class << self
30
+ attr_accessor :configuration
31
+ end
32
+
33
+ # Configure Phantomjs someplace sensible,
34
+ # like config/initializers/phantomjs.rb
35
+ #
36
+ # @example
37
+ # Shrimp.configure do |config|
38
+ # config.phantomjs = '/usr/local/bin/phantomjs'
39
+ # config.format = 'Letter'
40
+ # end
41
+
42
+ def self.configuration
43
+ @configuration ||= Configuration.new
44
+ end
45
+
46
+ def self.configure
47
+ yield(configuration)
48
+ end
49
+ end
@@ -0,0 +1,175 @@
1
+ module Shrimp
2
+ class Middleware
3
+ def initialize(app, options = { }, conditions = { })
4
+ @app = app
5
+ @options = options
6
+ @conditions = conditions
7
+ @options[:polling_interval] ||= 1
8
+ @options[:polling_offset] ||= 1
9
+ @options[:cache_ttl] ||= 1
10
+ @options[:request_timeout] ||= @options[:polling_interval] * 10
11
+ end
12
+
13
+ def call(env)
14
+ @request = Rack::Request.new(env)
15
+ if render_as_pdf? #&& headers['Content-Type'] =~ /text\/html|application\/xhtml\+xml/
16
+ if already_rendered? && (up_to_date?(@options[:cache_ttl]) || @options[:cache_ttl] == 0)
17
+ if File.size(render_to) == 0
18
+ File.delete(render_to)
19
+ remove_rendering_flag
20
+ return error_response
21
+ end
22
+ return ready_response if env['HTTP_X_REQUESTED_WITH']
23
+ file = File.open(render_to, "rb")
24
+ body = file.read
25
+ file.close
26
+ File.delete(render_to) if @options[:cache_ttl] == 0
27
+ remove_rendering_flag
28
+ response = [body]
29
+ headers = { }
30
+ headers["Content-Length"] = (body.respond_to?(:bytesize) ? body.bytesize : body.size).to_s
31
+ headers["Content-Type"] = "application/pdf"
32
+ [200, headers, response]
33
+ else
34
+ if rendering_in_progress?
35
+ if rendering_timed_out?
36
+ remove_rendering_flag
37
+ error_response
38
+ else
39
+ reload_response(@options[:polling_interval])
40
+ end
41
+ else
42
+ File.delete(render_to) if already_rendered?
43
+ set_rendering_flag
44
+ fire_phantom
45
+ reload_response(@options[:polling_offset])
46
+ end
47
+ end
48
+ else
49
+ @app.call(env)
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ # Private: start phantom rendering in a separate process
56
+ def fire_phantom
57
+ Process::detach fork { Phantom.new(@request.url.sub(%r{\.pdf$}, ''), @options, @request.cookies).to_pdf(render_to) }
58
+ end
59
+
60
+ def render_to
61
+ file_name = Digest::MD5.hexdigest(@request.path) + ".pdf"
62
+ file_path = @options[:out_path]
63
+ "#{file_path}/#{file_name}"
64
+ end
65
+
66
+ def already_rendered?
67
+ File.exists?(render_to)
68
+ end
69
+
70
+ def up_to_date?(ttl = 30)
71
+ (Time.now - File.new(render_to).mtime) <= ttl
72
+ end
73
+
74
+
75
+ def remove_rendering_flag
76
+ @request.session["phantom-rendering"] ||={ }
77
+ @request.session["phantom-rendering"].delete(render_to)
78
+ end
79
+
80
+ def set_rendering_flag
81
+ @request.session["phantom-rendering"] ||={ }
82
+ @request.session["phantom-rendering"][render_to] = Time.now
83
+ end
84
+
85
+ def rendering_timed_out?
86
+ Time.now - @request.session["phantom-rendering"][render_to] > @options[:request_timeout]
87
+ end
88
+
89
+ def rendering_in_progress?
90
+ @request.session["phantom-rendering"]||={ }
91
+ @request.session["phantom-rendering"][render_to]
92
+ end
93
+
94
+ def render_as_pdf?
95
+ request_path_is_pdf = !!@request.path.match(%r{\.pdf$})
96
+
97
+ if request_path_is_pdf && @conditions[:only]
98
+ rules = [@conditions[:only]].flatten
99
+ rules.any? do |pattern|
100
+ if pattern.is_a?(Regexp)
101
+ @request.path =~ pattern
102
+ else
103
+ @request.path[0, pattern.length] == pattern
104
+ end
105
+ end
106
+ elsif request_path_is_pdf && @conditions[:except]
107
+ rules = [@conditions[:except]].flatten
108
+ rules.map do |pattern|
109
+ if pattern.is_a?(Regexp)
110
+ return false if @request.path =~ pattern
111
+ else
112
+ return false if @request.path[0, pattern.length] == pattern
113
+ end
114
+ end
115
+ return true
116
+ else
117
+ request_path_is_pdf
118
+ end
119
+ end
120
+
121
+ def concat(accepts, type)
122
+ (accepts || '').split(',').unshift(type).compact.join(',')
123
+ end
124
+
125
+ def reload_response(interval=1)
126
+ body = <<-HTML.gsub(/[ \n]+/, ' ').strip
127
+ <html>
128
+ <head>
129
+ </head>
130
+ <body onLoad="setTimeout(function(){ window.location.reload()}, #{interval * 1000});">
131
+ <h2>Prepareing pdf... </h2>
132
+ </body>
133
+ </ html>
134
+ HTML
135
+ headers = { }
136
+ headers["Content-Length"] = body.size.to_s
137
+ headers["Content-Type"] = "text/html"
138
+ headers["Retry-After"] = interval.to_s
139
+
140
+ [503, headers, [body]]
141
+ end
142
+
143
+ def ready_response
144
+ body = <<-HTML.gsub(/[ \n]+/, ' ').strip
145
+ <html>
146
+ <head>
147
+ </head>
148
+ <body>
149
+ <a href="#{@request.path}">PDF ready here</a>
150
+ </body>
151
+ </ html>
152
+ HTML
153
+ headers = { }
154
+ headers["Content-Length"] = body.size.to_s
155
+ headers["Content-Type"] = "text/html"
156
+ [200, headers, [body]]
157
+ end
158
+
159
+ def error_response
160
+ body = <<-HTML.gsub(/[ \n]+/, ' ').strip
161
+ <html>
162
+ <head>
163
+ </head>
164
+ <body>
165
+ <h2>Sorry request timed out... </h2>
166
+ </body>
167
+ </ html>
168
+ HTML
169
+ headers = { }
170
+ headers["Content-Length"] = body.size.to_s
171
+ headers["Content-Type"] = "text/html"
172
+ [504, headers, [body]]
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,93 @@
1
+ module Shrimp
2
+ class NoExecutableError < StandardError
3
+ def initialize
4
+ msg = "No phantomjs executable found at #{Shrimp.configuration.phantomjs}\n"
5
+ msg << ">> Please install phantomjs - http://phantomjs.org/download.html"
6
+ super(msg)
7
+ end
8
+ end
9
+
10
+ class ImproperSourceError < StandardError
11
+ def initialize(msg = nil)
12
+ super("Improper Source: #{msg}")
13
+ end
14
+ end
15
+
16
+
17
+ class Phantom
18
+ attr_accessor :source, :configuration, :outfile
19
+ attr_reader :options, :cookies
20
+ SCRIPT_FILE = File.expand_path('../rasterize.js', __FILE__)
21
+
22
+ # Public: Runs the phantomjs binary
23
+ #
24
+ # Returns the stdout output of phantomjs
25
+ def run
26
+ @result = `#{cmd}`
27
+ end
28
+
29
+ # Public: Returns the phantom rasterize command
30
+ def cmd
31
+ cookie_file = dump_cookies
32
+ format, zoom, margin, orientation = options[:format], options[:zoom], options[:margin], options[:orientation]
33
+ rendering_time, timeout = options[:rendering_time], options[:rendering_timeout]
34
+ @outfile ||= "#{options[:tmpdir]}/#{Digest::MD5.hexdigest((Time.now.to_i + rand(9001)).to_s)}.pdf"
35
+
36
+ [Shrimp.configuration.phantomjs, SCRIPT_FILE, @source.to_s, @outfile, format, zoom, margin, orientation, cookie_file, rendering_time, timeout].join(" ")
37
+ end
38
+
39
+ # Public: initializes a new Phantom Object
40
+ #
41
+ # url_or_file - The url of the html document to render
42
+ # options - a hash with options for rendering
43
+ # * format - the paper format for the output eg: "5in*7.5in", "10cm*20cm", "A4", "Letter"
44
+ # * zoom - the viewport zoom factor
45
+ # * margin - the margins for the pdf
46
+ # cookies - hash with cookies to use for rendering
47
+ # outfile - optional path for the output file a Tempfile will be created if not given
48
+ #
49
+ # Returns self
50
+ def initialize(url_or_file, options = { }, cookies={ }, outfile = nil)
51
+ @source = Source.new(url_or_file)
52
+ @options = Shrimp.configuration.default_options.merge(options)
53
+ @cookies = cookies
54
+ @outfile = outfile
55
+ raise NoExecutableError.new unless File.exists?(Shrimp.configuration.phantomjs)
56
+ end
57
+
58
+ # Public: renders to pdf
59
+ # path - the destination path defaults to outfile
60
+ #
61
+ # Returns the path to the pdf file
62
+ def to_pdf(path=nil)
63
+ @outfile = path
64
+ self.run
65
+ @outfile
66
+ end
67
+
68
+ # Public: renders to pdf
69
+ # path - the destination path defaults to outfile
70
+ #
71
+ # Returns a File Handle of the Resulting pdf
72
+ def to_file(path=nil)
73
+ self.to_pdf(path)
74
+ File.new(@outfile)
75
+ end
76
+
77
+ # Public: renders to pdf
78
+ # path - the destination path defaults to outfile
79
+ #
80
+ # Returns the binary string of the pdf
81
+ def to_string(path=nil)
82
+ self.to_pdf(path)
83
+ File.open(path).read
84
+ end
85
+
86
+ private
87
+ def dump_cookies
88
+ host = @source.url? ? URI::parse(@source.to_s).host : "/"
89
+ json = @cookies.inject([]) { |a, (k, v)| a.push({ name: k, value: v, domain: host }); a }.to_json
90
+ File.open("#{options[:tmpdir]}/#{rand}.cookies", 'w') { |f| f.puts json; f }.path
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,73 @@
1
+ var page = require('webpage').create(),
2
+ fs = require('fs'),
3
+ system = require('system'),
4
+ margin = system.args[5] || '0cm',
5
+ orientation = system.args[6] || 'portrait',
6
+ cookie_file = system.args[7] ,
7
+ render_time = system.args[8] || 10000 ,
8
+ time_out = system.args[9] || 90000 ,
9
+ cookies = {},
10
+ address, output, size, statusCode;
11
+
12
+ window.setTimeout(function () {
13
+ console.log("Shit's being weird no result within: " + time_out + "ms");
14
+ phantom.exit(1);
15
+ }, time_out);
16
+
17
+ try {
18
+ f = fs.open(cookie_file, "r");
19
+ cookies = JSON.parse(f.read());
20
+ fs.remove(cookie_file)
21
+ } catch (e) {
22
+ console.log(e);
23
+ }
24
+ phantom.cookiesEnabled = true;
25
+ phantom.cookies = cookies;
26
+
27
+ if (system.args.length < 3 || system.args.length > 10) {
28
+ console.log('Usage: rasterize.js URL filename [paperwidth*paperheight|paperformat] [zoom] [margin] [orientation] [cookie_file] [render_time] [time_out]');
29
+ console.log(' paper (pdf output) examples: "5in*7.5in", "10cm*20cm", "A4", "Letter"');
30
+ phantom.exit(1);
31
+ } else {
32
+ address = system.args[1];
33
+ output = system.args[2];
34
+ page.viewportSize = { width:600, height:600 };
35
+ if (system.args.length > 3 && system.args[2].substr(-4) === ".pdf") {
36
+ size = system.args[3].split('*');
37
+ page.paperSize = size.length === 2 ? { width:size[0], height:size[1], margin:'0px' }
38
+ : { format:system.args[3], orientation:orientation, margin:margin };
39
+ }
40
+ if (system.args.length > 4) {
41
+ page.zoomFactor = system.args[4];
42
+ }
43
+
44
+ // determine the statusCode
45
+ page.onResourceReceived = function (resource) {
46
+ if (resource.url == address) {
47
+ statusCode = resource.status;
48
+ }
49
+ };
50
+
51
+ page.open(address, function (status) {
52
+ if (status !== 'success' || (statusCode != 200 && statusCode != null)) {
53
+ console.log(statusCode, 'Unable to load the address!');
54
+ if (fs.exists(output)) {
55
+ fs.remove(output);
56
+ }
57
+ fs.touch(output);
58
+ phantom.exit();
59
+ } else {
60
+ window.setTimeout(function () {
61
+ page.render(output + '_tmp.pdf');
62
+
63
+ if (fs.exists(output)) {
64
+ fs.remove(output);
65
+ }
66
+
67
+ fs.move(output + '_tmp.pdf', output);
68
+ console.log('rendered to: ' + output, new Date().getTime());
69
+ phantom.exit();
70
+ }, render_time);
71
+ }
72
+ });
73
+ }
@@ -0,0 +1,25 @@
1
+ require 'uri'
2
+ module Shrimp
3
+ class Source
4
+ def initialize(url_or_file)
5
+ @source = url_or_file
6
+ raise ImproperSourceError.new unless url? || file?
7
+ end
8
+
9
+ def url?
10
+ @source.is_a?(String) && @source.match(URI::regexp)
11
+ end
12
+
13
+ def file?
14
+ @source.kind_of?(File)
15
+ end
16
+
17
+ def html?
18
+ !(url? || file?)
19
+ end
20
+
21
+ def to_s
22
+ file? ? @source.path : @source
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ module Shrimp
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'shrimp/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "shrimp"
8
+ gem.version = Shrimp::VERSION
9
+ gem.authors = ["Manuel Kniep"]
10
+ gem.email = %w(manuel@adeven.com)
11
+ gem.description = %q{html to pdf with phantomjs}
12
+ gem.summary = %q{a phantomjs based pdf renderer}
13
+ gem.homepage = "http://github.com/adeven/shrimp"
14
+ gem.files = `git ls-files`.split($/)
15
+ gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+ gem.require_paths = %w(lib)
18
+ gem.requirements << 'phantomjs, v1.6 or greater'
19
+ gem.add_runtime_dependency "json"
20
+
21
+ # Developmnet Dependencies
22
+ gem.add_development_dependency(%q<rake>, [">=0.9.2"])
23
+ gem.add_development_dependency(%q<rspec>, [">= 2.2.0"])
24
+ gem.add_development_dependency(%q<rack-test>, [">= 0.5.6"])
25
+ end
@@ -0,0 +1,124 @@
1
+ require 'spec_helper'
2
+
3
+ def app;
4
+ Rack::Lint.new(@app)
5
+ end
6
+
7
+ def options
8
+ { :margin => "1cm", :out_path => Dir.tmpdir,
9
+ :polling_offset => 10, :polling_interval => 1, :cache_ttl => 3600,
10
+ :request_timeout => 1 }
11
+ end
12
+
13
+ def mock_app(options = { }, conditions = { })
14
+ main_app = lambda { |env|
15
+ headers = { 'Content-Type' => "text/html" }
16
+ [200, headers, ['Hello world!']]
17
+ }
18
+
19
+ @middleware = Shrimp::Middleware.new(main_app, options, conditions)
20
+ @app = Rack::Session::Cookie.new(@middleware, :key => 'rack.session')
21
+ @middleware.should_receive(:fire_phantom).any_number_of_times
22
+ end
23
+
24
+
25
+ describe Shrimp::Middleware do
26
+ before { mock_app(options) }
27
+
28
+ context "matching pdf" do
29
+ it "should render as pdf" do
30
+ get '/test.pdf'
31
+ @middleware.send(:'render_as_pdf?').should be true
32
+ end
33
+ it "should return 503 the first time" do
34
+ get '/test.pdf'
35
+ last_response.status.should eq 503
36
+ last_response.header["Retry-After"].should eq "10"
37
+ end
38
+
39
+ it "should return 503 the with polling interval the second time" do
40
+ get '/test.pdf'
41
+ get '/test.pdf'
42
+ last_response.status.should eq 503
43
+ last_response.header["Retry-After"].should eq "1"
44
+ end
45
+
46
+ it "should set render to to outpath" do
47
+ get '/test.pdf'
48
+ @middleware.send(:render_to).should match (Regexp.new("^#{options[:out_path]}"))
49
+ end
50
+
51
+ it "should return 504 on timeout" do
52
+ get '/test.pdf'
53
+ sleep 1
54
+ get '/test.pdf'
55
+ last_response.status.should eq 504
56
+ end
57
+
58
+ it "should retry rendering after timeout" do
59
+ get '/test.pdf'
60
+ sleep 1
61
+ get '/test.pdf'
62
+ get '/test.pdf'
63
+ last_response.status.should eq 503
64
+ end
65
+
66
+ it "should return a pdf with 200 after rendering" do
67
+ mock_file = mock(File, :read => "Hello World", :close => true, :mtime => Time.now)
68
+ File.should_receive(:'exists?').and_return true
69
+ File.should_receive(:'size').and_return 1000
70
+ File.should_receive(:'open').and_return mock_file
71
+ File.should_receive(:'new').and_return mock_file
72
+ get '/test.pdf'
73
+ last_response.status.should eq 200
74
+ last_response.body.should eq "Hello World"
75
+ end
76
+
77
+
78
+ end
79
+ context "not matching pdf" do
80
+ it "should skip pdf rendering" do
81
+ get 'http://www.example.org/test'
82
+ last_response.body.should include "Hello world!"
83
+ @middleware.send(:'render_as_pdf?').should be false
84
+ end
85
+ end
86
+ end
87
+
88
+ describe "Conditions" do
89
+ context "only" do
90
+ before { mock_app(options, :only => [%r[^/invoice], %r[^/public]]) }
91
+ it "render pdf for set only option" do
92
+ get '/invoice/test.pdf'
93
+ @middleware.send(:'render_as_pdf?').should be true
94
+ end
95
+
96
+ it "render pdf for set only option" do
97
+ get '/public/test.pdf'
98
+ @middleware.send(:'render_as_pdf?').should be true
99
+ end
100
+
101
+ it "not render pdf for any other path" do
102
+ get '/secret/test.pdf'
103
+ @middleware.send(:'render_as_pdf?').should be false
104
+ end
105
+ end
106
+
107
+ context "except" do
108
+ before { mock_app(options, :except => %w(/secret)) }
109
+ it "render pdf for set only option" do
110
+ get '/invoice/test.pdf'
111
+ @middleware.send(:'render_as_pdf?').should be true
112
+ end
113
+
114
+ it "render pdf for set only option" do
115
+ get '/public/test.pdf'
116
+ @middleware.send(:'render_as_pdf?').should be true
117
+ end
118
+
119
+ it "not render pdf for any other path" do
120
+ get '/secret/test.pdf'
121
+ @middleware.send(:'render_as_pdf?').should be false
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,102 @@
1
+ #encoding: UTF-8
2
+ require 'spec_helper'
3
+
4
+ def valid_pdf(io)
5
+ case io
6
+ when File
7
+ io.read[0...4] == "%PDF"
8
+ when String
9
+ io[0...4] == "%PDF" || File.open(io).read[0...4] == "%PDF"
10
+ end
11
+ end
12
+
13
+ def testfile
14
+ File.expand_path('../test_file.html', __FILE__)
15
+ end
16
+
17
+ Shrimp.configure do |config|
18
+ config.rendering_time = 1000
19
+ end
20
+
21
+ describe Shrimp::Phantom do
22
+ before do
23
+ Shrimp.configure do |config|
24
+ config.rendering_time = 1000
25
+ end
26
+ end
27
+
28
+ it "should initialize attributes" do
29
+ phantom = Shrimp::Phantom.new("file://#{testfile}", { :margin => "2cm" }, { }, "#{Dir.tmpdir}/test.pdf")
30
+ phantom.source.to_s.should eq "file://#{testfile}"
31
+ phantom.options[:margin].should eq "2cm"
32
+ phantom.outfile.should eq "#{Dir.tmpdir}/test.pdf"
33
+ end
34
+
35
+ it "should render a pdf file" do
36
+ #phantom = Shrimp::Phantom.new("file://#{@path}")
37
+ #phantom.to_pdf("#{Dir.tmpdir}/test.pdf").first should eq "#{Dir.tmpdir}/test.pdf"
38
+ end
39
+
40
+ it "should accept a local file url" do
41
+ phantom = Shrimp::Phantom.new("file://#{testfile}")
42
+ phantom.source.should be_url
43
+ end
44
+
45
+ it "should accept a URL as source" do
46
+ phantom = Shrimp::Phantom.new("http://google.com")
47
+ phantom.source.should be_url
48
+ end
49
+
50
+ it "should parse options into a cmd line" do
51
+ phantom = Shrimp::Phantom.new("file://#{testfile}", { :margin => "2cm" }, { }, "#{Dir.tmpdir}/test.pdf")
52
+ phantom.cmd.should include "test.pdf A4 1 2cm portrait"
53
+ phantom.cmd.should include "file://#{testfile}"
54
+ phantom.cmd.should include "lib/shrimp/rasterize.js"
55
+ end
56
+
57
+ context "rendering to a file" do
58
+ before(:all) do
59
+ phantom = Shrimp::Phantom.new("file://#{testfile}", { :margin => "2cm" }, { }, "#{Dir.tmpdir}/test.pdf")
60
+ @result = phantom.to_file
61
+ end
62
+
63
+ it "should return a File" do
64
+ @result.should be_a File
65
+ end
66
+
67
+ it "should be a valid pdf" do
68
+ valid_pdf(@result)
69
+ end
70
+ end
71
+
72
+ context "rendering to a pdf" do
73
+ before(:all) do
74
+ @phantom = Shrimp::Phantom.new("file://#{testfile}", { :margin => "2cm" }, { })
75
+ @result = @phantom.to_pdf("#{Dir.tmpdir}/test.pdf")
76
+ end
77
+
78
+ it "should return a path to pdf" do
79
+ @result.should be_a String
80
+ @result.should eq "#{Dir.tmpdir}/test.pdf"
81
+ end
82
+
83
+ it "should be a valid pdf" do
84
+ valid_pdf(@result)
85
+ end
86
+ end
87
+
88
+ context "rendering to a String" do
89
+ before(:all) do
90
+ phantom = Shrimp::Phantom.new("file://#{testfile}", { :margin => "2cm" }, { })
91
+ @result = phantom.to_string("#{Dir.tmpdir}/test.pdf")
92
+ end
93
+
94
+ it "should return the File IO String" do
95
+ @result.should be_a String
96
+ end
97
+
98
+ it "should be a valid pdf" do
99
+ valid_pdf(@result)
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,15 @@
1
+ #encoding: UTF-8
2
+ require 'spec_helper'
3
+
4
+ describe Shrimp::Source do
5
+ context "url" do
6
+ it "should match file urls" do
7
+ source = Shrimp::Source.new("file:///test/test.html")
8
+ source.should be_url
9
+ end
10
+ it "should match http urls" do
11
+ source = Shrimp::Source.new("http:///test/test.html")
12
+ source.should be_url
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,6 @@
1
+ <html>
2
+ <head></head>
3
+ <body>
4
+ <h1>Hello World!</h1>
5
+ </body>
6
+ </html>
@@ -0,0 +1,11 @@
1
+ require 'URI'
2
+ require 'json'
3
+ require 'rack'
4
+ require 'rack/test'
5
+ require 'shrimp'
6
+
7
+
8
+ RSpec.configure do |config|
9
+ include Rack::Test::Methods
10
+ end
11
+
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: shrimp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Manuel Kniep
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-17 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: json
16
+ requirement: &70106322524180 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70106322524180
25
+ - !ruby/object:Gem::Dependency
26
+ name: rake
27
+ requirement: &70106322523660 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: 0.9.2
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70106322523660
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ requirement: &70106322523140 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: 2.2.0
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70106322523140
47
+ - !ruby/object:Gem::Dependency
48
+ name: rack-test
49
+ requirement: &70106322522660 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: 0.5.6
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70106322522660
58
+ description: html to pdf with phantomjs
59
+ email:
60
+ - manuel@adeven.com
61
+ executables: []
62
+ extensions: []
63
+ extra_rdoc_files: []
64
+ files:
65
+ - .gitignore
66
+ - .travis.yml
67
+ - Gemfile
68
+ - LICENSE.txt
69
+ - README.md
70
+ - Rakefile
71
+ - lib/shrimp.rb
72
+ - lib/shrimp/configuration.rb
73
+ - lib/shrimp/middleware.rb
74
+ - lib/shrimp/phantom.rb
75
+ - lib/shrimp/rasterize.js
76
+ - lib/shrimp/source.rb
77
+ - lib/shrimp/version.rb
78
+ - shrimp.gemspec
79
+ - spec/shrimp/middleware_spec.rb
80
+ - spec/shrimp/phantom_spec.rb
81
+ - spec/shrimp/source_spec.rb
82
+ - spec/shrimp/test_file.html
83
+ - spec/spec_helper.rb
84
+ homepage: http://github.com/adeven/shrimp
85
+ licenses: []
86
+ post_install_message:
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ! '>='
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ requirements:
103
+ - phantomjs, v1.6 or greater
104
+ rubyforge_project:
105
+ rubygems_version: 1.8.10
106
+ signing_key:
107
+ specification_version: 3
108
+ summary: a phantomjs based pdf renderer
109
+ test_files:
110
+ - spec/shrimp/middleware_spec.rb
111
+ - spec/shrimp/phantom_spec.rb
112
+ - spec/shrimp/source_spec.rb
113
+ - spec/shrimp/test_file.html
114
+ - spec/spec_helper.rb