imgix 3.1.0 → 3.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -13,6 +13,13 @@ Gem::Specification.new do |spec|
13
13
  spec.homepage = 'https://github.com/imgix/imgix-rb'
14
14
  spec.license = 'MIT'
15
15
 
16
+ spec.metadata = {
17
+ 'bug_tracker_uri' => 'https://github.com/imgix/imgix-rb/issues',
18
+ 'changelog_uri' => 'https://github.com/imgix/imgix-rb/blob/main/CHANGELOG.md',
19
+ 'documentation_uri' => "https://www.rubydoc.info/gems/imgix/#{spec.version}",
20
+ 'source_code_uri' => "https://github.com/imgix/imgix-rb/tree/#{spec.version}"
21
+ }
22
+
16
23
  spec.files = `git ls-files`.split($/)
17
24
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
25
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
@@ -6,22 +6,46 @@ require 'imgix/path'
6
6
 
7
7
  module Imgix
8
8
  # regex pattern used to determine if a domain is valid
9
- DOMAIN_REGEX = /^(?:[a-z\d\-_]{1,62}\.){0,125}(?:[a-z\d](?:\-(?=\-*[a-z\d])|[a-z]|\d){0,62}\.)[a-z\d]{1,63}$/i
9
+ DOMAIN_REGEX = /^(?:[a-z\d\-_]{1,62}\.){0,125}(?:[a-z\d](?:\-(?=\-*[a-z\d])|[a-z]|\d){0,62}\.)[a-z\d]{1,63}$/i.freeze
10
+
11
+ # determines the growth rate when building out srcset pair widths
12
+ DEFAULT_WIDTH_TOLERANCE = 0.08
13
+
14
+ # the default minimum srcset width
15
+ MIN_WIDTH = 100
16
+
17
+ # the default maximum srcset width, also the max width supported by imgix
18
+ MAX_WIDTH = 8192
10
19
 
11
20
  # returns an array of width values used during scrset generation
