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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b65f94b265a2cff7033dd91d10ca2d1cad51c0b4e447e644d4ccdcbb8df14b30
4
- data.tar.gz: 170d807936eb67a6d805717545548ffc5650a200c6dd2a06cf503411d87d00d4
3
+ metadata.gz: 3a0324b73710864e876853e8de3ffe71974b5544015b1f602d203b4b409cb6fc
4
+ data.tar.gz: 7c27e1acd0b266a69f71c2c2af90d8f0c14ff103ccbc1a28ccf3248854081b56
5
5
  SHA512:
6
- metadata.gz: 841ac6e650d6427cece6e3c1be1d21ed433d42bb3b5c1366efafd4dff29244a9bba2f5ccc606d51888e537bcfb6dc2ddbd43d87a680e73cc300c3ca74ff4aa18
7
- data.tar.gz: e87b7f047c77048d5b8b35dfd38dadff2cb3e1c7d40df699ea09bb51edfd0282ca47119ba5c6e8562b7eef70416098ecc02dfd6a5837818ed673e7172bcafa69
6
+ metadata.gz: df27ae8e1291e8d071afdbc7d6eb249776481c245b8f663261bb199f385e712132b6647d6a3e75c46508aec8dc6ed03a8a1b17b10adaaf08154bf97deef5875f
7
+ data.tar.gz: 211d0e9eb2ac4d033e9a9bae46fc4319cf618e62f6c380e5f600f80968621bef40365bbebadf93b2451a7ea8964b6abda83b11278edc08426b952ec3e5f297db
@@ -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
@@ -12,21 +12,18 @@ class Grover
12
12
  class Middleware
13
13
  def initialize(app)
14
14
  @app = app
15
- @render_pdf = false
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
- @render_pdf = pdf_request?
22
+ identify_request_type
21
23
 
22
- configure_env_for_pdf_request(env) if rendering_pdf?
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 rendering_pdf?
39
- @render_pdf
45
+ def path_matches?(regex)
46
+ !@request.path.match(regex).nil?
40
47
  end
41
48
 
42
- def pdf_request?
43
- !@request.path.match(PDF_REGEX).nil?
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 convert_to_pdf(response)
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 update_headers(headers, body)
84
- # Do not cache PDFs
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'] = 'application/pdf'
122
+ headers['Content-Type'] = content_type
90
123
  end
91
124
 
92
- def configure_env_for_pdf_request(env)
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(PDF_REGEX, '').sub(@request.script_name, '')
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
  #
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Grover
4
- VERSION = '0.7.4'
4
+ VERSION = '0.8.1'
5
5
  end
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
- method :convert_pdf, Utils.strip_heredoc(<<-FUNCTION)
30
- async (url_or_html, options) => {
31
- let browser;
32
- try {
33
- let launchParams = #{launch_params};
34
-
35
- // Configure puppeteer debugging options
36
- const debug = options.debug; delete options.debug;
37
- if (typeof debug === 'object' && !!debug) {
38
- if (debug.headless != undefined) { launchParams.headless = debug.headless; }
39
- if (debug.devtools != undefined) { launchParams.devtools = debug.devtools; }
40
- }
41
-
42
- // Launch the browser and create a page
43
- browser = await puppeteer.launch(launchParams);
44
- const page = await browser.newPage();
45
-
46
- // Set caching flag (if provided)
47
- const cache = options.cache; delete options.cache;
48
- if (cache != undefined) {
49
- await page.setCacheEnabled(cache);
50
- }
51
-
52
- // Setup timeout option (if provided)
53
- let request_options = {};
54
- const timeout = options.timeout; delete options.timeout;
55
- if (timeout != undefined) {
56
- request_options.timeout = timeout;
57
- }
58
-
59
- if (url_or_html.match(/^http/i)) {
60
- // Request is for a URL, so request it
61
- request_options.waitUntil = 'networkidle2';
62
- await page.goto(url_or_html, request_options);
63
- } else {
64
- // Request is some HTML content. Use request interception to assign the body
65
- request_options.waitUntil = 'networkidle0';
66
- await page.setRequestInterception(true);
67
- page.once('request', request => {
68
- request.respond({ body: url_or_html });
69
- // Reset the request interception
70
- // (we only want to intercept the first request - ie our HTML)
71
- page.on('request', request => request.continue());
72
- });
73
- const displayUrl = options.displayUrl; delete options.displayUrl;
74
- await page.goto(displayUrl || 'http://example.com', request_options);
75
- }
76
-
77
- // If specified, emulate the media type
78
- const emulateMedia = options.emulateMedia; delete options.emulateMedia;
79
- if (emulateMedia != undefined) {
80
- await page.emulateMedia(emulateMedia);
81
- }
82
-
83
- // If we're running puppeteer in headless mode, return the converted PDF
84
- if (debug == undefined || (typeof debug === 'object' && (debug.headless == undefined || debug.headless))) {
85
- return await page.pdf(options);
86
- }
87
- } finally {
88
- if (browser) {
89
- await browser.close();
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
- FUNCTION
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 = Utils.strip_heredoc(<<-HTML).freeze
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
- normalized_options = Utils.normalize_object @options
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('c*')
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.7.4
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-09 00:00:00.000000000 Z
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.1'
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.1'
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.7'
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.7'
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.53'
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.53'
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.28'
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.28'
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.15'
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.15'
153
- description: Transform HTML into PDFs using Google Puppeteer/Chromium
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 PDFs wrapper the NodeJS Google Puppeteer
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: []