grover 0.7.4 → 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/grover/configuration.rb +6 -1
- data/lib/grover/middleware.rb +62 -19
- data/lib/grover/utils.rb +0 -11
- data/lib/grover/version.rb +1 -1
- data/lib/grover.rb +119 -68
- metadata +29 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3a0324b73710864e876853e8de3ffe71974b5544015b1f602d203b4b409cb6fc
|
4
|
+
data.tar.gz: 7c27e1acd0b266a69f71c2c2af90d8f0c14ff103ccbc1a28ccf3248854081b56
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: df27ae8e1291e8d071afdbc7d6eb249776481c245b8f663261bb199f385e712132b6647d6a3e75c46508aec8dc6ed03a8a1b17b10adaaf08154bf97deef5875f
|
7
|
+
data.tar.gz: 211d0e9eb2ac4d033e9a9bae46fc4319cf618e62f6c380e5f600f80968621bef40365bbebadf93b2451a7ea8964b6abda83b11278edc08426b952ec3e5f297db
|
data/lib/grover/configuration.rb
CHANGED
@@ -5,11 +5,16 @@ class Grover
|
|
5
5
|
# Configuration of the options for Grover HTML to PDF conversion
|
6
6
|
#
|
7
7
|
class Configuration
|
8
|
-
attr_accessor :options, :meta_tag_prefix
|
8
|
+
attr_accessor :options, :meta_tag_prefix, :ignore_path,
|
9
|
+
:use_pdf_middleware, :use_png_middleware, :use_jpeg_middleware
|
9
10
|
|
10
11
|
def initialize
|
11
12
|
@options = {}
|
12
13
|
@meta_tag_prefix = 'grover-'
|
14
|
+
@ignore_path = nil
|
15
|
+
@use_pdf_middleware = true
|
16
|
+
@use_png_middleware = false
|
17
|
+
@use_jpeg_middleware = false
|
13
18
|
end
|
14
19
|
end
|
15
20
|
end
|
data/lib/grover/middleware.rb
CHANGED
@@ -12,21 +12,18 @@ class Grover
|
|
12
12
|
class Middleware
|
13
13
|
def initialize(app)
|
14
14
|
@app = app
|
15
|
-
@
|
15
|
+
@pdf_request = false
|
16
|
+
@png_request = false
|
17
|
+
@jpeg_request = false
|
16
18
|
end
|
17
19
|
|
18
20
|
def call(env)
|
19
21
|
@request = Rack::Request.new(env)
|
20
|
-
|
22
|
+
identify_request_type
|
21
23
|
|
22
|
-
|
24
|
+
configure_env_for_grover_request(env) if grover_request?
|
23
25
|
status, headers, response = @app.call(env)
|
24
|
-
|
25
|
-
if rendering_pdf? && html_content?(headers)
|
26
|
-
pdf = convert_to_pdf response
|
27
|
-
response = [pdf]
|
28
|
-
update_headers headers, pdf
|
29
|
-
end
|
26
|
+
response = update_response response, headers if grover_request? && html_content?(headers)
|
30
27
|
|
31
28
|
[status, headers, response]
|
32
29
|
end
|
@@ -34,21 +31,57 @@ class Grover
|
|
34
31
|
private
|
35
32
|
|
36
33
|
PDF_REGEX = /\.pdf$/i.freeze
|
34
|
+
PNG_REGEX = /\.png$/i.freeze
|
35
|
+
JPEG_REGEX = /\.jpe?g$/i.freeze
|
36
|
+
|
37
|
+
attr_reader :pdf_request, :png_request, :jpeg_request
|
38
|
+
|
39
|
+
def identify_request_type
|
40
|
+
@pdf_request = Grover.configuration.use_pdf_middleware && path_matches?(PDF_REGEX)
|
41
|
+
@png_request = Grover.configuration.use_png_middleware && path_matches?(PNG_REGEX)
|
42
|
+
@jpeg_request = Grover.configuration.use_jpeg_middleware && path_matches?(JPEG_REGEX)
|
43
|
+
end
|
37
44
|
|
38
|
-
def
|
39
|
-
|
45
|
+
def path_matches?(regex)
|
46
|
+
!@request.path.match(regex).nil?
|
40
47
|
end
|
41
48
|
|
42
|
-
def
|
43
|
-
|
49
|
+
def grover_request?
|
50
|
+
(pdf_request || png_request || jpeg_request) && !ignore_request?
|
51
|
+
end
|
52
|
+
|
53
|
+
def ignore_request?
|
54
|
+
ignore_path = Grover.configuration.ignore_path
|
55
|
+
case ignore_path
|
56
|
+
when String then @request.path.start_with? ignore_path
|
57
|
+
when Regexp then !ignore_path.match(@request.path).nil?
|
58
|
+
when Proc then ignore_path.call @request.path
|
59
|
+
end
|
44
60
|
end
|
45
61
|
|
46
62
|
def html_content?(headers)
|
47
63
|
headers['Content-Type'] =~ %r{text/html|application/xhtml\+xml}
|
48
64
|
end
|
49
65
|
|
50
|
-
def
|
66
|
+
def update_response(response, headers)
|
67
|
+
body, content_type = convert_response response
|
68
|
+
assign_headers headers, body, content_type
|
69
|
+
[body]
|
70
|
+
end
|
71
|
+
|
72
|
+
def convert_response(response)
|
51
73
|
grover = create_grover_for_response(response)
|
74
|
+
|
75
|
+
if pdf_request
|
76
|
+
[convert_to_pdf(grover), 'application/pdf']
|
77
|
+
elsif png_request
|
78
|
+
[grover.to_png, 'image/png']
|
79
|
+
elsif jpeg_request
|
80
|
+
[grover.to_jpeg, 'image/jpeg']
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def convert_to_pdf(grover)
|
52
85
|
if grover.show_front_cover? || grover.show_back_cover?
|
53
86
|
add_cover_content grover
|
54
87
|
else
|
@@ -80,16 +113,16 @@ class Grover
|
|
80
113
|
CombinePDF.parse grover.to_pdf
|
81
114
|
end
|
82
115
|
|
83
|
-
def
|
84
|
-
# Do not cache
|
116
|
+
def assign_headers(headers, body, content_type)
|
117
|
+
# Do not cache results
|
85
118
|
headers.delete 'ETag'
|
86
119
|
headers.delete 'Cache-Control'
|
87
120
|
|
88
121
|
headers['Content-Length'] = (body.respond_to?(:bytesize) ? body.bytesize : body.size).to_s
|
89
|
-
headers['Content-Type'] =
|
122
|
+
headers['Content-Type'] = content_type
|
90
123
|
end
|
91
124
|
|
92
|
-
def
|
125
|
+
def configure_env_for_grover_request(env)
|
93
126
|
env['PATH_INFO'] = env['REQUEST_URI'] = path_without_extension
|
94
127
|
env['HTTP_ACCEPT'] = concat(env['HTTP_ACCEPT'], Rack::Mime.mime_type('.html'))
|
95
128
|
env['Rack-Middleware-Grover'] = 'true'
|
@@ -108,7 +141,17 @@ class Grover
|
|
108
141
|
end
|
109
142
|
|
110
143
|
def path_without_extension
|
111
|
-
@request.path.sub(
|
144
|
+
@request.path.sub(request_regex, '').sub(@request.script_name, '')
|
145
|
+
end
|
146
|
+
|
147
|
+
def request_regex
|
148
|
+
if pdf_request
|
149
|
+
PDF_REGEX
|
150
|
+
elsif png_request
|
151
|
+
PNG_REGEX
|
152
|
+
elsif jpeg_request
|
153
|
+
JPEG_REGEX
|
154
|
+
end
|
112
155
|
end
|
113
156
|
|
114
157
|
def request_url
|
data/lib/grover/utils.rb
CHANGED
@@ -22,17 +22,6 @@ class Grover
|
|
22
22
|
gsub(/[[:space:]]+/, ' ')
|
23
23
|
end
|
24
24
|
|
25
|
-
#
|
26
|
-
# Remove minimum spaces from the front of all lines within a string
|
27
|
-
#
|
28
|
-
# Based on active support
|
29
|
-
# @see active_support/core_ext/string/strip.rb
|
30
|
-
#
|
31
|
-
def self.strip_heredoc(string, inline: false)
|
32
|
-
string = string.gsub(/^#{string.scan(/^[ \t]*(?=\S)/).min}/, '')
|
33
|
-
inline ? string.delete("\n") : string
|
34
|
-
end
|
35
|
-
|
36
25
|
#
|
37
26
|
# Assign value to a hash using an array of keys to traverse
|
38
27
|
#
|
data/lib/grover/version.rb
CHANGED
data/lib/grover.rb
CHANGED
@@ -26,76 +26,87 @@ class Grover
|
|
26
26
|
ENV['GROVER_NO_SANDBOX'] == 'true' ? "{args: ['--no-sandbox', '--disable-setuid-sandbox']}" : '{}'
|
27
27
|
end
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
if (debug
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
//
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
29
|
+
def self.convert_function(convert_action)
|
30
|
+
<<~FUNCTION
|
31
|
+
async (url_or_html, options) => {
|
32
|
+
let browser;
|
33
|
+
try {
|
34
|
+
let launchParams = #{launch_params};
|
35
|
+
|
36
|
+
// Configure puppeteer debugging options
|
37
|
+
const debug = options.debug; delete options.debug;
|
38
|
+
if (typeof debug === 'object' && !!debug) {
|
39
|
+
if (debug.headless != undefined) { launchParams.headless = debug.headless; }
|
40
|
+
if (debug.devtools != undefined) { launchParams.devtools = debug.devtools; }
|
41
|
+
}
|
42
|
+
|
43
|
+
// Launch the browser and create a page
|
44
|
+
browser = await puppeteer.launch(launchParams);
|
45
|
+
const page = await browser.newPage();
|
46
|
+
|
47
|
+
// Set caching flag (if provided)
|
48
|
+
const cache = options.cache; delete options.cache;
|
49
|
+
if (cache != undefined) {
|
50
|
+
await page.setCacheEnabled(cache);
|
51
|
+
}
|
52
|
+
|
53
|
+
// Setup timeout option (if provided)
|
54
|
+
let request_options = {};
|
55
|
+
const timeout = options.timeout; delete options.timeout;
|
56
|
+
if (timeout != undefined) {
|
57
|
+
request_options.timeout = timeout;
|
58
|
+
}
|
59
|
+
|
60
|
+
// Setup viewport options (if provided)
|
61
|
+
const viewport = options.viewport; delete options.viewport;
|
62
|
+
if (viewport != undefined) {
|
63
|
+
await page.setViewport(viewport);
|
64
|
+
}
|
65
|
+
|
66
|
+
if (url_or_html.match(/^http/i)) {
|
67
|
+
// Request is for a URL, so request it
|
68
|
+
request_options.waitUntil = 'networkidle2';
|
69
|
+
await page.goto(url_or_html, request_options);
|
70
|
+
} else {
|
71
|
+
// Request is some HTML content. Use request interception to assign the body
|
72
|
+
request_options.waitUntil = 'networkidle0';
|
73
|
+
await page.setRequestInterception(true);
|
74
|
+
page.once('request', request => {
|
75
|
+
request.respond({ body: url_or_html });
|
76
|
+
// Reset the request interception
|
77
|
+
// (we only want to intercept the first request - ie our HTML)
|
78
|
+
page.on('request', request => request.continue());
|
79
|
+
});
|
80
|
+
const displayUrl = options.displayUrl; delete options.displayUrl;
|
81
|
+
await page.goto(displayUrl || 'http://example.com', request_options);
|
82
|
+
}
|
83
|
+
|
84
|
+
// If specified, emulate the media type
|
85
|
+
const emulateMedia = options.emulateMedia; delete options.emulateMedia;
|
86
|
+
if (emulateMedia != undefined) {
|
87
|
+
await page.emulateMedia(emulateMedia);
|
88
|
+
}
|
89
|
+
|
90
|
+
// If we're running puppeteer in headless mode, return the converted PDF
|
91
|
+
if (debug == undefined || (typeof debug === 'object' && (debug.headless == undefined || debug.headless))) {
|
92
|
+
return await page.#{convert_action}(options);
|
93
|
+
}
|
94
|
+
} finally {
|
95
|
+
if (browser) {
|
96
|
+
await browser.close();
|
97
|
+
}
|
90
98
|
}
|
91
99
|
}
|
92
|
-
|
93
|
-
|
100
|
+
FUNCTION
|
101
|
+
end
|
102
|
+
|
103
|
+
method :convert_pdf, convert_function('pdf')
|
104
|
+
method :convert_screenshot, convert_function('screenshot')
|
94
105
|
end
|
95
106
|
private_constant :Processor
|
96
107
|
|
97
108
|
DEFAULT_HEADER_TEMPLATE = "<div class='date text left'></div><div class='title text center'></div>"
|
98
|
-
DEFAULT_FOOTER_TEMPLATE =
|
109
|
+
DEFAULT_FOOTER_TEMPLATE = <<~HTML
|
99
110
|
<div class='url text left grow'></div>
|
100
111
|
<div class='text right'><span class='pageNumber'></span>/<span class='totalPages'></span></div>
|
101
112
|
HTML
|
@@ -123,12 +134,46 @@ class Grover
|
|
123
134
|
# @return [String] The resulting PDF data
|
124
135
|
#
|
125
136
|
def to_pdf(path = nil)
|
126
|
-
|
127
|
-
normalized_options['path'] = path if path.is_a? ::String
|
128
|
-
result = processor.convert_pdf @url, normalized_options
|
137
|
+
result = processor.convert_pdf @url, normalized_options(path: path)
|
129
138
|
return unless result
|
130
139
|
|
131
|
-
result['data'].pack('
|
140
|
+
result['data'].pack('C*')
|
141
|
+
end
|
142
|
+
|
143
|
+
#
|
144
|
+
# Request URL with provided options and create screenshot
|
145
|
+
#
|
146
|
+
# @param [String] path Optional path to write the screenshot to
|
147
|
+
# @param [String] format Optional format of the screenshot
|
148
|
+
# @return [String] The resulting image data
|
149
|
+
#
|
150
|
+
def screenshot(path: nil, format: nil)
|
151
|
+
options = normalized_options(path: path)
|
152
|
+
options['type'] = format if format.is_a? ::String
|
153
|
+
result = processor.convert_screenshot @url, options
|
154
|
+
return unless result
|
155
|
+
|
156
|
+
result['data'].pack('C*')
|
157
|
+
end
|
158
|
+
|
159
|
+
#
|
160
|
+
# Request URL with provided options and create PNG
|
161
|
+
#
|
162
|
+
# @param [String] path Optional path to write the screenshot to
|
163
|
+
# @return [String] The resulting PNG data
|
164
|
+
#
|
165
|
+
def to_png(path = nil)
|
166
|
+
screenshot(path: path, format: 'png')
|
167
|
+
end
|
168
|
+
|
169
|
+
#
|
170
|
+
# Request URL with provided options and create JPEG
|
171
|
+
#
|
172
|
+
# @param [String] path Optional path to write the screenshot to
|
173
|
+
# @return [String] The resulting JPEG data
|
174
|
+
#
|
175
|
+
def to_jpeg(path = nil)
|
176
|
+
screenshot(path: path, format: 'jpeg')
|
132
177
|
end
|
133
178
|
|
134
179
|
#
|
@@ -232,4 +277,10 @@ class Grover
|
|
232
277
|
|
233
278
|
options['scale'] = options['scale'].to_f
|
234
279
|
end
|
280
|
+
|
281
|
+
def normalized_options(path:)
|
282
|
+
normalized_options = Utils.normalize_object @options
|
283
|
+
normalized_options['path'] = path if path.is_a? ::String
|
284
|
+
normalized_options
|
285
|
+
end
|
235
286
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: grover
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Bromwich
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-07-
|
11
|
+
date: 2019-07-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: combine_pdf
|
@@ -52,20 +52,34 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0.2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: mini_magick
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '4.9'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '4.9'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: pdf-reader
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
58
72
|
requirements:
|
59
73
|
- - "~>"
|
60
74
|
- !ruby/object:Gem::Version
|
61
|
-
version: '2.
|
75
|
+
version: '2.2'
|
62
76
|
type: :development
|
63
77
|
prerelease: false
|
64
78
|
version_requirements: !ruby/object:Gem::Requirement
|
65
79
|
requirements:
|
66
80
|
- - "~>"
|
67
81
|
- !ruby/object:Gem::Version
|
68
|
-
version: '2.
|
82
|
+
version: '2.2'
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: rack-test
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -100,57 +114,57 @@ dependencies:
|
|
100
114
|
requirements:
|
101
115
|
- - "~>"
|
102
116
|
- !ruby/object:Gem::Version
|
103
|
-
version: '3.
|
117
|
+
version: '3.8'
|
104
118
|
type: :development
|
105
119
|
prerelease: false
|
106
120
|
version_requirements: !ruby/object:Gem::Requirement
|
107
121
|
requirements:
|
108
122
|
- - "~>"
|
109
123
|
- !ruby/object:Gem::Version
|
110
|
-
version: '3.
|
124
|
+
version: '3.8'
|
111
125
|
- !ruby/object:Gem::Dependency
|
112
126
|
name: rubocop
|
113
127
|
requirement: !ruby/object:Gem::Requirement
|
114
128
|
requirements:
|
115
129
|
- - "~>"
|
116
130
|
- !ruby/object:Gem::Version
|
117
|
-
version: '0.
|
131
|
+
version: '0.72'
|
118
132
|
type: :development
|
119
133
|
prerelease: false
|
120
134
|
version_requirements: !ruby/object:Gem::Requirement
|
121
135
|
requirements:
|
122
136
|
- - "~>"
|
123
137
|
- !ruby/object:Gem::Version
|
124
|
-
version: '0.
|
138
|
+
version: '0.72'
|
125
139
|
- !ruby/object:Gem::Dependency
|
126
140
|
name: rubocop-rspec
|
127
141
|
requirement: !ruby/object:Gem::Requirement
|
128
142
|
requirements:
|
129
143
|
- - "~>"
|
130
144
|
- !ruby/object:Gem::Version
|
131
|
-
version: '1.
|
145
|
+
version: '1.33'
|
132
146
|
type: :development
|
133
147
|
prerelease: false
|
134
148
|
version_requirements: !ruby/object:Gem::Requirement
|
135
149
|
requirements:
|
136
150
|
- - "~>"
|
137
151
|
- !ruby/object:Gem::Version
|
138
|
-
version: '1.
|
152
|
+
version: '1.33'
|
139
153
|
- !ruby/object:Gem::Dependency
|
140
154
|
name: simplecov
|
141
155
|
requirement: !ruby/object:Gem::Requirement
|
142
156
|
requirements:
|
143
157
|
- - "~>"
|
144
158
|
- !ruby/object:Gem::Version
|
145
|
-
version: '0.
|
159
|
+
version: '0.17'
|
146
160
|
type: :development
|
147
161
|
prerelease: false
|
148
162
|
version_requirements: !ruby/object:Gem::Requirement
|
149
163
|
requirements:
|
150
164
|
- - "~>"
|
151
165
|
- !ruby/object:Gem::Version
|
152
|
-
version: '0.
|
153
|
-
description: Transform HTML into
|
166
|
+
version: '0.17'
|
167
|
+
description: Transform HTML into PDF/PNG/JPEG using Google Puppeteer/Chromium
|
154
168
|
email:
|
155
169
|
- abromwich@studiosity.com
|
156
170
|
executables: []
|
@@ -187,6 +201,6 @@ requirements: []
|
|
187
201
|
rubygems_version: 3.0.3
|
188
202
|
signing_key:
|
189
203
|
specification_version: 4
|
190
|
-
summary: A Ruby gem to transform HTML into
|
191
|
-
driver for Chromium
|
204
|
+
summary: A Ruby gem to transform HTML into PDF, PNG or JPEG by wrapping the NodeJS
|
205
|
+
Google Puppeteer driver for Chromium
|
192
206
|
test_files: []
|