grover 0.4.4 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 21fdefff2c21b5341c4d824e0fd2b1a497f23636
4
- data.tar.gz: 423949075eecb0abd2ba0ac9fa77493fa7131861
3
+ metadata.gz: 2ec8a1908f15407ef3d24ab28a2e211bee743b06
4
+ data.tar.gz: 96e00c18f6ebb1835e768807a6d9275aec88a95f
5
5
  SHA512:
6
- metadata.gz: a5a62083e5deabebf8728033b0519d1ffadc7cd08683a5d1ad85e25fe490d83113d41db34db24bde1f4b89c0c709e0b46e424ee02ca23d96ff82f8fe79911e6d
7
- data.tar.gz: 6fad9c43307155729a8425d1553953ae1ce22e75a2a12f7f6d6aa7c755e18339130205b36e9dbe3f379ac0a0e0b3bb1ff1c2e8b63ead6867669b9c64332cd789
6
+ metadata.gz: 62002773ddebdff4bcf9dcc0bc6a4fde4d68c5922e1ce14c5fb07704f815462fbf04b737a34402de2a240feb79d3eff816eb8d0e2e60ebe04f8b0e0a81259a21
7
+ data.tar.gz: 7016d579dcba337bf2beb558883ca0fec597e9d1b06f7dedecfd86fe63c6572be82a34cc9926a8dace39ab150f725f0a43c7f37f17ad4ab2bec2c3ccabc9bfee
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copied from active support
4
+ # @see active_support/core_ext/object/duplicable.rb
5
+
6
+ require 'active_support_ext/object/duplicable'
7
+
8
+ class Object
9
+ # Returns a deep copy of object if it's duplicable. If it's
10
+ # not duplicable, returns +self+.
11
+ #
12
+ # object = Object.new
13
+ # dup = object.deep_dup
14
+ # dup.instance_variable_set(:@a, 1)
15
+ #
16
+ # object.instance_variable_defined?(:@a) # => false
17
+ # dup.instance_variable_defined?(:@a) # => true
18
+ def deep_dup
19
+ duplicable? ? dup : self
20
+ end
21
+ end
22
+
23
+ class Array
24
+ # Returns a deep copy of array.
25
+ #
26
+ # array = [1, [2, 3]]
27
+ # dup = array.deep_dup
28
+ # dup[1][2] = 4
29
+ #
30
+ # array[1][2] # => nil
31
+ # dup[1][2] # => 4
32
+ def deep_dup
33
+ map(&:deep_dup)
34
+ end
35
+ end
36
+
37
+ class Hash
38
+ # Returns a deep copy of hash.
39
+ #
40
+ # hash = { a: { b: 'b' } }
41
+ # dup = hash.deep_dup
42
+ # dup[:a][:c] = 'c'
43
+ #
44
+ # hash[:a][:c] # => nil
45
+ # dup[:a][:c] # => "c"
46
+ def deep_dup
47
+ hash = dup
48
+ each_pair do |key, value|
49
+ if key.frozen? && key.is_a?(::String)
50
+ hash[key] = value.deep_dup
51
+ else
52
+ hash.delete(key)
53
+ hash[key.deep_dup] = value.deep_dup
54
+ end
55
+ end
56
+ hash
57
+ end
58
+ end
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copied from active support
4
+ # @see active_support/core_ext/object/duplicable.rb
5
+
6
+ #--
7
+ # Most objects are cloneable, but not all. For example you can't dup methods:
8
+ #
9
+ # method(:puts).dup # => TypeError: allocator undefined for Method
10
+ #
11
+ # Classes may signal their instances are not duplicable removing +dup+/+clone+
12
+ # or raising exceptions from them. So, to dup an arbitrary object you normally
13
+ # use an optimistic approach and are ready to catch an exception, say:
14
+ #
15
+ # arbitrary_object.dup rescue object
16
+ #
17
+ # Rails dups objects in a few critical spots where they are not that arbitrary.
18
+ # That rescue is very expensive (like 40 times slower than a predicate), and it
19
+ # is often triggered.
20
+ #
21
+ # That's why we hardcode the following cases and check duplicable? instead of
22
+ # using that rescue idiom.
23
+ #++
24
+ class Object
25
+ # Can you safely dup this object?
26
+ #
27
+ # False for method objects;
28
+ # true otherwise.
29
+ def duplicable?
30
+ true
31
+ end
32
+ end
33
+
34
+ class NilClass
35
+ begin
36
+ nil.dup
37
+ rescue TypeError
38
+ # +nil+ is not duplicable:
39
+ #
40
+ # nil.duplicable? # => false
41
+ # nil.dup # => TypeError: can't dup NilClass
42
+ def duplicable?
43
+ false
44
+ end
45
+ end
46
+ end
47
+
48
+ class FalseClass
49
+ begin
50
+ false.dup
51
+ rescue TypeError
52
+ # +false+ is not duplicable:
53
+ #
54
+ # false.duplicable? # => false
55
+ # false.dup # => TypeError: can't dup FalseClass
56
+ def duplicable?
57
+ false
58
+ end
59
+ end
60
+ end
61
+
62
+ class TrueClass
63
+ begin
64
+ true.dup
65
+ rescue TypeError
66
+ # +true+ is not duplicable:
67
+ #
68
+ # true.duplicable? # => false
69
+ # true.dup # => TypeError: can't dup TrueClass
70
+ def duplicable?
71
+ false
72
+ end
73
+ end
74
+ end
75
+
76
+ class Symbol
77
+ begin
78
+ :symbol.dup # Ruby 2.4.x.
79
+ 'symbol_from_string'.to_sym.dup # Some symbols can't `dup` in Ruby 2.4.0.
80
+ rescue TypeError
81
+ # Symbols are not duplicable:
82
+ #
83
+ # :my_symbol.duplicable? # => false
84
+ # :my_symbol.dup # => TypeError: can't dup Symbol
85
+ def duplicable?
86
+ false
87
+ end
88
+ end
89
+ end
90
+
91
+ class Numeric
92
+ begin
93
+ 1.dup
94
+ rescue TypeError
95
+ # Numbers are not duplicable:
96
+ #
97
+ # 3.duplicable? # => false
98
+ # 3.dup # => TypeError: can't dup Integer
99
+ def duplicable?
100
+ false
101
+ end
102
+ end
103
+ end
104
+
105
+ require 'bigdecimal'
106
+ class BigDecimal
107
+ # BigDecimals are duplicable:
108
+ #
109
+ # BigDecimal("1.2").duplicable? # => true
110
+ # BigDecimal("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)>
111
+ def duplicable?
112
+ true
113
+ end
114
+ end
115
+
116
+ class Method
117
+ # Methods are not duplicable:
118
+ #
119
+ # method(:puts).duplicable? # => false
120
+ # method(:puts).dup # => TypeError: allocator undefined for Method
121
+ def duplicable?
122
+ false
123
+ end
124
+ end
125
+
126
+ class Complex
127
+ begin
128
+ Complex(1).dup
129
+ rescue TypeError
130
+ # Complexes are not duplicable:
131
+ #
132
+ # Complex(1).duplicable? # => false
133
+ # Complex(1).dup # => TypeError: can't copy Complex
134
+ def duplicable?
135
+ false
136
+ end
137
+ end
138
+ end
139
+
140
+ class Rational
141
+ begin
142
+ Rational(1).dup
143
+ rescue TypeError
144
+ # Rationals are not duplicable:
145
+ #
146
+ # Rational(1).duplicable? # => false
147
+ # Rational(1).dup # => TypeError: can't copy Rational
148
+ def duplicable?
149
+ false
150
+ end
151
+ end
152
+ end
data/lib/grover.rb CHANGED
@@ -1,12 +1,14 @@
1
1
  require 'grover/version'
