imgwire 0.2.0 → 0.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 +4 -4
- data/CODEGEN_VERSION +1 -1
- data/README.md +30 -0
- data/lib/imgwire/image.rb +2 -80
- data/lib/imgwire/url_transformations.rb +929 -0
- data/lib/imgwire/version.rb +1 -1
- metadata +17 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: df6fe59538abf5207310fee7e35a2bc69122f3e989c79bad3a674979553ddc62
|
|
4
|
+
data.tar.gz: ea52a41f725f7d9ddd339151eb1e350a815b39276caddb1db643ba8627da9a50
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fd1bfa952e82bf07d9f0c3dd517d17e6863e15a1a8107ab743c262f64a72f71a866599f2a7f79d5ff9e8c5c2ad7dcd217319732429b4e93f5c7ec0665a31d442
|
|
7
|
+
data.tar.gz: 813abc30206d27d06d109af62d0a82b6bd8068244d050a6bdfe8fbb1a4317e84012aa429c63c2d23fb8d6fecd8311469ebe2a34c92cda7229d87716fef9f3eae
|
data/CODEGEN_VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
b57e439d6e9c3859c7bddb889658b5ff808503ccc136ea28a56e0fd57a1678aa
|
data/README.md
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
1
3
|
# `imgwire`
|
|
2
4
|
|
|
3
5
|
[](https://rubygems.org/gems/imgwire)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
7
|
[](https://github.com/Blackhawk-Software/imgwire-ruby/actions/workflows/ci.yml)
|
|
5
8
|
[](https://github.com/Blackhawk-Software/imgwire-ruby/actions/workflows/release.yml)
|
|
6
9
|
|
|
@@ -8,6 +11,9 @@
|
|
|
8
11
|
|
|
9
12
|
Use it in Rails apps, workers, jobs, and other backend runtimes to authenticate with a Server API Key, upload files from Ruby IO objects, manage server-side resources, and generate image transformation URLs without rebuilding imgwire request plumbing yourself.
|
|
10
13
|
|
|
14
|
+
> [!TIP]
|
|
15
|
+
> Obtain an API key by signing up at [imgwire.dev](https://imgwire.dev). Read the full API & SDK documentation [here](https://docs.imgwire.dev/guides/backend-quickstart).
|
|
16
|
+
|
|
11
17
|
## Installation
|
|
12
18
|
|
|
13
19
|
```bash
|
|
@@ -114,6 +120,30 @@ puts image.url(
|
|
|
114
120
|
)
|
|
115
121
|
```
|
|
116
122
|
|
|
123
|
+
The helper accepts the transform names and aliases from
|
|
124
|
+
`docs/imgwire-url-transformations-guide.md`. Multi-field transforms can be
|
|
125
|
+
passed as their URL string syntax or as Ruby hashes:
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
puts image.url(
|
|
129
|
+
gradient: {
|
|
130
|
+
colors: ["#0b1f5e", "#ff2a2a"],
|
|
131
|
+
angle: 90,
|
|
132
|
+
opacity: 0.25,
|
|
133
|
+
blend: "overlay"
|
|
134
|
+
},
|
|
135
|
+
watermark_url: "https://example.com/logo.png",
|
|
136
|
+
watermark_position: {
|
|
137
|
+
gravity: "southeast",
|
|
138
|
+
x: -24,
|
|
139
|
+
y: -24,
|
|
140
|
+
opacity: 0.85
|
|
141
|
+
}
|
|
142
|
+
)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Out-of-range transform values are omitted from the generated URL.
|
|
146
|
+
|
|
117
147
|
## Generation
|
|
118
148
|
|
|
119
149
|
From a clean checkout:
|
data/lib/imgwire/image.rb
CHANGED
|
@@ -1,25 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'uri'
|
|
4
|
+
require 'imgwire/url_transformations'
|
|
4
5
|
|
|
5
6
|
module Imgwire
|
|
6
7
|
class Image < ImgwireGenerated::ImageSchema
|
|
7
8
|
PRESETS = %w[thumbnail small medium large].freeze
|
|
8
|
-
ROTATE_ANGLES = [0, 90, 180, 270, 360].freeze
|
|
9
|
-
FORMATS = %w[jpg png avif gif webp auto].freeze
|
|
10
|
-
|
|
11
|
-
RULES = {
|
|
12
|
-
'background' => %w[background bg],
|
|
13
|
-
'crop' => %w[crop],
|
|
14
|
-
'enlarge' => %w[enlarge],
|
|
15
|
-
'format' => %w[format fm],
|
|
16
|
-
'gravity' => %w[gravity],
|
|
17
|
-
'height' => %w[height h],
|
|
18
|
-
'quality' => %w[quality q],
|
|
19
|
-
'rotate' => %w[rotate rot],
|
|
20
|
-
'strip_metadata' => %w[strip_metadata strip],
|
|
21
|
-
'width' => %w[width w]
|
|
22
|
-
}.freeze
|
|
23
9
|
|
|
24
10
|
def self.wrap(value)
|
|
25
11
|
return value if value.is_a?(self)
|
|
@@ -38,7 +24,7 @@ module Imgwire
|
|
|
38
24
|
def url(options = {})
|
|
39
25
|
options = symbolize_keys(options)
|
|
40
26
|
path = build_preset_path(options[:preset])
|
|
41
|
-
query = build_query(options.except(:preset))
|
|
27
|
+
query = Imgwire::URLTransformations.build_query(options.except(:preset))
|
|
42
28
|
|
|
43
29
|
uri = URI.parse(cdn_url)
|
|
44
30
|
uri.path = path
|
|
@@ -63,69 +49,5 @@ module Imgwire
|
|
|
63
49
|
uri = URI.parse(cdn_url)
|
|
64
50
|
"#{uri.path}@#{preset}"
|
|
65
51
|
end
|
|
66
|
-
|
|
67
|
-
def build_query(options)
|
|
68
|
-
present = []
|
|
69
|
-
|
|
70
|
-
RULES.each do |canonical, aliases|
|
|
71
|
-
matches = aliases.filter_map do |name|
|
|
72
|
-
symbol = name.to_sym
|
|
73
|
-
[symbol, options[symbol]] if options.key?(symbol)
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
next if matches.empty?
|
|
77
|
-
raise ArgumentError, "Duplicate transformation rule: #{canonical}" if matches.length > 1
|
|
78
|
-
|
|
79
|
-
value = normalize_rule(canonical, matches.first.last)
|
|
80
|
-
present << [canonical, value] unless value.nil?
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
present
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def normalize_rule(canonical, value)
|
|
87
|
-
case canonical
|
|
88
|
-
when 'background'
|
|
89
|
-
string = value.to_s.delete_prefix('#')
|
|
90
|
-
unless string.match?(/\A[\da-fA-F]{6}\z/)
|
|
91
|
-
raise ArgumentError,
|
|
92
|
-
"Invalid transformation rule value for #{canonical}"
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
string.downcase
|
|
96
|
-
when 'crop', 'gravity'
|
|
97
|
-
value.to_s
|
|
98
|
-
when 'enlarge', 'strip_metadata'
|
|
99
|
-
value ? 'true' : nil
|
|
100
|
-
when 'format'
|
|
101
|
-
string = value.to_s
|
|
102
|
-
unless FORMATS.include?(string)
|
|
103
|
-
raise ArgumentError,
|
|
104
|
-
"Invalid transformation rule value for #{canonical}"
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
string
|
|
108
|
-
when 'height', 'quality', 'width'
|
|
109
|
-
integer = Integer(value)
|
|
110
|
-
unless integer.positive?
|
|
111
|
-
raise ArgumentError,
|
|
112
|
-
"Invalid transformation rule value for #{canonical}"
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
integer.to_s
|
|
116
|
-
when 'rotate'
|
|
117
|
-
integer = Integer(value)
|
|
118
|
-
unless ROTATE_ANGLES.include?(integer)
|
|
119
|
-
raise ArgumentError,
|
|
120
|
-
"Invalid transformation rule value for #{canonical}"
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
integer.to_s
|
|
124
|
-
else
|
|
125
|
-
raise ArgumentError, "Unsupported transformation rule: #{canonical}"
|
|
126
|
-
end
|
|
127
|
-
rescue ArgumentError, TypeError
|
|
128
|
-
raise ArgumentError, "Invalid transformation rule value for #{canonical}"
|
|
129
|
-
end
|
|
130
52
|
end
|
|
131
53
|
end
|
|
@@ -0,0 +1,929 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'base64'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'uri'
|
|
6
|
+
|
|
7
|
+
module Imgwire
|
|
8
|
+
# rubocop:disable Metrics/ModuleLength
|
|
9
|
+
module URLTransformations
|
|
10
|
+
RULES = {
|
|
11
|
+
'adjust' => %w[a adjust],
|
|
12
|
+
'background' => %w[bg background],
|
|
13
|
+
'background_alpha' => %w[bga background_alpha],
|
|
14
|
+
'blur' => %w[bl blur],
|
|
15
|
+
'brightness' => %w[br brightness],
|
|
16
|
+
'chroma_subsampling' => %w[chroma_subsampling],
|
|
17
|
+
'color_profile' => %w[cp icc color_profile],
|
|
18
|
+
'colorize' => %w[col colorize],
|
|
19
|
+
'contrast' => %w[co contrast],
|
|
20
|
+
'crop' => %w[c crop],
|
|
21
|
+
'dpi' => %w[dpi],
|
|
22
|
+
'dpr' => %w[dpr],
|
|
23
|
+
'duotone' => %w[dt duotone],
|
|
24
|
+
'enlarge' => %w[el enlarge],
|
|
25
|
+
'extend' => %w[ex extend],
|
|
26
|
+
'extend_aspect_ratio' => %w[exar extend_ar extend_aspect_ratio],
|
|
27
|
+
'flip' => %w[fl flip],
|
|
28
|
+
'format' => %w[f format ext extension fm],
|
|
29
|
+
'gradient' => %w[gr gradient],
|
|
30
|
+
'gravity' => %w[g gravity],
|
|
31
|
+
'height' => %w[h height],
|
|
32
|
+
'hue' => %w[hu hue],
|
|
33
|
+
'keep_copyright' => %w[kcr keep_copyright],
|
|
34
|
+
'lightness' => %w[l lightness],
|
|
35
|
+
'min-height' => %w[mh min_height min-height],
|
|
36
|
+
'min-width' => %w[mw min_width min-width],
|
|
37
|
+
'monochrome' => %w[mc monochrome],
|
|
38
|
+
'negate' => %w[neg negate],
|
|
39
|
+
'normalize' => %w[norm normalise normalize],
|
|
40
|
+
'padding' => %w[pd padding],
|
|
41
|
+
'pixelate' => %w[pix pixelate],
|
|
42
|
+
'progressive' => %w[progressive],
|
|
43
|
+
'quality' => %w[q quality],
|
|
44
|
+
'resizing_algorithm' => %w[ra resizing_algorithm],
|
|
45
|
+
'resizing_type' => %w[resizing_type],
|
|
46
|
+
'rotate' => %w[rot rotate],
|
|
47
|
+
'saturation' => %w[sa saturation],
|
|
48
|
+
'sharpen' => %w[sh sharpen],
|
|
49
|
+
'strip_color_profile' => %w[scp strip_color_profile],
|
|
50
|
+
'strip_metadata' => %w[sm strip_metadata strip],
|
|
51
|
+
'watermark' => %w[wm watermark],
|
|
52
|
+
'watermark_position' => %w[wmp watermark_offset watermark_position],
|
|
53
|
+
'watermark_rotate' => %w[wmr wm_rot watermark_rotate],
|
|
54
|
+
'watermark_shadow' => %w[wmsh watermark_shadow],
|
|
55
|
+
'watermark_size' => %w[wms watermark_size],
|
|
56
|
+
'watermark_text' => %w[wmt watermark_text],
|
|
57
|
+
'watermark_url' => %w[wmu watermark_url],
|
|
58
|
+
'width' => %w[w width],
|
|
59
|
+
'zoom' => %w[z zoom]
|
|
60
|
+
}.freeze
|
|
61
|
+
|
|
62
|
+
COLOR_PROFILES = %w[srgb rgb16 cmyk keep preserve].freeze
|
|
63
|
+
CHROMA_SUBSAMPLING_VALUES = %w[4:2:0 4:4:4 auto].freeze
|
|
64
|
+
FORMATS = %w[auto jpg jpeg png webp avif gif tiff].freeze
|
|
65
|
+
FLIP_VALUES = %w[vertical horizontal both].freeze
|
|
66
|
+
RESIZING_ALGORITHMS = %w[nearest cubic mitchell lanczos2 lanczos3].freeze
|
|
67
|
+
RESIZING_TYPES = %w[cover contain fill inside outside].freeze
|
|
68
|
+
RESIZING_TYPE_ALIASES = {
|
|
69
|
+
'auto' => 'inside',
|
|
70
|
+
'fill-down' => 'inside',
|
|
71
|
+
'fit' => 'inside',
|
|
72
|
+
'force' => 'fill'
|
|
73
|
+
}.freeze
|
|
74
|
+
GRAVITY_VALUES = %w[
|
|
75
|
+
ce center north south east west northeast northwest southeast southwest attention entropy
|
|
76
|
+
].freeze
|
|
77
|
+
GRAVITY_ALIASES = {
|
|
78
|
+
'n' => 'north',
|
|
79
|
+
'no' => 'north',
|
|
80
|
+
's' => 'south',
|
|
81
|
+
'so' => 'south',
|
|
82
|
+
'e' => 'east',
|
|
83
|
+
'ea' => 'east',
|
|
84
|
+
'w' => 'west',
|
|
85
|
+
'we' => 'west',
|
|
86
|
+
'ne' => 'northeast',
|
|
87
|
+
'noea' => 'northeast',
|
|
88
|
+
'se' => 'southeast',
|
|
89
|
+
'soea' => 'southeast',
|
|
90
|
+
'nw' => 'northwest',
|
|
91
|
+
'nowe' => 'northwest',
|
|
92
|
+
'sw' => 'southwest',
|
|
93
|
+
'sowe' => 'southwest',
|
|
94
|
+
'ce:sm' => 'attention'
|
|
95
|
+
}.freeze
|
|
96
|
+
|
|
97
|
+
module_function
|
|
98
|
+
|
|
99
|
+
def build_query(options)
|
|
100
|
+
normalized_options = normalize_top_level_keys(options)
|
|
101
|
+
|
|
102
|
+
RULES.each_with_object([]) do |(canonical, aliases), present|
|
|
103
|
+
matches = aliases.filter_map do |name|
|
|
104
|
+
key = name.to_sym
|
|
105
|
+
[key, normalized_options[key]] if normalized_options.key?(key)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
next if matches.empty?
|
|
109
|
+
raise ArgumentError, "Duplicate transformation rule: #{canonical}" if matches.length > 1
|
|
110
|
+
|
|
111
|
+
value = normalize_rule(canonical, matches.first.last)
|
|
112
|
+
present << [canonical, value] unless value.nil?
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def normalize_rule(canonical, value)
|
|
117
|
+
case canonical
|
|
118
|
+
when 'adjust'
|
|
119
|
+
normalize_adjust(value)
|
|
120
|
+
when 'background', 'colorize'
|
|
121
|
+
normalize_color(value)
|
|
122
|
+
when 'background_alpha'
|
|
123
|
+
normalize_number(value, min: 0, max: 1)
|
|
124
|
+
when 'blur'
|
|
125
|
+
normalize_true_or_number(value, min: 0.3, max: 100)
|
|
126
|
+
when 'brightness', 'lightness', 'saturation'
|
|
127
|
+
normalize_number(value, min: 0.01, max: 10)
|
|
128
|
+
when 'chroma_subsampling'
|
|
129
|
+
normalize_enum(value, CHROMA_SUBSAMPLING_VALUES)
|
|
130
|
+
when 'color_profile'
|
|
131
|
+
normalize_enum(value, COLOR_PROFILES)
|
|
132
|
+
when 'contrast'
|
|
133
|
+
normalize_contrast(value)
|
|
134
|
+
when 'crop'
|
|
135
|
+
normalize_crop(value)
|
|
136
|
+
when 'dpi'
|
|
137
|
+
normalize_integer(value, min: 1, max: 600)
|
|
138
|
+
when 'dpr'
|
|
139
|
+
normalize_number(value, min: 0.01, max: 8)
|
|
140
|
+
when 'duotone'
|
|
141
|
+
normalize_duotone(value)
|
|
142
|
+
when 'enlarge', 'keep_copyright', 'strip_color_profile', 'strip_metadata'
|
|
143
|
+
normalize_boolean_flag(value)
|
|
144
|
+
when 'extend'
|
|
145
|
+
normalize_extend(value)
|
|
146
|
+
when 'extend_aspect_ratio'
|
|
147
|
+
normalize_extend_aspect_ratio(value)
|
|
148
|
+
when 'flip'
|
|
149
|
+
normalize_flip(value)
|
|
150
|
+
when 'format'
|
|
151
|
+
normalize_enum(value, FORMATS)
|
|
152
|
+
when 'gradient'
|
|
153
|
+
normalize_gradient(value)
|
|
154
|
+
when 'gravity'
|
|
155
|
+
normalize_gravity(value)
|
|
156
|
+
when 'height', 'min-height', 'min-width', 'width'
|
|
157
|
+
normalize_integer(value, min: 1, max: 8192)
|
|
158
|
+
when 'hue'
|
|
159
|
+
normalize_number(value)
|
|
160
|
+
when 'monochrome'
|
|
161
|
+
normalize_monochrome(value)
|
|
162
|
+
when 'negate'
|
|
163
|
+
normalize_negate(value)
|
|
164
|
+
when 'normalize'
|
|
165
|
+
normalize_normalize(value)
|
|
166
|
+
when 'padding'
|
|
167
|
+
normalize_padding(value)
|
|
168
|
+
when 'pixelate'
|
|
169
|
+
normalize_integer(value, min: 2, max: 256)
|
|
170
|
+
when 'progressive'
|
|
171
|
+
normalize_boolean_or_auto(value)
|
|
172
|
+
when 'quality'
|
|
173
|
+
normalize_quality(value)
|
|
174
|
+
when 'resizing_algorithm'
|
|
175
|
+
normalize_enum(value, RESIZING_ALGORITHMS)
|
|
176
|
+
when 'resizing_type'
|
|
177
|
+
normalize_enum(value, RESIZING_TYPES, aliases: RESIZING_TYPE_ALIASES)
|
|
178
|
+
when 'rotate', 'watermark_rotate'
|
|
179
|
+
normalize_rotate(value)
|
|
180
|
+
when 'sharpen'
|
|
181
|
+
normalize_sharpen(value)
|
|
182
|
+
when 'watermark'
|
|
183
|
+
normalize_watermark(value)
|
|
184
|
+
when 'watermark_position'
|
|
185
|
+
normalize_watermark_position(value)
|
|
186
|
+
when 'watermark_shadow'
|
|
187
|
+
normalize_watermark_shadow(value)
|
|
188
|
+
when 'watermark_size'
|
|
189
|
+
normalize_watermark_size(value)
|
|
190
|
+
when 'watermark_text'
|
|
191
|
+
normalize_text_or_json_object(value)
|
|
192
|
+
when 'watermark_url'
|
|
193
|
+
normalize_watermark_url(value)
|
|
194
|
+
when 'zoom'
|
|
195
|
+
normalize_zoom(value)
|
|
196
|
+
end
|
|
197
|
+
rescue ArgumentError, TypeError
|
|
198
|
+
nil
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def normalize_adjust(value)
|
|
202
|
+
return normalize_json_string(value) if json_object_string?(value)
|
|
203
|
+
|
|
204
|
+
hash = value_hash(value)
|
|
205
|
+
if hash
|
|
206
|
+
brightness = normalize_number(field(hash, 'brightness'))
|
|
207
|
+
saturation = normalize_number(field(hash, 'saturation'))
|
|
208
|
+
color = normalize_number(field(hash, 'color'))
|
|
209
|
+
|
|
210
|
+
return contiguous_segments([brightness], [saturation, color])
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
segments = split_segments(value, min: 1, max: 3)
|
|
214
|
+
return unless segments
|
|
215
|
+
|
|
216
|
+
brightness = normalize_number(segments[0])
|
|
217
|
+
saturation = normalize_number(segments[1]) if segments.length > 1
|
|
218
|
+
color = normalize_number(segments[2]) if segments.length > 2
|
|
219
|
+
|
|
220
|
+
contiguous_segments([brightness], [saturation, color].first(segments.length - 1))
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def normalize_contrast(value)
|
|
224
|
+
return normalize_json_string(value) if json_object_string?(value)
|
|
225
|
+
|
|
226
|
+
hash = value_hash(value)
|
|
227
|
+
if hash
|
|
228
|
+
multiplier = normalize_number(field(hash, 'multiplier'))
|
|
229
|
+
pivot = normalize_number(field(hash, 'pivot'))
|
|
230
|
+
|
|
231
|
+
return contiguous_segments([multiplier], [pivot])
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
segments = split_segments(value, min: 1, max: 2)
|
|
235
|
+
return unless segments
|
|
236
|
+
|
|
237
|
+
multiplier = normalize_number(segments[0])
|
|
238
|
+
pivot = normalize_number(segments[1]) if segments.length > 1
|
|
239
|
+
|
|
240
|
+
contiguous_segments([multiplier], [pivot].first(segments.length - 1))
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def normalize_crop(value)
|
|
244
|
+
return normalize_json_string(value) if json_object_string?(value)
|
|
245
|
+
|
|
246
|
+
hash = value_hash(value)
|
|
247
|
+
return normalize_crop_hash(hash) if hash
|
|
248
|
+
|
|
249
|
+
segments = split_segments(value, min: 2, max: 5)
|
|
250
|
+
return unless [2, 3, 4, 5].include?(segments&.length)
|
|
251
|
+
|
|
252
|
+
if segments.length <= 3
|
|
253
|
+
width = normalize_integer(segments[0], min: 1)
|
|
254
|
+
height = normalize_integer(segments[1], min: 1)
|
|
255
|
+
gravity = normalize_gravity(segments[2]) if segments.length == 3
|
|
256
|
+
|
|
257
|
+
return contiguous_segments([width, height], [gravity].first(segments.length - 2))
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
x = normalize_integer(segments[0])
|
|
261
|
+
y = normalize_integer(segments[1])
|
|
262
|
+
width = normalize_integer(segments[2], min: 1)
|
|
263
|
+
height = normalize_integer(segments[3], min: 1)
|
|
264
|
+
gravity = normalize_gravity(segments[4]) if segments.length == 5
|
|
265
|
+
|
|
266
|
+
contiguous_segments([x, y, width, height], [gravity].first(segments.length - 4))
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def normalize_crop_hash(hash)
|
|
270
|
+
x = normalize_integer(field(hash, 'x'))
|
|
271
|
+
y = normalize_integer(field(hash, 'y'))
|
|
272
|
+
width = normalize_integer(field(hash, 'width', 'w'), min: 1)
|
|
273
|
+
height = normalize_integer(field(hash, 'height', 'h'), min: 1)
|
|
274
|
+
gravity = normalize_gravity(field(hash, 'gravity', 'g'))
|
|
275
|
+
|
|
276
|
+
if field?(hash, 'x') || field?(hash, 'y')
|
|
277
|
+
contiguous_segments([x, y, width, height], [gravity])
|
|
278
|
+
else
|
|
279
|
+
contiguous_segments([width, height], [gravity])
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def normalize_duotone(value)
|
|
284
|
+
return normalize_json_string(value) if json_object_string?(value)
|
|
285
|
+
|
|
286
|
+
hash = value_hash(value)
|
|
287
|
+
if hash
|
|
288
|
+
shadow = normalize_color(field(hash, 'shadowColor', 'shadow_color', 'shadow'))
|
|
289
|
+
highlight = normalize_color(field(hash, 'highlightColor', 'highlight_color', 'highlight'))
|
|
290
|
+
|
|
291
|
+
return contiguous_segments([shadow, highlight], [])
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
segments = split_segments(value, min: 2, max: 2)
|
|
295
|
+
return unless segments
|
|
296
|
+
|
|
297
|
+
shadow = normalize_color(segments[0])
|
|
298
|
+
highlight = normalize_color(segments[1])
|
|
299
|
+
|
|
300
|
+
contiguous_segments([shadow, highlight], [])
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def normalize_extend(value)
|
|
304
|
+
return normalize_json_string(value) if json_object_string?(value)
|
|
305
|
+
|
|
306
|
+
hash = value_hash(value)
|
|
307
|
+
return normalize_extend_hash(hash) if hash
|
|
308
|
+
|
|
309
|
+
segments = split_segments(value, min: 1, max: 5)
|
|
310
|
+
return unless segments
|
|
311
|
+
|
|
312
|
+
top = normalize_integer(segments[0])
|
|
313
|
+
right = normalize_integer(segments[1]) if segments.length > 1
|
|
314
|
+
bottom = normalize_integer(segments[2]) if segments.length > 2
|
|
315
|
+
left = normalize_integer(segments[3]) if segments.length > 3
|
|
316
|
+
background = normalize_color(segments[4]) if segments.length > 4
|
|
317
|
+
|
|
318
|
+
contiguous_segments([top], [right, bottom, left, background].first(segments.length - 1))
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def normalize_extend_hash(hash)
|
|
322
|
+
top = normalize_integer(field(hash, 'top'))
|
|
323
|
+
right = normalize_integer(field(hash, 'right'))
|
|
324
|
+
bottom = normalize_integer(field(hash, 'bottom'))
|
|
325
|
+
left = normalize_integer(field(hash, 'left'))
|
|
326
|
+
background = normalize_color(field(hash, 'background', 'bg'))
|
|
327
|
+
|
|
328
|
+
compact_json = compact_json_object(
|
|
329
|
+
hash,
|
|
330
|
+
'top' => top,
|
|
331
|
+
'right' => right,
|
|
332
|
+
'bottom' => bottom,
|
|
333
|
+
'left' => left,
|
|
334
|
+
'background' => background
|
|
335
|
+
)
|
|
336
|
+
return unless compact_json
|
|
337
|
+
|
|
338
|
+
contiguous_segments([top], [right, bottom, left, background]) || compact_json
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def normalize_extend_aspect_ratio(value)
|
|
342
|
+
return normalize_json_string(value) if json_object_string?(value)
|
|
343
|
+
|
|
344
|
+
hash = value_hash(value)
|
|
345
|
+
if hash
|
|
346
|
+
ratio = normalize_number(field(hash, 'ratio'), min: 0.000001)
|
|
347
|
+
return ratio if ratio
|
|
348
|
+
|
|
349
|
+
width = normalize_integer(field(hash, 'width', 'w'), min: 1)
|
|
350
|
+
height = normalize_integer(field(hash, 'height', 'h'), min: 1)
|
|
351
|
+
|
|
352
|
+
return contiguous_segments([width, height], [])
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
segments = split_segments(value, min: 1, max: 2)
|
|
356
|
+
return unless segments
|
|
357
|
+
|
|
358
|
+
return normalize_number(segments[0], min: 0.000001) if segments.length == 1
|
|
359
|
+
|
|
360
|
+
width = normalize_integer(segments[0], min: 1)
|
|
361
|
+
height = normalize_integer(segments[1], min: 1)
|
|
362
|
+
|
|
363
|
+
contiguous_segments([width, height], [])
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def normalize_flip(value)
|
|
367
|
+
hash = value_hash(value)
|
|
368
|
+
if hash
|
|
369
|
+
horizontal = parse_boolean(field(hash, 'horizontal'))
|
|
370
|
+
vertical = parse_boolean(field(hash, 'vertical'))
|
|
371
|
+
return if horizontal.nil? && vertical.nil?
|
|
372
|
+
|
|
373
|
+
return 'both' if horizontal && vertical
|
|
374
|
+
return 'horizontal' if horizontal
|
|
375
|
+
return 'vertical' if vertical
|
|
376
|
+
|
|
377
|
+
return
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
string = normalize_string(value)
|
|
381
|
+
return string if FLIP_VALUES.include?(string)
|
|
382
|
+
|
|
383
|
+
segments = split_segments(value, min: 2, max: 2)
|
|
384
|
+
return unless segments
|
|
385
|
+
|
|
386
|
+
horizontal = parse_boolean(segments[0])
|
|
387
|
+
vertical = parse_boolean(segments[1])
|
|
388
|
+
return if horizontal.nil? || vertical.nil?
|
|
389
|
+
|
|
390
|
+
"#{horizontal}:#{vertical}"
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def normalize_gradient(value)
|
|
394
|
+
return normalize_json_string(value) if json_object_string?(value)
|
|
395
|
+
|
|
396
|
+
hash = value_hash(value)
|
|
397
|
+
return normalize_gradient_hash(hash) if hash
|
|
398
|
+
|
|
399
|
+
segments = split_segments(value, min: 1, max: 4)
|
|
400
|
+
return unless segments
|
|
401
|
+
|
|
402
|
+
colors = normalize_color_list(segments[0])
|
|
403
|
+
angle = normalize_number(segments[1]) if segments.length > 1
|
|
404
|
+
opacity = normalize_number(segments[2], min: 0, max: 1) if segments.length > 2
|
|
405
|
+
blend = normalize_non_empty_string(segments[3]) if segments.length > 3
|
|
406
|
+
|
|
407
|
+
contiguous_segments([colors], [angle, opacity, blend].first(segments.length - 1))
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
def normalize_gradient_hash(hash)
|
|
411
|
+
colors = normalize_color_list(field(hash, 'colors'))
|
|
412
|
+
angle = normalize_number(field(hash, 'angle'))
|
|
413
|
+
opacity = normalize_number(field(hash, 'opacity'), min: 0, max: 1)
|
|
414
|
+
blend = normalize_non_empty_string(field(hash, 'blend'))
|
|
415
|
+
|
|
416
|
+
contiguous_segments([colors], [angle, opacity, blend])
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
def normalize_monochrome(value)
|
|
420
|
+
boolean = parse_boolean(value)
|
|
421
|
+
return 'true' if boolean == true
|
|
422
|
+
return if boolean == false
|
|
423
|
+
|
|
424
|
+
normalize_color(value)
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
def normalize_negate(value)
|
|
428
|
+
hash = value_hash(value)
|
|
429
|
+
if hash
|
|
430
|
+
alpha = parse_boolean(field(hash, 'alpha'))
|
|
431
|
+
return unless [true, false].include?(alpha)
|
|
432
|
+
|
|
433
|
+
return "alpha:#{alpha}"
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
boolean = parse_boolean(value)
|
|
437
|
+
return 'true' if boolean == true
|
|
438
|
+
return if boolean == false
|
|
439
|
+
|
|
440
|
+
segments = split_segments(value, min: 2, max: 2)
|
|
441
|
+
return unless segments&.first == 'alpha'
|
|
442
|
+
|
|
443
|
+
alpha = parse_boolean(segments[1])
|
|
444
|
+
return unless [true, false].include?(alpha)
|
|
445
|
+
|
|
446
|
+
"alpha:#{alpha}"
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
def normalize_normalize(value)
|
|
450
|
+
hash = value_hash(value)
|
|
451
|
+
if hash
|
|
452
|
+
lower = normalize_number(field(hash, 'lower'))
|
|
453
|
+
upper = normalize_number(field(hash, 'upper'))
|
|
454
|
+
|
|
455
|
+
return contiguous_segments([lower, upper], [])
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
boolean = parse_boolean(value)
|
|
459
|
+
return 'true' if boolean == true
|
|
460
|
+
return if boolean == false
|
|
461
|
+
|
|
462
|
+
segments = split_segments(value, min: 2, max: 2)
|
|
463
|
+
return unless segments
|
|
464
|
+
|
|
465
|
+
lower = normalize_number(segments[0])
|
|
466
|
+
upper = normalize_number(segments[1])
|
|
467
|
+
|
|
468
|
+
contiguous_segments([lower, upper], [])
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
def normalize_padding(value)
|
|
472
|
+
return normalize_json_string(value) if json_object_string?(value)
|
|
473
|
+
|
|
474
|
+
hash = value_hash(value)
|
|
475
|
+
return normalize_padding_hash(hash) if hash
|
|
476
|
+
|
|
477
|
+
segments = split_segments(value, min: 1, max: 4)
|
|
478
|
+
return unless segments
|
|
479
|
+
|
|
480
|
+
normalized = segments.map { |segment| normalize_integer(segment) }
|
|
481
|
+
return if normalized.any?(&:nil?)
|
|
482
|
+
|
|
483
|
+
normalized.join(':')
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
def normalize_padding_hash(hash)
|
|
487
|
+
all = normalize_integer(field(hash, 'all'))
|
|
488
|
+
return all if all
|
|
489
|
+
|
|
490
|
+
x = normalize_integer(field(hash, 'x'))
|
|
491
|
+
y = normalize_integer(field(hash, 'y'))
|
|
492
|
+
return contiguous_segments([x, y], []) if x || y
|
|
493
|
+
|
|
494
|
+
top = normalize_integer(field(hash, 'top'))
|
|
495
|
+
right = normalize_integer(field(hash, 'right'))
|
|
496
|
+
bottom = normalize_integer(field(hash, 'bottom'))
|
|
497
|
+
left = normalize_integer(field(hash, 'left'))
|
|
498
|
+
|
|
499
|
+
compact_json = compact_json_object(
|
|
500
|
+
hash,
|
|
501
|
+
'top' => top,
|
|
502
|
+
'right' => right,
|
|
503
|
+
'bottom' => bottom,
|
|
504
|
+
'left' => left
|
|
505
|
+
)
|
|
506
|
+
return unless compact_json
|
|
507
|
+
|
|
508
|
+
contiguous_segments([top, right, bottom, left], []) || compact_json
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
def normalize_quality(value)
|
|
512
|
+
return 'auto' if normalize_string(value) == 'auto'
|
|
513
|
+
|
|
514
|
+
normalize_integer(value, min: 1, max: 100)
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
def normalize_rotate(value)
|
|
518
|
+
return normalize_json_string(value) if json_object_string?(value)
|
|
519
|
+
|
|
520
|
+
hash = value_hash(value)
|
|
521
|
+
if hash
|
|
522
|
+
angle = normalize_number(field(hash, 'angle'))
|
|
523
|
+
background = normalize_color(field(hash, 'background', 'bg'))
|
|
524
|
+
|
|
525
|
+
return contiguous_segments([angle], [background])
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
segments = split_segments(value, min: 1, max: 2)
|
|
529
|
+
return unless segments
|
|
530
|
+
|
|
531
|
+
angle = normalize_number(segments[0])
|
|
532
|
+
background = normalize_color(segments[1]) if segments.length > 1
|
|
533
|
+
|
|
534
|
+
contiguous_segments([angle], [background].first(segments.length - 1))
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
def normalize_sharpen(value)
|
|
538
|
+
return normalize_json_string(value) if json_object_string?(value)
|
|
539
|
+
|
|
540
|
+
hash = value_hash(value)
|
|
541
|
+
return json_object(hash) if hash
|
|
542
|
+
|
|
543
|
+
normalize_true_or_number(value)
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
def normalize_watermark(value)
|
|
547
|
+
return normalize_json_string(value) if json_object_string?(value)
|
|
548
|
+
|
|
549
|
+
hash = value_hash(value)
|
|
550
|
+
return normalize_watermark_hash(hash) if hash
|
|
551
|
+
|
|
552
|
+
segments = split_segments(value, min: 1, max: 4)
|
|
553
|
+
return unless segments
|
|
554
|
+
|
|
555
|
+
image_id = normalize_non_empty_string(segments[0])
|
|
556
|
+
gravity = normalize_gravity(segments[1]) if segments.length > 1
|
|
557
|
+
x = normalize_integer(segments[2]) if segments.length > 2
|
|
558
|
+
y = normalize_integer(segments[3]) if segments.length > 3
|
|
559
|
+
|
|
560
|
+
contiguous_segments([image_id], [gravity, x, y].first(segments.length - 1))
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
def normalize_watermark_hash(hash)
|
|
564
|
+
image_id = normalize_non_empty_string(field(hash, 'image_id', 'imageId', 'id'))
|
|
565
|
+
gravity = normalize_gravity(field(hash, 'gravity', 'g'))
|
|
566
|
+
x = normalize_integer(field(hash, 'x'))
|
|
567
|
+
y = normalize_integer(field(hash, 'y'))
|
|
568
|
+
|
|
569
|
+
contiguous_segments([image_id], [gravity, x, y])
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
def normalize_watermark_position(value)
|
|
573
|
+
return normalize_json_string(value) if json_object_string?(value)
|
|
574
|
+
|
|
575
|
+
hash = value_hash(value)
|
|
576
|
+
return normalize_watermark_position_hash(hash) if hash
|
|
577
|
+
|
|
578
|
+
segments = split_segments(value, min: 1, max: 5)
|
|
579
|
+
return unless segments
|
|
580
|
+
|
|
581
|
+
gravity = normalize_gravity(segments[0])
|
|
582
|
+
x = normalize_integer(segments[1]) if segments.length > 1
|
|
583
|
+
y = normalize_integer(segments[2]) if segments.length > 2
|
|
584
|
+
opacity = normalize_number(segments[3], min: 0, max: 1) if segments.length > 3
|
|
585
|
+
blend = normalize_non_empty_string(segments[4]) if segments.length > 4
|
|
586
|
+
|
|
587
|
+
contiguous_segments([gravity], [x, y, opacity, blend].first(segments.length - 1))
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
def normalize_watermark_position_hash(hash)
|
|
591
|
+
gravity = normalize_gravity(field(hash, 'gravity', 'g'))
|
|
592
|
+
x = normalize_integer(field(hash, 'x'))
|
|
593
|
+
y = normalize_integer(field(hash, 'y'))
|
|
594
|
+
opacity = normalize_number(field(hash, 'opacity'), min: 0, max: 1)
|
|
595
|
+
blend = normalize_non_empty_string(field(hash, 'blend'))
|
|
596
|
+
|
|
597
|
+
contiguous_segments([gravity], [x, y, opacity, blend])
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
def normalize_watermark_shadow(value)
|
|
601
|
+
return normalize_json_string(value) if json_object_string?(value)
|
|
602
|
+
|
|
603
|
+
boolean = parse_boolean(value)
|
|
604
|
+
return 'true' if boolean == true
|
|
605
|
+
return if boolean == false
|
|
606
|
+
|
|
607
|
+
hash = value_hash(value)
|
|
608
|
+
return normalize_watermark_shadow_hash(hash) if hash
|
|
609
|
+
|
|
610
|
+
segments = split_segments(value, min: 1, max: 4)
|
|
611
|
+
return unless segments
|
|
612
|
+
|
|
613
|
+
color = normalize_color(segments[0])
|
|
614
|
+
blur = normalize_number(segments[1]) if segments.length > 1
|
|
615
|
+
x = normalize_integer(segments[2]) if segments.length > 2
|
|
616
|
+
y = normalize_integer(segments[3]) if segments.length > 3
|
|
617
|
+
|
|
618
|
+
contiguous_segments([color], [blur, x, y].first(segments.length - 1))
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
def normalize_watermark_shadow_hash(hash)
|
|
622
|
+
color = normalize_color(field(hash, 'color'))
|
|
623
|
+
blur = normalize_number(field(hash, 'blur'))
|
|
624
|
+
x = normalize_integer(field(hash, 'x'))
|
|
625
|
+
y = normalize_integer(field(hash, 'y'))
|
|
626
|
+
|
|
627
|
+
contiguous_segments([color], [blur, x, y])
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
def normalize_watermark_size(value)
|
|
631
|
+
return normalize_json_string(value) if json_object_string?(value)
|
|
632
|
+
|
|
633
|
+
hash = value_hash(value)
|
|
634
|
+
return normalize_watermark_size_hash(hash) if hash
|
|
635
|
+
|
|
636
|
+
segments = split_segments(value, min: 1, max: 3)
|
|
637
|
+
return unless segments
|
|
638
|
+
|
|
639
|
+
width = normalize_integer(segments[0], min: 1)
|
|
640
|
+
height = normalize_integer(segments[1], min: 1) if segments.length > 1
|
|
641
|
+
scale = normalize_number(segments[2], min: 0.000001) if segments.length > 2
|
|
642
|
+
|
|
643
|
+
contiguous_segments([width], [height, scale].first(segments.length - 1))
|
|
644
|
+
end
|
|
645
|
+
|
|
646
|
+
def normalize_watermark_size_hash(hash)
|
|
647
|
+
width = normalize_integer(field(hash, 'width', 'w'), min: 1)
|
|
648
|
+
height = normalize_integer(field(hash, 'height', 'h'), min: 1)
|
|
649
|
+
scale = normalize_number(field(hash, 'scale'), min: 0.000001)
|
|
650
|
+
|
|
651
|
+
contiguous_segments([width], [height, scale])
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
def normalize_text_or_json_object(value)
|
|
655
|
+
return normalize_json_string(value) if json_object_string?(value)
|
|
656
|
+
|
|
657
|
+
hash = value_hash(value)
|
|
658
|
+
return json_object(hash) if hash
|
|
659
|
+
|
|
660
|
+
normalize_non_empty_string(value)
|
|
661
|
+
end
|
|
662
|
+
|
|
663
|
+
def normalize_watermark_url(value)
|
|
664
|
+
string = normalize_non_empty_string(value)
|
|
665
|
+
return unless string
|
|
666
|
+
|
|
667
|
+
return Base64.strict_encode64(string) if valid_https_url?(string)
|
|
668
|
+
|
|
669
|
+
string if valid_base64_https_url?(string)
|
|
670
|
+
end
|
|
671
|
+
|
|
672
|
+
def normalize_zoom(value)
|
|
673
|
+
hash = value_hash(value)
|
|
674
|
+
if hash
|
|
675
|
+
factor = normalize_number(field(hash, 'factor'), min: 0.000001)
|
|
676
|
+
gravity = normalize_gravity(field(hash, 'gravity', 'g'))
|
|
677
|
+
|
|
678
|
+
return contiguous_segments([factor], [gravity])
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
segments = split_segments(value, min: 1, max: 2)
|
|
682
|
+
return unless segments
|
|
683
|
+
|
|
684
|
+
factor = normalize_number(segments[0], min: 0.000001)
|
|
685
|
+
gravity = normalize_gravity(segments[1]) if segments.length > 1
|
|
686
|
+
|
|
687
|
+
contiguous_segments([factor], [gravity].first(segments.length - 1))
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
def normalize_top_level_keys(hash)
|
|
691
|
+
hash.to_h.transform_keys(&:to_sym)
|
|
692
|
+
end
|
|
693
|
+
|
|
694
|
+
def value_hash(value)
|
|
695
|
+
return unless value.respond_to?(:to_hash)
|
|
696
|
+
|
|
697
|
+
value.to_hash.transform_keys(&:to_s)
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
def field(hash, *names)
|
|
701
|
+
names.each do |name|
|
|
702
|
+
return hash[name] if hash.key?(name)
|
|
703
|
+
end
|
|
704
|
+
|
|
705
|
+
nil
|
|
706
|
+
end
|
|
707
|
+
|
|
708
|
+
def field?(hash, *names)
|
|
709
|
+
names.any? { |name| hash.key?(name) }
|
|
710
|
+
end
|
|
711
|
+
|
|
712
|
+
def split_segments(value, min:, max:)
|
|
713
|
+
string = normalize_non_empty_string(value)
|
|
714
|
+
return unless string
|
|
715
|
+
|
|
716
|
+
segments = string.split(':', -1)
|
|
717
|
+
return unless segments.length.between?(min, max)
|
|
718
|
+
return if segments.any?(&:empty?)
|
|
719
|
+
|
|
720
|
+
segments
|
|
721
|
+
end
|
|
722
|
+
|
|
723
|
+
def contiguous_segments(required, optional)
|
|
724
|
+
return if required.any?(&:nil?)
|
|
725
|
+
|
|
726
|
+
last_present = optional.rindex { |segment| !segment.nil? }
|
|
727
|
+
selected_optional = last_present.nil? ? [] : optional[0..last_present]
|
|
728
|
+
return if selected_optional.any?(&:nil?)
|
|
729
|
+
|
|
730
|
+
(required + selected_optional).join(':')
|
|
731
|
+
end
|
|
732
|
+
|
|
733
|
+
def normalize_boolean_flag(value)
|
|
734
|
+
parse_boolean(value) == true ? 'true' : nil
|
|
735
|
+
end
|
|
736
|
+
|
|
737
|
+
def normalize_boolean_or_auto(value)
|
|
738
|
+
return 'auto' if normalize_string(value) == 'auto'
|
|
739
|
+
|
|
740
|
+
boolean = parse_boolean(value)
|
|
741
|
+
return 'true' if boolean == true
|
|
742
|
+
|
|
743
|
+
'false' if boolean == false
|
|
744
|
+
end
|
|
745
|
+
|
|
746
|
+
def parse_boolean(value)
|
|
747
|
+
return true if value == true
|
|
748
|
+
return false if value == false
|
|
749
|
+
return true if value == 1
|
|
750
|
+
return false if value.is_a?(Numeric) && value.zero?
|
|
751
|
+
|
|
752
|
+
case normalize_string(value)
|
|
753
|
+
when 'true', '1'
|
|
754
|
+
true
|
|
755
|
+
when 'false', '0'
|
|
756
|
+
false
|
|
757
|
+
end
|
|
758
|
+
end
|
|
759
|
+
|
|
760
|
+
def normalize_true_or_number(value, min: nil, max: nil)
|
|
761
|
+
boolean = parse_boolean(value)
|
|
762
|
+
return 'true' if boolean == true
|
|
763
|
+
return if boolean == false
|
|
764
|
+
|
|
765
|
+
normalize_number(value, min: min, max: max)
|
|
766
|
+
end
|
|
767
|
+
|
|
768
|
+
def normalize_integer(value, min: nil, max: nil)
|
|
769
|
+
integer = parse_integer(value)
|
|
770
|
+
return if integer.nil?
|
|
771
|
+
return if min && integer < min
|
|
772
|
+
return if max && integer > max
|
|
773
|
+
|
|
774
|
+
integer.to_s
|
|
775
|
+
end
|
|
776
|
+
|
|
777
|
+
def parse_integer(value)
|
|
778
|
+
return value if value.is_a?(Integer)
|
|
779
|
+
|
|
780
|
+
if value.is_a?(Float)
|
|
781
|
+
return unless value.finite? && value == value.to_i
|
|
782
|
+
|
|
783
|
+
return value.to_i
|
|
784
|
+
end
|
|
785
|
+
|
|
786
|
+
string = normalize_non_empty_string(value)
|
|
787
|
+
return unless string&.match?(/\A[+-]?\d+\z/)
|
|
788
|
+
|
|
789
|
+
Integer(string)
|
|
790
|
+
end
|
|
791
|
+
|
|
792
|
+
def normalize_number(value, min: nil, max: nil)
|
|
793
|
+
number = parse_number(value)
|
|
794
|
+
return if number.nil?
|
|
795
|
+
return if min && number < min
|
|
796
|
+
return if max && number > max
|
|
797
|
+
|
|
798
|
+
format_number(number)
|
|
799
|
+
end
|
|
800
|
+
|
|
801
|
+
def parse_number(value)
|
|
802
|
+
return value.to_f if value.is_a?(Numeric) && value.to_f.finite?
|
|
803
|
+
|
|
804
|
+
string = normalize_non_empty_string(value)
|
|
805
|
+
return unless string
|
|
806
|
+
|
|
807
|
+
number = Float(string)
|
|
808
|
+
return unless number.finite?
|
|
809
|
+
|
|
810
|
+
number
|
|
811
|
+
rescue ArgumentError, TypeError
|
|
812
|
+
nil
|
|
813
|
+
end
|
|
814
|
+
|
|
815
|
+
def format_number(number)
|
|
816
|
+
return number.to_i.to_s if number == number.to_i
|
|
817
|
+
|
|
818
|
+
number.to_s
|
|
819
|
+
end
|
|
820
|
+
|
|
821
|
+
def normalize_enum(value, values, aliases: {})
|
|
822
|
+
string = normalize_string(value)
|
|
823
|
+
return aliases[string] if aliases.key?(string)
|
|
824
|
+
|
|
825
|
+
string if values.include?(string)
|
|
826
|
+
end
|
|
827
|
+
|
|
828
|
+
def normalize_gravity(value)
|
|
829
|
+
string = normalize_string(value)
|
|
830
|
+
return GRAVITY_ALIASES[string] if GRAVITY_ALIASES.key?(string)
|
|
831
|
+
|
|
832
|
+
string if GRAVITY_VALUES.include?(string)
|
|
833
|
+
end
|
|
834
|
+
|
|
835
|
+
def normalize_color(value)
|
|
836
|
+
string = normalize_non_empty_string(value)
|
|
837
|
+
return unless string
|
|
838
|
+
|
|
839
|
+
string = string.delete_prefix('#')
|
|
840
|
+
return string.downcase if string.match?(/\A(?:[[:xdigit:]]{3}|[[:xdigit:]]{6}|[[:xdigit:]]{8})\z/)
|
|
841
|
+
|
|
842
|
+
string if rgb_color?(string)
|
|
843
|
+
end
|
|
844
|
+
|
|
845
|
+
def rgb_color?(string)
|
|
846
|
+
segments = string.split(':', -1)
|
|
847
|
+
return false unless [3, 4].include?(segments.length)
|
|
848
|
+
|
|
849
|
+
red, green, blue = segments.first(3).map { |segment| parse_integer(segment) }
|
|
850
|
+
return false unless [red, green, blue].all? { |part| part&.between?(0, 255) }
|
|
851
|
+
return true if segments.length == 3
|
|
852
|
+
|
|
853
|
+
alpha = parse_number(segments[3])
|
|
854
|
+
alpha&.between?(0, 1)
|
|
855
|
+
end
|
|
856
|
+
|
|
857
|
+
def normalize_color_list(value)
|
|
858
|
+
colors = value.is_a?(Array) ? value : normalize_non_empty_string(value)&.split(',', -1)
|
|
859
|
+
return unless colors&.length&.>= 2
|
|
860
|
+
|
|
861
|
+
normalized = colors.map { |color| normalize_color(color) }
|
|
862
|
+
return if normalized.any?(&:nil?)
|
|
863
|
+
|
|
864
|
+
normalized.join(',')
|
|
865
|
+
end
|
|
866
|
+
|
|
867
|
+
def normalize_non_empty_string(value)
|
|
868
|
+
string = value.to_s.strip
|
|
869
|
+
return if string.empty?
|
|
870
|
+
|
|
871
|
+
string
|
|
872
|
+
end
|
|
873
|
+
|
|
874
|
+
def normalize_string(value)
|
|
875
|
+
normalize_non_empty_string(value)&.downcase
|
|
876
|
+
end
|
|
877
|
+
|
|
878
|
+
def json_object_string?(value)
|
|
879
|
+
string = normalize_non_empty_string(value)
|
|
880
|
+
return false unless string&.start_with?('{')
|
|
881
|
+
|
|
882
|
+
JSON.parse(string).is_a?(Hash)
|
|
883
|
+
rescue JSON::ParserError
|
|
884
|
+
false
|
|
885
|
+
end
|
|
886
|
+
|
|
887
|
+
def normalize_json_string(value)
|
|
888
|
+
normalize_non_empty_string(value)
|
|
889
|
+
end
|
|
890
|
+
|
|
891
|
+
def compact_json_object(input_hash, fields)
|
|
892
|
+
return unless fields.values.any?
|
|
893
|
+
return if fields.any? { |key, item| field?(input_hash, key) && item.nil? }
|
|
894
|
+
|
|
895
|
+
JSON.generate(fields.compact)
|
|
896
|
+
end
|
|
897
|
+
|
|
898
|
+
def json_object(hash)
|
|
899
|
+
return if hash.empty?
|
|
900
|
+
|
|
901
|
+
JSON.generate(camelize_hash_keys(hash))
|
|
902
|
+
end
|
|
903
|
+
|
|
904
|
+
def camelize_hash_keys(hash)
|
|
905
|
+
hash.each_with_object({}) do |(key, value), result|
|
|
906
|
+
result[camelize_key(key)] = value.is_a?(Hash) ? camelize_hash_keys(value) : value
|
|
907
|
+
end
|
|
908
|
+
end
|
|
909
|
+
|
|
910
|
+
def camelize_key(key)
|
|
911
|
+
key.to_s.gsub(/_([a-z])/) { Regexp.last_match(1).upcase }
|
|
912
|
+
end
|
|
913
|
+
|
|
914
|
+
def valid_https_url?(value)
|
|
915
|
+
uri = URI.parse(value)
|
|
916
|
+
uri.is_a?(URI::HTTPS) && uri.host && !uri.host.empty?
|
|
917
|
+
rescue URI::InvalidURIError
|
|
918
|
+
false
|
|
919
|
+
end
|
|
920
|
+
|
|
921
|
+
def valid_base64_https_url?(value)
|
|
922
|
+
decoded = Base64.strict_decode64(value)
|
|
923
|
+
valid_https_url?(decoded)
|
|
924
|
+
rescue ArgumentError
|
|
925
|
+
false
|
|
926
|
+
end
|
|
927
|
+
end
|
|
928
|
+
# rubocop:enable Metrics/ModuleLength
|
|
929
|
+
end
|
data/lib/imgwire/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: imgwire
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Blackhawk Software, LLC
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-04-
|
|
11
|
+
date: 2026-04-30 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: typhoeus
|
|
@@ -24,6 +24,20 @@ dependencies:
|
|
|
24
24
|
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '1.4'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: base64
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0.2'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0.2'
|
|
27
41
|
- !ruby/object:Gem::Dependency
|
|
28
42
|
name: rubocop
|
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -130,6 +144,7 @@ files:
|
|
|
130
144
|
- lib/imgwire/resources/images_resource.rb
|
|
131
145
|
- lib/imgwire/resources/metrics_resource.rb
|
|
132
146
|
- lib/imgwire/uploads.rb
|
|
147
|
+
- lib/imgwire/url_transformations.rb
|
|
133
148
|
- lib/imgwire/version.rb
|
|
134
149
|
homepage: https://github.com/Blackhawk-Software/imgwire-ruby
|
|
135
150
|
licenses:
|