grover 0.7.4 → 0.8.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.
- 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: []
|