2
2
 
3
3
  require 'grover/utils'
4
+ require 'active_support_ext/object/deep_dup'
5
+
4
6
  require 'grover/html_preprocessor'
5
7
  require 'grover/middleware'
6
8
  require 'grover/configuration'
7
9
 
8
- require 'schmooze'
9
10
  require 'nokogiri'
11
+ require 'schmooze'
10
12
 
11
13
  #
12
14
  # Grover interface for converting HTML to PDF
@@ -58,6 +60,8 @@ class Grover
58
60
  <div class='text right'><span class='pageNumber'></span>/<span class='totalPages'></span></div>
59
61
  HTML
60
62
 
63
+ attr_reader :front_cover_path, :back_cover_path
64
+
61
65
  #
62
66
  # @param [String] url URL of the page to convert
63
67
  # @param [Hash] options Optional parameters to pass to PDF processor
@@ -65,8 +69,11 @@ class Grover
65
69
  #
66
70
  def initialize(url, options = {})
67
71
  @url = url
68
- @options = Grover.configuration.options.merge options
69
- @root_path = @options.delete :root_path
72
+ @options = combine_options options
73
+
74
+ @root_path = @options.delete 'root_path'
75
+ @front_cover_path = @options.delete 'front_cover_path'
76
+ @back_cover_path = @options.delete 'back_cover_path'
70
77
  end
