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.
- checksums.yaml +5 -5
- data/.github/CODEOWNERS +2 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +28 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +27 -0
- data/.github/ISSUE_TEMPLATE/question.md +17 -0
- data/.github/pull_request_template.md +73 -0
- data/.travis.yml +15 -13
- data/CHANGELOG.md +32 -1
- data/README.md +149 -21
- data/imgix.gemspec +7 -0
- data/lib/imgix.rb +32 -8
- data/lib/imgix/client.rb +47 -15
- data/lib/imgix/param_helpers.rb +1 -0
- data/lib/imgix/path.rb +115 -44
- data/lib/imgix/version.rb +1 -1
- data/test/units/param_helpers_test.rb +23 -0
- data/test/units/path_test.rb +54 -4
- data/test/units/srcset_test.rb +751 -222
- data/test/units/url_test.rb +47 -5
- metadata +15 -5
data/imgix.gemspec
CHANGED
@@ -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)/})
|
data/lib/imgix.rb
CHANGED
@@ -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 =
|
14
|
-
|
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 =
|
31
|
+
prev = min || MIN_WIDTH
|
17
32
|
|
18
|
-
while
|
33
|
+
while prev < max_size
|
19
34
|
# ensures that each width is even
|
20
|
-
resolutions.push(
|
21
|
-
prev *= 1 + (
|
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
|
data/lib/imgix/client.rb
CHANGED
@@ -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,
|
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(
|
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
|
-
|
33
|
-
|
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
|
-
|
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({
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
data/lib/imgix/param_helpers.rb
CHANGED
data/lib/imgix/path.rb
CHANGED
@@ -10,26 +10,26 @@ module Imgix
|
|
10
10
|
include ParamHelpers
|
11
11
|
|
12
12
|
ALIASES = {
|
13
|
-
width:
|
14
|
-
height:
|
15
|
-
rotation:
|
13
|
+
width: :w,
|
14
|
+
height: :h,
|
15
|
+
rotation: :rot,
|
16
16
|
noise_reduction: :nr,
|
17
|
-
sharpness:
|
18
|
-
exposure:
|
19
|
-
vibrance:
|
20
|
-
saturation:
|
21
|
-
brightness:
|
22
|
-
contrast:
|
23
|
-
highlight:
|
24
|
-
shadow:
|
25
|
-
gamma:
|
26
|
-
pixelate:
|
27
|
-
halftone:
|
28
|
-
watermark:
|
29
|
-
text:
|
30
|
-
format:
|
31
|
-
quality:
|
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
|
-
|
56
|
+
url
|
57
57
|
end
|
58
58
|
|
59
59
|
def defaults
|
60
60
|
@options = {}
|
61
|
-
|
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
|
-
|
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(
|
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
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
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
|
-
|
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
|
-
|
140
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/imgix/version.rb
CHANGED
@@ -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
|
data/test/units/path_test.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|