12
- TARGET_WIDTHS = lambda {
13
- increment_percentage = 8
14
- max_size = 8192
21
+ TARGET_WIDTHS = lambda { |tolerance, min, max|
22
+ increment_percentage = tolerance || DEFAULT_WIDTH_TOLERANCE
23
+
24
+ unless increment_percentage.is_a?(Numeric) && increment_percentage > 0
25
+ width_increment_error = 'error: `width_tolerance` must be a positive `Numeric` value'
26
+ raise ArgumentError, width_increment_error
27
+ end
28
+
29
+ max_size = max || MAX_WIDTH
15
30
  resolutions = []
16
- prev = 100
31
+ prev = min || MIN_WIDTH
17
32
 
18
- while(prev <= max_size)
33
+ while prev < max_size
19
34
  # ensures that each width is even
20
- resolutions.push((2 * (prev / 2).round))
21
- prev *= 1 + ((increment_percentage.to_f) / 100) * 2
35
+ resolutions.push(prev.round)
36
+ prev *= 1 + (increment_percentage * 2)
22
37
  end
23
38
 
24
39
  resolutions.push(max_size)
25
40
  return resolutions
26
41
  }
42
+
43
+ # hash of default quality parameter values mapped by each dpr srcset entry
44
+ DPR_QUALITY = {
45
+ 1 => 75,
46
+ 2 => 50,
47
+ 3 => 35,
48
+ 4 => 23,
49
+ 5 => 20
50
+ }.freeze
27
51
  end
@@ -7,54 +7,86 @@ require 'uri'
7
7
 
8
8
  module Imgix
9
9
  class Client
10
- DEFAULTS = { use_https: true }
10
+ DEFAULTS = { use_https: true }.freeze
11
11
 
12
12
  def initialize(options = {})
13
13
  options = DEFAULTS.merge(options)
14
+ host, domain = options[:host], options[:domain]
15
+
16
+ host_deprecated = "Warning: The identifier `host' has been deprecated and " \
17
+ "will\nappear as `domain' in the next major version, e.g. " \
18
+ "`@host'\nbecomes `@domain', `options[:host]' becomes " \
19
+ "`options[:domain]'.\n"
20
+
21
+ if host
22
+ warn host_deprecated
23
+ @host = host
24
+ elsif domain
25
+ @host = domain
26
+ else
27
+ @host = host
28
+ end
14
29
 
15
- @host = options[:host]
16
30
  validate_host!
31
+
17
32
  @secure_url_token = options[:secure_url_token]
18
33
  @api_key = options[:api_key]
19
34
  @use_https = options[:use_https]
20
35
  @include_library_param = options.fetch(:include_library_param, true)
21
- @library = options.fetch(:library_param, "rb")
36
+ @library = options.fetch(:library_param, 'rb')
22
37
  @version = options.fetch(:library_version, Imgix::VERSION)
23
38
  end
24
39
 
25
40
  def path(path)
26
- p = Path.new(prefix(path), @secure_url_token, path)
41
+ p = Path.new(new_prefix, @secure_url_token, path)
27
42
  p.ixlib("#{@library}-#{@version}") if @include_library_param
28
43
  p
29
44
  end
30
45
 
31
46
  def purge(path)
32
- raise "Authentication token required" unless !!(@api_key)
33
- url = prefix(path)+path
47
+ token_error = 'Authentication token required'
48
+ raise token_error if @api_key.nil?
49
+
50
+ url = new_prefix + path
34
51
  uri = URI.parse('https://api.imgix.com/v2/image/purger')
35
- req = Net::HTTP::Post.new(uri.path, {"User-Agent" => "imgix #{@library}-#{@version}"})
52
+
53
+ user_agent = { 'User-Agent' => "imgix #{@library}-#{@version}" }
54
+
55
+ req = Net::HTTP::Post.new(uri.path, user_agent)
36
56
  req.basic_auth @api_key, ''
37
- req.set_form_data({'url' => url})
57
+ req.set_form_data({ url: url })
58
+
38
59
  sock = Net::HTTP.new(uri.host, uri.port)
39
60
  sock.use_ssl = true
40
- res = sock.start {|http| http.request(req) }
61
+ res = sock.start { |http| http.request(req) }
62
+
41
63
  res
42
64
  end
43
65
 
44
66
  def prefix(path)
67
+ msg = "Warning: `Client::prefix' will take zero arguments " \
68
+ "in the next major version.\n"
69
+ warn msg
70
+ new_prefix
71
+ end
72
+
73
+ def new_prefix
45
74
  "#{@use_https ? 'https' : 'http'}://#{@host}"
46
75
  end
47
76
 
48
77
  private
49
78
 
50
79
  def validate_host!
51
- unless @host != nil
52
- raise ArgumentError, "The :host option must be specified"
53
- end
54
- if @host.match(DOMAIN_REGEX) == nil
55
- raise ArgumentError, "Domains must be passed in as fully-qualified domain names and should not include a protocol or any path element, i.e. \"example.imgix.net\"."
80
+ host_error = 'The :host option must be specified'
81
+ raise ArgumentError, host_error if @host.nil?
82
+
83
+ domain_error = 'Domains must be passed in as fully-qualified'\
84
+ 'domain names and should not include a protocol'\
85
+ 'or any path element, i.e. "example.imgix.net"'\
86
+
87
+ if @host.match(DOMAIN_REGEX).nil?
88
+ raise ArgumentError, domain_error
56
89
  end
57
90
  end
58
-
59
91
  end
60
92
  end
@@ -3,6 +3,7 @@
3
3
  module Imgix
4
4
  module ParamHelpers
5
5
  def rect(position)
6
+ warn "Warning: `ParamHelpers.rect` has been deprecated and will be removed in the next major version.\n"
6
7
  @options[:rect] = position and return self if position.is_a?(String)
7
8
 
8
9
  @options[:rect] = [
@@ -10,26 +10,26 @@ module Imgix
10
10
  include ParamHelpers
11
11
 
12
12
  ALIASES = {
13
- width: :w,
14
- height: :h,
15
- rotation: :rot,
13
+ width: :w,
14
+ height: :h,
15
+ rotation: :rot,
16
16
  noise_reduction: :nr,
17
- sharpness: :sharp,
18
- exposure: :exp,
19
- vibrance: :vib,
20
- saturation: :sat,
21
- brightness: :bri,
22
- contrast: :con,
23
- highlight: :high,
24
- shadow: :shad,
25
- gamma: :gam,
26
- pixelate: :px,
27
- halftone: :htn,
28
- watermark: :mark,
29
- text: :txt,
30
- format: :fm,
31
- quality: :q
32
- }
17
+ sharpness: :sharp,
18
+ exposure: :exp,
19
+ vibrance: :vib,
20
+ saturation: :sat,
21
+ brightness: :bri,
22
+ contrast: :con,
23
+ highlight: :high,
24
+ shadow: :shad,
25
+ gamma: :gam,
26
+ pixelate: :px,
27
+ halftone: :htn,
28
+ watermark: :mark,
29
+ text: :txt,
30
+ format: :fm,
31
+ quality: :q
32
+ }.freeze
33
33
 
34
34
  def initialize(prefix, secure_url_token, path = '/')
35
35
  @prefix = prefix
@@ -39,7 +39,7 @@ module Imgix
39
39
 
40
40
  @path = CGI.escape(@path) if /^https?/ =~ @path
41
41
  @path = "/#{@path}" if @path[0] != '/'
42
- @target_widths = TARGET_WIDTHS.call
42
+ @target_widths = TARGET_WIDTHS.call(DEFAULT_WIDTH_TOLERANCE, MIN_WIDTH, MAX_WIDTH)
43
43
  end
44
44
 
45
45
  def to_url(opts = {})
@@ -53,12 +53,12 @@ module Imgix
53
53
  end
54
54
 
55
55
  @options = prev_options
56
- return url
56
+ url
57
57
  end
58
58
 
59
59
  def defaults
60
60
  @options = {}
61
- return self
61
+ self
62
62
  end
63
63
 
64
64
  def method_missing(method, *args, &block)
@@ -70,33 +70,44 @@ module Imgix
70
70
  end
71
71
 
72
72
  @options[key] = args.join(',')
73
- return self
73
+ self
74
74
  end
75
75
 
76
76
  ALIASES.each do |from, to|
77
77
  define_method from do |*args|
78
+ warn "Warning: `Path.#{from}' has been deprecated and " \
79
+ "will be removed in the next major version (along " \
80
+ "with all parameter `ALIASES`).\n"
78
81
  self.send(to, *args)
79
82
  end
80
83
 
81
84
  define_method "#{from}=" do |*args|
85
+ warn "Warning: `Path.#{from}=' has been deprecated and " \
86
+ "will be removed in the next major version (along " \
87
+ "with all parameter `ALIASES`).\n"
82
88
  self.send("#{to}=", *args)
83
89
  return self
84
90
  end
85
91
  end
86
92
 
87
- def to_srcset(params = {})
93
+ def to_srcset(options: {}, **params)
94
+ prev_options = @options.dup
88
95
  @options.merge!(params)
89
- width = @options['w'.to_sym]
90
- height = @options['h'.to_sym]
91
- aspect_ratio = @options['ar'.to_sym]
92
96
 
93
- if ((width) || (height && aspect_ratio))
94
- build_dpr_srcset(@options)
95
- else
96
- build_srcset_pairs(@options)
97
- end
97
+ width = @options[:w]
98
+ height = @options[:h]
99
+ aspect_ratio = @options[:ar]
100
+
101
+ srcset = if width || (height && aspect_ratio)
102
+ build_dpr_srcset(options: options, params: @options)
103
+ else
104
+ build_srcset_pairs(options: options, params: @options)
105
+ end
106
+
107
+ @options = prev_options
108
+ srcset
98
109
  end
99
-
110
+
100
111
  private
101
112
 
102
113
  def signature
@@ -123,27 +134,87 @@ module Imgix
123
134
  query.length > 0
124
135
  end
125
136
 
126
- def build_srcset_pairs(params = {})
137
+ def build_srcset_pairs(options:, params:)
127
138
  srcset = ''
128
- for width in @target_widths do
129
- currentParams = params || {}
130
- currentParams['w'] = width
131
- srcset += "#{to_url(currentParams)} #{width}w,\n"
139
+
140
+ widths = options[:widths] || []
141
+ width_tolerance = options[:width_tolerance] || DEFAULT_WIDTH_TOLERANCE
142
+ min_width = options[:min_width] || MIN_WIDTH
143
+ max_width = options[:max_width] || MAX_WIDTH
144
+
145
+ if !widths.empty?
146
+ validate_widths!(widths)
147
+ srcset_widths = widths
148
+ elsif width_tolerance != DEFAULT_WIDTH_TOLERANCE || min_width != MIN_WIDTH || max_width != MAX_WIDTH
149
+ validate_range!(min_width, max_width)
150
+ validate_width_tolerance!(width_tolerance)
151
+ srcset_widths = TARGET_WIDTHS.call(width_tolerance, min_width, max_width)
152
+ else
153
+ srcset_widths = @target_widths
154
+ end
155
+
156
+ for width in srcset_widths do
157
+ params[:w] = width
158
+ srcset += "#{to_url(params)} #{width}w,\n"
132
159
  end
133
160
 
134
- return srcset[0..-3]
161
+ srcset[0..-3]
135
162
  end
136
163
 
137
- def build_dpr_srcset(params = {})
164
+ def build_dpr_srcset(options:, params:)
138
165
  srcset = ''
139
- target_ratios = [1,2,3,4,5]
140
- url = to_url(params)
166
+
167
+ disable_variable_quality = options[:disable_variable_quality] || false
168
+ validate_variable_qualities!(disable_variable_quality)
169
+
170
+ target_ratios = [1, 2, 3, 4, 5]
171
+ quality = params[:q]
141
172
 
142
173
  for ratio in target_ratios do
143
- srcset += "#{url} #{ratio}x,\n"
174
+ params[:dpr] = ratio
175
+
176
+ unless disable_variable_quality
177
+ params[:q] = quality || DPR_QUALITY[ratio]
178
+ end
179
+
180
+ srcset += "#{to_url(params)} #{ratio}x,\n"
144
181
  end
145
182
 
146
- return srcset[0..-3]
183
+ srcset[0..-3]
184
+ end
185
+
186
+ def validate_width_tolerance!(width_tolerance)
187
+ width_increment_error = 'error: `width_tolerance` must be a positive `Numeric` value'
188
+
189
+ if !width_tolerance.is_a?(Numeric) || width_tolerance <= 0
190
+ raise ArgumentError, width_increment_error
191
+ end
192
+ end
193
+
194
+ def validate_widths!(widths)
195
+ widths_error = 'error: `widths` must be an array of positive `Numeric` values'
196
+ raise ArgumentError, widths_error unless widths.is_a?(Array)
197
+
198
+ all_positive_integers = widths.all? { |i| i.is_a?(Integer) && i > 0 }
199
+ raise ArgumentError, widths_error unless all_positive_integers
200
+ end
201
+
202
+ def validate_range!(min_srcset, max_srcset)
203
+ range_numeric_error = 'error: `min_width` and `max_width` must be positive `Numeric` values'
204
+ unless min_srcset.is_a?(Numeric) && max_srcset.is_a?(Numeric)
205
+ raise ArgumentError, range_numeric_error
206
+ end
207
+
208
+ unless min_srcset > 0 && max_srcset > 0
209
+ raise ArgumentError, range_numeric_error
210
+ end
211
+ end
212
+
213
+ def validate_variable_qualities!(disable_quality)
214
+ disable_quality_error = 'error: `disable_quality` must be a Boolean value'
215
+ unless disable_quality.is_a?(TrueClass) || disable_quality.is_a?(FalseClass)
216
+ raise ArgumentError, disable_quality_error
217
+ end
147
218
  end
148
219
  end
149
220
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Imgix
4
- VERSION = '3.1.0'
4
+ VERSION = '3.3.1'
5
5
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class ParamHelpers < Imgix::Test
6
+ def test_param_helpers_emits_dep_warning
7
+ host_warn = "Warning: The identifier `host' has been deprecated and " \
8
+ "will\nappear as `domain' in the next major version, e.g. " \
9
+ "`@host'\nbecomes `@domain', `options[:host]' becomes " \
10
+ "`options[:domain]'.\n"
11
+
12
+ assert_output(nil, host_warn) {
13
+ client = Imgix::Client.new(host: 'test.imgix.net')
14
+
15
+ rect_warn = "Warning: `ParamHelpers.rect` has been deprecated and " \
16
+ "will be removed in the next major version.\n"
17
+
18
+ assert_output(nil, rect_warn){
19
+ client.path('/images/demo.png').rect(x: 0, y: 50, width: 200, height: 300)
20
+ }
21
+ }
22
+ end
23
+ end
@@ -3,6 +3,27 @@
3
3
  require 'test_helper'
4
4
 
5
5
  class PathTest < Imgix::Test
6
+ def test_prefix_with_arg_warns
7
+ prefix_warn = "Warning: `Client::prefix' will take zero arguments " \
8
+ "in the next major version.\n"
9
+
10
+ assert_output(nil, prefix_warn) {
11
+ Imgix::Client.new(
12
+ domain: 'test.imgix.net',
13
+ include_library_param: false
14
+ ).prefix("")
15
+ }
16
+
17
+ # `new_prefix' is a placeholder until the bump, when it will become
18
+ # `prefix`.
19
+ assert_output(nil, nil) {
20
+ Imgix::Client.new(
21
+ domain: 'test.imgix.net',
22
+ include_library_param: false
23
+ ).new_prefix
24
+ }
25
+ end
26
+
6
27
  def test_creating_a_path
7
28
  path = client.path('/images/demo.png')
8
29
  assert_equal 'https://demo.imgix.net/images/demo.png?s=2c7c157eaf23b06a0deb2f60b81938c4', path.to_url
@@ -14,7 +35,12 @@ class PathTest < Imgix::Test
14
35
  def test_signing_path_with_param
15
36
  url = 'https://demo.imgix.net/images/demo.png?w=200&s=da421114ca238d1f4a927b889f67c34e'
16
37
  path = client.path('/images/demo.png')
17
- path.width = 200
38
+
39
+ assert_output(nil, "Warning: `Path.width=' has been deprecated and " \
40
+ "will be removed in the next major version (along " \
41
+ "with all parameter `ALIASES`).\n") {
42
+ path.width = 200
43
+ }
18
44
 
19
45
  assert_equal url, path.to_url
20
46
 
@@ -22,13 +48,23 @@ class PathTest < Imgix::Test
22
48
  assert_equal url, path.to_url(w: 200)
23
49
 
24
50
  path = client.path('/images/demo.png')
25
- assert_equal url, path.width(200).to_url
51
+
52
+ assert_output(nil, "Warning: `Path.width' has been deprecated and " \
53
+ "will be removed in the next major version (along " \
54
+ "with all parameter `ALIASES`).\n") {
55
+ assert_equal url, path.width(200).to_url
56
+ }
26
57
  end
27
58
 
28
59
  def test_resetting_defaults
29
60
  url = 'https://demo.imgix.net/images/demo.png?w=200&s=da421114ca238d1f4a927b889f67c34e'
30
61
  path = client.path('/images/demo.png')
31
- path.height = 300
62
+
63
+ assert_output(nil, "Warning: `Path.height=' has been deprecated and " \
64
+ "will be removed in the next major version (along " \
65
+ "with all parameter `ALIASES`).\n") {
66
+ path.height = 300
67
+ }
32
68
 
33
69
  assert_equal url, path.defaults.to_url(w: 200)
34
70
  end
@@ -40,7 +76,21 @@ class PathTest < Imgix::Test
40
76
  assert_equal url, path.to_url(h: 200, w: 200)
41
77
 
42
78
  path = client.path('/images/demo.png')
43
- assert_equal url, path.height(200).width(200).to_url
79
+
80
+ assert_output(nil, "Warning: `Path.height' has been deprecated and " \
81
+ "will be removed in the next major version (along " \
82
+ "with all parameter `ALIASES`).\n") {
83
+ path.height(200)
84
+ }
85
+
86
+
87
+ assert_output(nil, "Warning: `Path.width' has been deprecated and " \
88
+ "will be removed in the next major version (along " \
89
+ "with all parameter `ALIASES`).\n") {
90
+ path.width(200)
91
+ }
92
+
93
+ assert_equal url, path.to_url
44
94
  end
45
95
 
46
96
  def test_path_with_multi_value_param_safely_encoded