71
78
 
72
79
  #
@@ -76,10 +83,30 @@ class Grover
76
83
  # @return [String] The resulting PDF data
77
84
  #
78
85
  def to_pdf(path = nil)
79
- result = processor.convert_pdf @url, normalized_options(path)
86
+ normalized_options = Utils.normalize_object @options
87
+ normalized_options['path'] = path if path.is_a? ::String
88
+ result = processor.convert_pdf @url, normalized_options
80
89
  result['data'].pack('c*')
81
90
  end
82
91
 
92
+ #
93
+ # Returns whether a front cover (request) path has been specified in the options
94
+ #
95
+ # @return [Boolean] Front cover path is configured
96
+ #
97
+ def show_front_cover?
98
+ front_cover_path.is_a?(::String) && front_cover_path.start_with?('/')
99
+ end
100
+
101
+ #
102
+ # Returns whether a back cover (request) path has been specified in the options
103
+ #
104
+ # @return [Boolean] Back cover path is configured
105
+ #
106
+ def show_back_cover?
107
+ back_cover_path.is_a?(::String) && back_cover_path.start_with?('/')
108
+ end
109
+
83
110
  #
84
111
  # Instance inspection
85
112
  #
@@ -113,11 +140,16 @@ class Grover
113
140
  Processor.new(root_path)
114
141
  end
115
142
 
116
- def base_options
117
- options = {}
118
- @options.each { |k, v| options[k.to_s] = v }
119
- options.merge! meta_options unless url_source?
120
- options
143
+ def combine_options(options)
144
+ combined = Utils.deep_stringify_keys Grover.configuration.options
145
+ Utils.deep_merge! combined, Utils.deep_stringify_keys(options)
146
+ Utils.deep_merge! combined, meta_options unless url_source?
147
+
148
+ fix_templates! combined
149
+ fix_boolean_options! combined
150
+ fix_numeric_options! combined
151
+
152
+ combined
121
153
  end
122
154
 
123
155
  #
@@ -144,17 +176,6 @@ class Grover
144
176
  @url.match(/^http/i)
145
177
  end
146
178
 
147
- def normalized_options(path)
148
- options = base_options
149
-
150
- fix_templates! options
151
- fix_boolean_options! options
152
- fix_numeric_options! options
153
- options['path'] = path if path
154
-
155
- Utils.normalize_object options
156
- end
157
-
158
179
  def fix_templates!(options)
159
180
  display_url = options.delete 'display_url'
160
181
  return unless display_url
@@ -162,7 +183,7 @@ class Grover
162
183
  options['footer_template'] ||= DEFAULT_FOOTER_TEMPLATE
163
184
 
164
185
  %w[header_template footer_template].each do |key|
165
- next unless options[key].is_a? String
186
+ next unless options[key].is_a? ::String
166
187
 
167
188
  options[key] = options[key].gsub(DISPLAY_URL_PLACEHOLDER, display_url)
168
189
  end
@@ -1,3 +1,5 @@
1
+ require 'combine_pdf'
2
+
1
3
  class Grover
2
4
  #
3
5
  # Rack middleware for catching PDF requests and returning the upstream HTML as a PDF
@@ -44,11 +46,36 @@ class Grover
44
46
  end
45
47
 
46
48
  def convert_to_pdf(response)
49
+ grover = create_grover_for_response(response)
50
+ if grover.show_front_cover? || grover.show_back_cover?
51
+ add_cover_content grover
52
+ else
53
+ grover.to_pdf
54
+ end
55
+ end
56
+
57
+ def create_grover_for_response(response)
47
58
  body = response.respond_to?(:body) ? response.body : response.join
48
59
  body = body.join if body.is_a?(Array)
49
60
 
50
61
  body = HTMLPreprocessor.process body, root_url, protocol
