imgix 3.1.0 → 3.3.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.
@@ -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