ruby-thumbor 1.3.0 → 4.0.0

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
- SHA1:
3
- metadata.gz: 893a41411617f13eb886aa0623348b8465367c2f
4
- data.tar.gz: 4f987bbd23926f386cc6e360a10156007dafb159
2
+ SHA256:
3
+ metadata.gz: 7f47258a05c0f11cbb6204f06e463613a467ea0dcadfddd971330de2d58202ed
4
+ data.tar.gz: c510bda2d4a88fdadb96ff5d6e8051070bc80834b0b1c838a1091596f7a4db14
5
5
  SHA512:
6
- metadata.gz: a7534f13bd29cda443f7b9338bf70b411ae6f4ce2802928d5c83e603272a1e3531f88ce4dd369adca224b2d5b59c7a0cbf1889e5d6097c2ea94f42993f242d28
7
- data.tar.gz: 3353dcafc369ea2695b3e2883b7b6400bd63f810ead8557018a4ac313c08cb7dafa6fa5f5e1e1b597980d3c5d2ff9494112b53242d1d843b63ebad8712405ef2
6
+ metadata.gz: 3b886a1f96d8a9703c24e338a7e469686a05283098bc7bdc9dc9460fd753a233d0afd78df66c3b9e6f5ad344b8d5f87ef113d1d0299c81ade419910d7ca7da36
7
+ data.tar.gz: 1a1603bc5c9851b4faec33c45852890152d497c01be0c9465c41aa9a0cc3d7b3f17d0aaf156bf4592ecf85b75a30a2d1f839c5600065cb5d96484ef31b636a0f
data/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # ruby-thumbor [![Github Actions](https://github.com/thumbor/ruby-thumbor/actions/workflows/main.yml/badge.svg)](https://github.com/thumbor/ruby-thumbor/actions) [![Gem Version](https://badge.fury.io/rb/ruby-thumbor.svg)](https://rubygems.org/gems/ruby-thumbor) [![Coverage Status](https://coveralls.io/repos/thumbor/ruby-thumbor/badge.svg?branch=master&service=github)](https://coveralls.io/github/thumbor/ruby-thumbor?branch=master)
2
+
3
+ ruby-thumbor is the Ruby client to the thumbor imaging service (http://github.com/thumbor/thumbor).
4
+
5
+ ## Features
6
+ * Generate thumbor encrypted URLs
7
+ * Obtain metadata from image operations in thumbor
8
+
9
+ ## Install
10
+ ``` bash
11
+ gem install ruby-thumbor
12
+ ```
13
+
14
+ ## Using it
15
+ ``` ruby
16
+ require 'ruby-thumbor'
17
+
18
+ image = Thumbor::Cascade.new('my-security-key', 'remote-image.com/path/to/image.jpg')
19
+ image.width(300).height(200).watermark_filter('http://remote-image.com/path/to/image.jpg', 30).generate
20
+
21
+ # url will contain something like:
22
+ # /2913921in321n3k2nj32hjhj3h22/remote-image.com/path/to/image.jpg
23
+
24
+ # Now you just need to concatenate this generated path, with your thumbor server url
25
+ ```
26
+
27
+ ### or
28
+ ``` ruby
29
+ require 'ruby-thumbor'
30
+
31
+ crypto = Thumbor::CryptoURL.new 'my-security-key'
32
+
33
+ url = crypto.generate(width: 200, height: 300, image: 'remote-image.com/path/to/image.jpg')
34
+ ```
35
+
36
+ ## Available Thumbor arguments
37
+ ``` ruby
38
+ meta: bool # flag that indicates that thumbor should return only meta-data on the operations it would otherwise perform;
39
+
40
+ crop: [<int>, <int>, <int>, <int>] # Coordinates for manual cropping. The first item is the two arguments are the coordinates for the left, top point and the last two are the coordinates for the right, bottom point (thus forming the square to crop);
41
+
42
+ width: <int> # the width for the thumbnail;
43
+
44
+ height: <int> # the height for the thumbnail;
45
+
46
+ flip: <bool> # flag that indicates that thumbor should flip horizontally (on the vertical axis) the image;
47
+
48
+ flop: <bool> # flag that indicates that thumbor should flip vertically (on the horizontal axis) the image;
49
+
50
+ halign: :left, :center or :right # horizontal alignment that thumbor should use for cropping;
51
+
52
+ valign: :top, :middle or :bottom # horizontal alignment that thumbor should use for cropping;
53
+
54
+ smart: <bool> # flag that indicates that thumbor should use smart cropping;
55
+
56
+ filters: ['blur(20)', 'watermark(http://my.site.com/img.png,-10,-10,50)'] # array of filters and their arguments
57
+ ```
58
+
59
+ If you need more info on what each option does, check thumbor's documentation at https://thumbor.readthedocs.io/en/latest/index.html.
60
+
61
+ ## License
62
+ (The MIT License)
63
+
64
+ Copyright (c) 2022 Bernardo Heynemann <heynemann@gmail.com>
65
+
66
+ Permission is hereby granted, free of charge, to any person obtaining
67
+ a copy of this software and associated documentation files (the
68
+ 'Software'), to deal in the Software without restriction, including
69
+ without limitation the rights to use, copy, modify, merge, publish,
70
+ distribute, sublicense, and/or sell copies of the Software, and to
71
+ permit persons to whom the Software is furnished to do so, subject to
72
+ the following conditions:
73
+
74
+ The above copyright notice and this permission notice shall be
75
+ included in all copies or substantial portions of the Software.
76
+
77
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
78
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
79
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
80
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
81
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
82
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
83
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/lib/ruby-thumbor.rb CHANGED
@@ -1,15 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thumbor/crypto_url'
2
4
  require 'thumbor/cascade'
3
5
 
4
6
  module Thumbor
5
- @@key = nil
6
-
7
- def self.key=(key)
8
- @@key = key
9
- end
10
-
11
- def self.key
12
- @@key
13
- end
14
7
  end
15
-
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'forwardable'
2
4
  require 'openssl'
3
5
  require 'base64'
@@ -6,20 +8,23 @@ require 'cgi'
6
8
 
7
9
  module Thumbor
8
10
  class Cascade
9
- attr_accessor :image, :old_crypto, :options, :filters
11
+ attr_accessor :image, :crypto, :options, :filters
12
+
13
+ FILTER_REGEX = Regexp.new('^(.+)_filter$')
10
14
 
11
- @available_options = [
12
- :meta, :crop, :center,
13
- :original_width, :original_height,
14
- :width, :height, :flip,
15
- :flop, :halign, :valign,
16
- :smart, :fit_in, :adaptive_fit_in,
17
- :full_fit_in, :adaptive_full_fit_in,
18
- :old, :trim]
15
+ @available_options = %i[
16
+ meta crop center
17
+ original_width original_height
18
+ width height flip
19
+ flop halign valign
20
+ smart fit_in adaptive_fit_in
21
+ full_fit_in adaptive_full_fit_in
22
+ trim debug
23
+ ]
19
24
 
20
25
  extend Forwardable
21
26
 
22
- def_delegators :@old_crypto, :computed_key
27
+ def_delegators :@crypto, :computed_key
23
28
 
24
29
  @available_options.each do |opt|
25
30
  define_method(opt) do |*args|
@@ -29,48 +34,48 @@ module Thumbor
29
34
  end
30
35
  end
31
36
 
32
- def initialize(image=nil)
37
+ def initialize(key = nil, image = nil)
38
+ @key = key
33
39
  @image = image
34
40
  @options = {}
35
41
  @filters = []
36
- @old_crypto = Thumbor::CryptoURL.new Thumbor.key
37
- end
38
-
39
- def url_for
40
- @old_crypto.url_for prepare_options(@options).merge({image: @image, filters: @filters})
42
+ @crypto = Thumbor::CryptoURL.new @key
41
43
  end
42
44
 
43
45
  def generate
44
- @old_crypto.generate prepare_options(@options).merge({image: @image, filters: @filters})
46
+ @crypto.generate prepare_options(@options).merge({ image: @image, filters: @filters })
45
47
  end
46
48
 
47
- def method_missing(m, *args)
48
- if /^(.+)_filter$/.match(m.to_s)
49
- @filters << "#{$1}(#{escape_args(args).join(',')})"
49
+ def method_missing(method, *args)
50
+ if method =~ FILTER_REGEX
51
+ @filters << "#{FILTER_REGEX.match(method)[1]}(#{escape_args(args).join(',')})"
50
52
  self
51
53
  else
52
54
  super
53
55
  end
54
56
  end
55
57
 
58
+ def respond_to_missing?(method, include_private = false)
59
+ method =~ FILTER_REGEX || super
60
+ end
61
+
56
62
  private
57
63
 
58
64
  def escape_args(args)
59
65
  args.map do |arg|
60
- arg = CGI::escape(arg) if arg.is_a? String and arg.match(/^https?:\/\//)
66
+ arg = CGI.escape(arg) if arg.is_a?(String) && arg.match(%r{^https?://})
61
67
  arg
62
68
  end
63
69
  end
64
70
 
65
71
  def prepare_options(options)
66
- options.reduce({}) do |final_options, item|
72
+ options.each_with_object({}) do |item, final_options|
67
73
  value = if item[1].length == 1
68
- item[1].first
69
- else
70
- item[1]
71
- end
74
+ item[1].first
75
+ else
76
+ item[1]
77
+ end
72
78
  final_options[item[0]] = value
73
- final_options
74
79
  end
75
80
  end
76
81
  end
@@ -1,240 +1,192 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'openssl'
2
4
  require 'base64'
3
5
  require 'digest/md5'
4
6
  require 'cgi'
5
7
 
6
8
  module Thumbor
7
- class CryptoURL
8
- attr_accessor :computed_key
9
-
10
- def initialize(key)
11
- Thumbor.key = key
12
- end
13
-
14
- def computed_key
15
- (Thumbor.key * 16)[0..15]
16
- end
9
+ class CryptoURL
10
+ def initialize(key = nil)
11
+ @key = key
12
+ end
17
13
 
18
- def pad(s)
19
- s + ("{" * (16 - s.length % 16))
20
- end
14
+ def calculate_width_and_height(url_parts, options)
15
+ width = options[:width]
16
+ height = options[:height]
17
+
18
+ width *= -1 if width && options[:flip]
19
+ height *= -1 if height && options[:flop]
20
+
21
+ if width || height
22
+ width ||= 0
23
+ height ||= 0
24
+ end
25
+
26
+ has_width = width
27
+ has_height = height
28
+ if options[:flip] && !has_width && !has_height
29
+ width = '-0'
30
+ height = '0' if !has_height && !(options[:flop])
31
+ end
32
+ if options[:flop] && !has_width && !has_height
33
+ height = '-0'
34
+ width = '0' if !has_width && !(options[:flip])
35
+ end
36
+
37
+ url_parts.push("#{width}x#{height}") if width || height
38
+ end
21
39
 
22
- def calculate_width_and_height(url_parts, options)
23
- width = options[:width]
24
- height = options[:height]
25
-
26
- if width and options[:flip]
27
- width = width * -1
28
- end
29
- if height and options[:flop]
30
- height = height * -1
31
- end
32
-
33
- if width or height
34
- width = 0 if not width
35
- height = 0 if not height
36
- end
37
-
38
- has_width = width
39
- has_height = height
40
- if options[:flip] and not has_width and not has_height
41
- width = "-0"
42
- height = '0' if not has_height and not options[:flop]
43
- end
44
- if options[:flop] and not has_width and not has_height
45
- height = "-0"
46
- width = '0' if not has_width and not options[:flip]
47
- end
48
-
49
- if width or height
50
- width = width.to_s
51
- height = height.to_s
52
- url_parts.push(width << 'x' << height)
53
- end
40
+ def calculate_centered_crop(options)
41
+ width = options[:width]
42
+ height = options[:height]
43
+ original_width = options[:original_width]
44
+ original_height = options[:original_height]
45
+ center = options[:center]
46
+
47
+ return unless original_width &&
48
+ original_height &&
49
+ center &&
50
+ (width || height)
51
+
52
+ raise 'center must be an array of x,y' unless center.is_a?(Array) && center.length == 2
53
+
54
+ center_x, center_y = center
55
+ width ||= original_width
56
+ height ||= original_height
57
+ width = width.abs
58
+ height = height.abs
59
+ new_aspect_ratio = width / height.to_f
60
+ original_aspect_ratio = original_width / original_height.to_f
61
+
62
+ crop = nil
63
+ # We're going wider, vertical crop
64
+ if new_aspect_ratio > original_aspect_ratio
65
+ # How tall should we be? because new_aspect_ratio is > original_aspect_ratio we can be sure
66
+ # that cropped_height is always less than original_height (original). This is assumed below.
67
+ cropped_height = (original_width / new_aspect_ratio).round
68
+ # Calculate coordinates around center
69
+ top_crop_point = (center_y - (cropped_height * 0.5)).round
70
+ bottom_crop_point = (center_y + (cropped_height * 0.5)).round
71
+
72
+ # If we've gone above the top of the image, take all from the bottom
73
+ if top_crop_point.negative?
74
+ top_crop_point = 0
75
+ bottom_crop_point = cropped_height
76
+ # If we've gone below the top of the image, take all from the top
77
+ elsif bottom_crop_point > original_height
78
+ top_crop_point = original_height - cropped_height
79
+ bottom_crop_point = original_height
80
+ # Because cropped_height < original_height, top_crop_point and
81
+ # bottom_crop_point will never both be out of bounds
54
82
  end
55
83
 
56
- def calculate_centered_crop(options)
57
- width = options[:width]
58
- height = options[:height]
59
- original_width = options[:original_width]
60
- original_height = options[:original_height]
61
- center = options[:center]
62
-
63
- return unless original_width &&
64
- original_height &&
65
- center &&
66
- (width || height)
67
-
68
- unless center.kind_of?(Array) && center.length == 2
69
- raise 'center must be an array of x,y'
70
- end
71
-
72
- center_x, center_y = center
73
- width ||= original_width
74
- height ||= original_height
75
- width = width.abs
76
- height = height.abs
77
- new_aspect_ratio = width / height.to_f
78
- original_aspect_ratio = original_width/original_height.to_f
79
-
80
- crop = nil
81
- # We're going wider, vertical crop
82
- if new_aspect_ratio > original_aspect_ratio
83
- # How tall should we be? because new_aspect_ratio is > original_aspect_ratio we can be sure
84
- # that cropped_height is always less than original_height (original). This is assumed below.
85
- cropped_height = (original_width / new_aspect_ratio).round
86
- # Calculate coordinates around center
87
- top_crop_point = (center_y - (cropped_height * 0.5)).round
88
- bottom_crop_point = (center_y + (cropped_height * 0.5)).round
89
-
90
- # If we've gone above the top of the image, take all from the bottom
91
- if top_crop_point < 0
92
- top_crop_point = 0
93
- bottom_crop_point = cropped_height
94
- # If we've gone below the top of the image, take all from the top
95
- elsif bottom_crop_point > original_height
96
- top_crop_point = original_height - cropped_height
97
- bottom_crop_point = original_height
98
- # Because cropped_height < original_height, top_crop_point and
99
- # bottom_crop_point will never both be out of bounds
100
- end
101
-
102
- # Put it together
103
- crop = [0, top_crop_point, original_width, bottom_crop_point]
104
- # We're going taller, horizontal crop
105
- elsif new_aspect_ratio < original_aspect_ratio
106
- # How wide should we be? because new_aspect_ratio is < original_aspect_ratio we can be sure
107
- # that cropped_width is always less than original_width (original). This is assumed below.
108
- cropped_width = (original_height * new_aspect_ratio).round
109
- # Calculate coordinates around center
110
- left_crop_point = (center_x - (cropped_width * 0.5)).round
111
- right_crop_point = (center_x + (cropped_width * 0.5)).round
112
-
113
- # If we've gone beyond the left of the image, take all from the right
114
- if left_crop_point < 0
115
- left_crop_point = 0
116
- right_crop_point = cropped_width
117
- # If we've gone beyond the right of the image, take all from the left
118
- elsif right_crop_point > original_width
119
- left_crop_point = original_width - cropped_width
120
- right_crop_point = original_width
121
- # Because cropped_width < original_width, left_crop_point and
122
- # right_crop_point will never both be out of bounds
123
- end
124
-
125
- # Put it together
126
- crop = [left_crop_point, 0, right_crop_point, original_height]
127
- end
128
-
129
- options[:crop] = crop
84
+ # Put it together
85
+ crop = [0, top_crop_point, original_width, bottom_crop_point]
86
+ # We're going taller, horizontal crop
87
+ elsif new_aspect_ratio < original_aspect_ratio
88
+ # How wide should we be? because new_aspect_ratio is < original_aspect_ratio we can be sure
89
+ # that cropped_width is always less than original_width (original). This is assumed below.
90
+ cropped_width = (original_height * new_aspect_ratio).round
91
+ # Calculate coordinates around center
92
+ left_crop_point = (center_x - (cropped_width * 0.5)).round
93
+ right_crop_point = (center_x + (cropped_width * 0.5)).round
94
+
95
+ # If we've gone beyond the left of the image, take all from the right
96
+ if left_crop_point.negative?
97
+ left_crop_point = 0
98
+ right_crop_point = cropped_width
99
+ # If we've gone beyond the right of the image, take all from the left
100
+ elsif right_crop_point > original_width
101
+ left_crop_point = original_width - cropped_width
102
+ right_crop_point = original_width
103
+ # Because cropped_width < original_width, left_crop_point and
104
+ # right_crop_point will never both be out of bounds
130
105
  end
131
106
 
132
- def url_for(options, include_hash = true)
133
- if not options[:image]
134
- raise 'image is a required argument.'
135
- end
136
-
137
- url_parts = Array.new
107
+ # Put it together
108
+ crop = [left_crop_point, 0, right_crop_point, original_height]
109
+ end
138
110
 
139
- if options[:trim]
140
- trim_options = ['trim']
141
- trim_options << options[:trim] unless options[:trim] == true or options[:trim][0] == true
142
- url_parts.push(trim_options.join(':'))
143
- end
111
+ options[:crop] = crop
112
+ end
144
113
 
145
- if options[:meta]
146
- url_parts.push('meta')
147
- end
114
+ def url_for(options)
115
+ raise 'image is a required argument.' unless options[:image]
148
116
 
149
- calculate_centered_crop(options)
117
+ url_parts = []
150
118
 
151
- crop = options[:crop]
152
- if crop
153
- crop_left = crop[0]
154
- crop_top = crop[1]
155
- crop_right = crop[2]
156
- crop_bottom = crop[3]
119
+ url_parts.push('debug') if options[:debug]
157
120
 
158
- if crop_left > 0 or crop_top > 0 or crop_bottom > 0 or crop_right > 0
159
- url_parts.push(crop_left.to_s << 'x' << crop_top.to_s << ':' << crop_right.to_s << 'x' << crop_bottom.to_s)
160
- end
161
- end
121
+ if options[:trim]
122
+ trim_options = ['trim']
123
+ trim_options << options[:trim] unless (options[:trim] == true) || (options[:trim][0] == true)
124
+ url_parts.push(trim_options.join(':'))
125
+ end
162
126
 
163
- [:fit_in, :adaptive_fit_in, :full_fit_in, :adaptive_full_fit_in].each do |fit|
164
- if options[fit]
165
- url_parts.push(fit.to_s.gsub('_','-'))
166
- end
167
- end
127
+ url_parts.push('meta') if options[:meta]
168
128
 
169
- if options.include?(:fit_in) or options.include?(:full_fit_in) and not (options.include?(:width) or options.include?(:height))
170
- raise ArgumentError, 'When using fit-in or full-fit-in, you must specify width and/or height.'
171
- end
129
+ calculate_centered_crop(options)
172
130
 
173
- calculate_width_and_height(url_parts, options)
131
+ crop = options[:crop]
132
+ if crop
133
+ crop_left = crop[0]
134
+ crop_top = crop[1]
135
+ crop_right = crop[2]
136
+ crop_bottom = crop[3]
174
137
 
175
- if options[:halign] and options[:halign] != :center
176
- url_parts.push(options[:halign])
177
- end
138
+ if crop_left.positive? || crop_top.positive? || crop_bottom.positive? || crop_right.positive?
139
+ url_parts.push(crop_left.to_s << 'x' << crop_top.to_s << ':' << crop_right.to_s << 'x' << crop_bottom.to_s)
140
+ end
141
+ end
178
142
 
179
- if options[:valign] and options[:valign] != :middle
180
- url_parts.push(options[:valign])
181
- end
143
+ %i[fit_in adaptive_fit_in full_fit_in adaptive_full_fit_in].each do |fit|
144
+ url_parts.push(fit.to_s.gsub('_', '-')) if options[fit]
145
+ end
182
146
 
183
- if options[:smart]
184
- url_parts.push('smart')
185
- end
147
+ if (options.include?(:fit_in) || options.include?(:full_fit_in)) && !(options.include?(:width) || options.include?(:height))
148
+ raise ArgumentError, 'When using fit-in or full-fit-in, you must specify width and/or height.'
149
+ end
186
150
 
187
- if options[:filters] && !options[:filters].empty?
188
- filter_parts = []
189
- options[:filters].each do |filter|
190
- filter_parts.push(filter)
191
- end
151
+ calculate_width_and_height(url_parts, options)
192
152
 
193
- url_parts.push("filters:#{ filter_parts.join(':') }")
194
- end
153
+ url_parts.push(options[:halign]) if options[:halign] && (options[:halign] != :center)
195
154
 
196
- if include_hash
197
- image_hash = Digest::MD5.hexdigest(options[:image])
198
- url_parts.push(image_hash)
199
- end
155
+ url_parts.push(options[:valign]) if options[:valign] && (options[:valign] != :middle)
200
156
 
201
- return url_parts.join('/')
202
- end
157
+ url_parts.push('smart') if options[:smart]
203
158
 
204
- def url_safe_base64(str)
205
- Base64.encode64(str).gsub('+', '-').gsub('/', '_').gsub!(/[\n]/, '')
159
+ if options[:filters] && !options[:filters].empty?
160
+ filter_parts = []
161
+ options[:filters].each do |filter|
162
+ filter_parts.push(filter)
206
163
  end
207
164
 
208
- def generate_old(options)
209
- url = pad(url_for(options))
210
- cipher = OpenSSL::Cipher::Cipher.new('aes-128-ecb').encrypt
211
- cipher.key = computed_key
212
- encrypted = cipher.update(url)
213
- based = url_safe_base64(encrypted)
165
+ url_parts.push("filters:#{filter_parts.join(':')}")
166
+ end
214
167
 
215
- "/#{based}/#{options[:image]}"
216
- end
168
+ url_parts.join('/')
169
+ end
217
170
 
218
- def generate_new(options)
219
- thumbor_path = ""
171
+ def url_safe_base64(str)
172
+ Base64.encode64(str).gsub('+', '-').gsub('/', '_').gsub!(/\n/, '')
173
+ end
220
174
 
221
- image_options = url_for(options, false)
222
- thumbor_path << image_options + '/' unless image_options.empty?
175
+ def generate(options)
176
+ thumbor_path = []
223
177
 
224
- thumbor_path << options[:image]
178
+ image_options = url_for(options)
179
+ thumbor_path << "#{image_options}/" unless image_options.empty?
225
180
 
226
- if Thumbor.key
227
- signature = url_safe_base64(OpenSSL::HMAC.digest('sha1', Thumbor.key, thumbor_path))
228
- thumbor_path.insert(0, "/#{signature}/")
229
- else
230
- thumbor_path.insert(0, "/unsafe/")
231
- end
232
- thumbor_path
233
- end
181
+ thumbor_path << options[:image]
234
182
 
235
- def generate(options)
236
- return generate_old(options) if options[:old]
237
- generate_new(options)
238
- end
183
+ if @key.nil?
184
+ thumbor_path.insert(0, '/unsafe/')
185
+ else
186
+ signature = url_safe_base64(OpenSSL::HMAC.digest('sha1', @key, thumbor_path.join))
187
+ thumbor_path.insert(0, "/#{signature}/")
188
+ end
189
+ thumbor_path.join
239
190
  end
191
+ end
240
192
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Thumbor
2
- VERSION = '1.3.0'
4
+ VERSION = '4.0.0'
3
5
  end
data/spec/spec_helper.rb CHANGED
@@ -1,14 +1,29 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'simplecov'
4
+ require 'simplecov-lcov'
5
+
6
+ SimpleCov::Formatter::LcovFormatter.config do |config|
7
+ config.report_with_single_file = true
8
+ config.single_report_path = 'coverage/lcov.info'
9
+ end
10
+
11
+ SimpleCov.formatters = SimpleCov::Formatter::MultiFormatter.new(
12
+ [
13
+ SimpleCov::Formatter::HTMLFormatter,
14
+ SimpleCov::Formatter::LcovFormatter
15
+ ]
16
+ )
2
17
 
3
18
  SimpleCov.start do
4
- add_filter '/spec/'
19
+ add_filter '/spec/'
5
20
  end
6
21
 
22
+ SimpleCov.at_exit do
23
+ SimpleCov.result.format!
24
+ end
7
25
 
8
26
  RSpec.configure do |c|
9
- c.filter_run :focus => true
27
+ c.filter_run focus: true
10
28
  c.run_all_when_everything_filtered = true
11
- c.expect_with :rspec do |config|
12
- config.syntax = [:should, :expect]
13
- end
14
29
  end