51
- Grover.new(body, display_url: request_url).to_pdf
62
+ Grover.new(body, display_url: request_url)
63
+ end
64
+
65
+ def add_cover_content(grover)
66
+ pdf = CombinePDF.parse grover.to_pdf
67
+ pdf >> fetch_cover_pdf(grover.front_cover_path) if grover.show_front_cover?
68
+ pdf << fetch_cover_pdf(grover.back_cover_path) if grover.show_back_cover?
69
+ pdf.to_pdf
70
+ end
71
+
72
+ def fetch_cover_pdf(path)
73
+ temp_env = env.deep_dup
74
+ temp_env['PATH_INFO'], temp_env['QUERY_STRING'] = path.split '?'
75
+ _, _, response = @app.call(temp_env)
76
+ response.close if response.respond_to? :close
77
+ grover = create_grover_for_response response
78
+ CombinePDF.parse grover.to_pdf
52
79
  end
53
80
 
54
81
  def update_headers(headers, body)
data/lib/grover/utils.rb CHANGED
@@ -23,7 +23,8 @@ class Grover
23
23
  #
24
24
  # Remove minimum spaces from the front of all lines within a string
25
25
  #
26
- # Based on active_support/core_ext/string/strip.rb
26
+ # Based on active support
27
+ # @see active_support/core_ext/string/strip.rb
27
28
  #
28
29
  def self.strip_heredoc(string, inline: false)
29
30
  string = string.gsub(/^#{string.scan(/^[ \t]*(?=\S)/).min}/, ''.freeze)
@@ -44,18 +45,54 @@ class Grover
44
45
  end
45
46
 
46
47
  #
47
- # Recursively normalizes hash objects with camelized string keys
48
+ # Deep transform the keys in an object (Hash/Array)
48
49
  #
49
- def self.normalize_object(object)
50
- if object.is_a? Hash
51
- object.each_with_object({}) do |(k, v), acc|
52
- acc[normalize_key(k)] = normalize_object(v)
50
+ # Copied from active support
51
+ # @see active_support/core_ext/hash/keys.rb
52
+ #
53
+ def self.deep_transform_keys_in_object(object, &block)
54
+ case object
55
+ when Hash
56
+ object.each_with_object({}) do |(key, value), result|
57
+ result[yield(key)] = deep_transform_keys_in_object(value, &block)
53
58
  end
59
+ when Array
60
+ object.map { |e| deep_transform_keys_in_object(e, &block) }
54
61
  else
55
62
  object
56
63
  end
57
64
  end
58
65
 
66
+ #
67
+ # Deep transform the keys in the hash to strings
68
+ #
69
+ def self.deep_stringify_keys(hash)
70
+ deep_transform_keys_in_object hash, &:to_s
71
+ end
72
+
73
+ #
74
+ # Deep merge a hash with another hash
75
+ #
76
+ # Based on active support
77
+ # @see active_support/core_ext/hash/deep_merge.rb
78
+ #
79
+ def self.deep_merge!(hash, other_hash)
80
+ hash.merge!(other_hash) do |_, this_val, other_val|
81
+ if this_val.is_a?(Hash) && other_val.is_a?(Hash)
82
+ deep_merge! this_val.dup, other_val
83
+ else
84
+ other_val
85
+ end
86
+ end
87
+ end
88
+
89
+ #
90
+ # Recursively normalizes hash objects with camelized string keys
91
+ #
92
+ def self.normalize_object(object)
93
+ deep_transform_keys_in_object(object) { |k| normalize_key(k) }
94
+ end
95
+
59
96
  #
60
97
  # Normalizes hash keys into camelized strings, including up-casing known acronyms
61
98
  #
@@ -1,3 +1,3 @@
1
1
  class Grover
2
- VERSION = '0.4.4'.freeze
2
+ VERSION = '0.5.1'.freeze
3
3
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grover
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.4
4
+ version: 0.5.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: 2018-09-10 00:00:00.000000000 Z
11
+ date: 2018-09-15 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: combine_pdf
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: nokogiri
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -157,6 +171,8 @@ executables: []
157
171
  extensions: []
158
172
  extra_rdoc_files: []
159
173
  files:
174
+ - lib/active_support_ext/object/deep_dup.rb
175
+ - lib/active_support_ext/object/duplicable.rb
160
176
  - lib/grover.rb
161
177
  - lib/grover/configuration.rb
162
178
  - lib/grover/html_preprocessor.rb