shrimp 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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