imglab 0.1.0
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 +7 -0
- data/.gitignore +9 -0
- data/.yardopts +1 -0
- data/Gemfile +8 -0
- data/LICENSE +21 -0
- data/README.md +290 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/imglab.gemspec +26 -0
- data/lib/imglab/color.rb +195 -0
- data/lib/imglab/position.rb +55 -0
- data/lib/imglab/signature.rb +22 -0
- data/lib/imglab/source.rb +63 -0
- data/lib/imglab/utils.rb +29 -0
- data/lib/imglab/version.rb +3 -0
- data/lib/imglab.rb +72 -0
- metadata +61 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 2d52305397eb47d122dcfb0211c013ba7d7d11958bb45854949321a3972c87c7
|
|
4
|
+
data.tar.gz: 7bf0e1b1325ba9ee9e54be5b3c56cd43de1cc386fa902aba7c9a7468a458fc3f
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: eca396e106d905482ea8ed1a8c081b3bc9d9e5d5380b2af576ae4ab3a36f1318e7585f9a10c90fed9f7fa85be8c75d1b3c971a5bf205e7fd212e05dd5d8dc271
|
|
7
|
+
data.tar.gz: b2255cf65a8887bbdd1dbcff7a52c9bf724b8d05ee4b2127931f0f8ed8b424a2c4df4808d41fc63374f959c3e000c08ee54e165d9e4e7c882d1884f8d332868e
|
data/.gitignore
ADDED
data/.yardopts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
lib/**/*.rb --readme README.md --files LICENSE
|
data/Gemfile
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 imglab
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# imglab
|
|
2
|
+
|
|
3
|
+
`imglab` is the official Ruby gem to integrate with imglab services.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem "imglab", "~> 0.1.0"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
And then execute:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
$ bundle install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or install it yourself as:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
$ gem install imglab
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Ruby compatibility
|
|
26
|
+
|
|
27
|
+
`imglab` has been successfully tested on the following Ruby versions: `3.0`, `2.7`, `2.6`, `2.5`, `2.4`, `2.3`, `2.2`, `2.1` and `2.0`.
|
|
28
|
+
|
|
29
|
+
## Generating URLs
|
|
30
|
+
|
|
31
|
+
You can use `Imglab.url` function to generate imglab compatible URLs for your application.
|
|
32
|
+
|
|
33
|
+
The easiest way to generate a URL is to specify the `source_name`, `path` and required `parameters`:
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
Imglab.url("assets", "image.jpeg", width: 500, height: 600)
|
|
37
|
+
"https://cdn.imglab.io/assets/image.jpeg?width=500&height=600"
|
|
38
|
+
|
|
39
|
+
Imglab.url("avatars", "user-01.jpeg", width: 300, height: 300, mode: :crop, crop: :face, format: :webp)
|
|
40
|
+
"https://cdn.imglab.io/avatars/user-01.jpeg?width=300&height=300&mode=crop&crop=face&format=webp"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
If some specific settings are required for the source you can use an instance of `Imglab::Source` class instead of a `string` source name:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
Imglab.url(Imglab::Source.new("assets"), "image.jpeg", width: 500, height: 600)
|
|
47
|
+
"https://cdn.imglab.io/assets/image.jpeg?width=500&height=600"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Using secure image sources
|
|
51
|
+
|
|
52
|
+
For sources that require signed URLs you can specify `secure_key` and `secure_salt` attributes for the source:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
source = Imglab::Source.new(secure_key: "assets-secure-key", secure_salt: "assets-secure-salt")
|
|
56
|
+
|
|
57
|
+
Imglab.url(source, "image.jpeg", width: 500, height: 600)
|
|
58
|
+
"https://cdn.imglab.io/assets/image.jpeg?width=500&height=600&signature=generated-signature"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`signature` query parameter will be automatically generated and attached to the returned URL.
|
|
62
|
+
|
|
63
|
+
> Note: `secure_key` and `secure_salt` attributes are secrets that should not be added to a code repository. Please use environment vars or other secure method to use them in your application.
|
|
64
|
+
|
|
65
|
+
### Using HTTP instead of HTTPS
|
|
66
|
+
|
|
67
|
+
In the case that HTTP schema is required instead of HTTPS you can set `https` attribute to `false` when creating the source:
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
Imglab.url(Imglab::Source.new("assets", https: false), "image.jpeg", width: 500, height: 600)
|
|
71
|
+
"http://cdn.imglab.io/assets/image.jpeg?width=500&height=600"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
> Note: HTTPS is the default and recommended way to generate URLs with imglab.
|
|
75
|
+
|
|
76
|
+
### Specifying parameters
|
|
77
|
+
|
|
78
|
+
Any parameter from the imglab API can be used to generate URLs with `Imglab.url` method. For parameters that required dashes characters like `trim-color` you can use regular underscore Ruby symbols like `:trim_color` those will be normalized in the URL generation to it's correct form:
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
Imglab.url("assets", "image.jpeg", trim: "color", trim_color: "black")
|
|
82
|
+
"https://cdn.imglab.io/assets/image.jpeg?trim=color&trim-color=black"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
It is possible to use strings too:
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
Imglab.url("assets", "image.jpeg", "trim" => "color", "trim-color" => "black")
|
|
89
|
+
"https://cdn.imglab.io/assets/image.jpeg?trim=color&trim-color=black"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
And quoted symbols for Ruby version >= 2.2:
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
Imglab.url("assets", "image.jpeg", trim: "color", "trim-color": "black")
|
|
96
|
+
"https://cdn.imglab.io/assets/image.jpeg?trim=color&trim-color=black"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Specifying color parameters
|
|
100
|
+
|
|
101
|
+
Some imglab parameters can receive a color as value. It is possible to specify these color values as strings:
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
# Specifying a RGB color as string
|
|
105
|
+
Imglab.url("assets", "image.jpeg", width: 500, height: 600, mode: :contain, background_color: "255,0,0")
|
|
106
|
+
"https://cdn.imglab.io/assets/image.jpeg?width=500&height=600&mode=contain&background-color=255%2C0%2C0"
|
|
107
|
+
|
|
108
|
+
# Specifying a RGBA color as string
|
|
109
|
+
Imglab.url("assets", "image.jpeg", width: 500, height: 600, mode: :contain, background_color: "255,0,0,128")
|
|
110
|
+
"https://cdn.imglab.io/assets/image.jpeg?width=500&height=600&mode=contain&background-color=255%2C0%2C0%2C128"
|
|
111
|
+
|
|
112
|
+
# Specifying a named color as string
|
|
113
|
+
Imglab.url("assets", "image.jpeg", width: 500, height: 600, mode: :contain, background_color: "red")
|
|
114
|
+
"https://cdn.imglab.io/assets/image.jpeg?width=500&height=600&mode=contain&background-color=red"
|
|
115
|
+
|
|
116
|
+
# Specifying a hexadecimal color as string
|
|
117
|
+
Imglab.url("assets", "image.jpeg", width: 500, height: 600, mode: :contain, background_color: "F00")
|
|
118
|
+
"https://cdn.imglab.io/assets/image.jpeg?width=500&height=600&mode=contain&background-color=F00"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
You can additionally use `Imglab::Color` helpers to specify these color values:
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
# Remember to include Imglab::Color module before use these helpers
|
|
125
|
+
include Imglab::Color
|
|
126
|
+
|
|
127
|
+
# Using color helper for a RGB color
|
|
128
|
+
Imglab.url("assets", "image.jpeg", width: 500, height: 600, mode: "contain", background_color: color(255, 0, 0))
|
|
129
|
+
"https://cdn.imglab.io/assets/image.jpeg?width=500&height=600&mode=contain&background-color=255%2C0%2C0"
|
|
130
|
+
|
|
131
|
+
# Using color helper for a RGBA color
|
|
132
|
+
Imglab.url("assets", "image.jpeg", width: 500, height: 600, mode: "contain", background_color: color(255, 0, 0, 128))
|
|
133
|
+
"https://cdn.imglab.io/assets/image.jpeg?width=500&height=600&mode=contain&background-color=255%2C0%2C0%2C128"
|
|
134
|
+
|
|
135
|
+
# Using color helper for a named color
|
|
136
|
+
Imglab.url("assets", "image.jpeg", width: 500, height: 600, mode: "contain", background_color: color("red"))
|
|
137
|
+
"https://cdn.imglab.io/assets/image.jpeg?width=500&height=600&mode=contain&background-color=red"
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
> Note: specify hexadecimal color values using `Imglab::Color` helpers is not allowed. You can use strings instead.
|
|
141
|
+
|
|
142
|
+
### Specifying position parameters
|
|
143
|
+
|
|
144
|
+
Some imglab parameters can receive a position as value. It is possible to specify these values using strings:
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
# Specifying a horizontal and vertical position as string
|
|
148
|
+
Imglab.url("assets", "image.jpeg", width: 500, height: 500, mode: "crop", crop: "left,top")
|
|
149
|
+
"https://cdn.imglab.io/assets/image.jpeg?width=500&height=500&mode=crop&crop=left%2Ctop"
|
|
150
|
+
|
|
151
|
+
# Specifying a vertical and horizontal position as string
|
|
152
|
+
Imglab.url("assets", "image.jpeg", width: 500, height: 500, mode: "crop", crop: "top,left")
|
|
153
|
+
"https://cdn.imglab.io/assets/image.jpeg?width=500&height=500&mode=crop&crop=top%2Cleft"
|
|
154
|
+
|
|
155
|
+
# Specifying a position as string
|
|
156
|
+
Imglab.url("assets", "image.jpeg", width: 500, height: 500, mode: "crop", crop: "left")
|
|
157
|
+
"https://cdn.imglab.io/assets/image.jpeg?width=500&height=500&mode=crop&crop=left"
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
You can additionally use `Imglab::Position` helpers to specify these position values:
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
# Remember to include Imglab::Position module before use these helpers
|
|
164
|
+
include Imglab::Position
|
|
165
|
+
|
|
166
|
+
# Using position helper for a horizontal and vertical position
|
|
167
|
+
Imglab.url("assets", "image.jpeg", width: 500, height: 500, mode: "crop", crop: position("left", "top"))
|
|
168
|
+
"https://cdn.imglab.io/assets/image.jpeg?width=500&height=500&mode=crop&crop=left%2Ctop"
|
|
169
|
+
|
|
170
|
+
# Using position macro for a vertical and horizontal position
|
|
171
|
+
Imglab.url("assets", "image.jpeg", width: 500, height: 500, mode: "crop", crop: position("top", "left"))
|
|
172
|
+
"https://cdn.imglab.io/assets/image.jpeg?width=500&height=500&mode=crop&crop=top%2Cleft"
|
|
173
|
+
|
|
174
|
+
# Using position macro for a position
|
|
175
|
+
Imglab.url("assets", "image.jpeg", width: 500, height: 500, mode: "crop", crop: position("left"))
|
|
176
|
+
"https://cdn.imglab.io/assets/image.jpeg?width=500&height=500&mode=crop&crop=left"
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Specifying URL parameters
|
|
180
|
+
|
|
181
|
+
Some imglab parameters can receive URLs as values. It is possible to specify these parameter values as strings.
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
Imglab.url("assets", "image.jpeg", width: 500, height: 600, watermark: "logo.svg")
|
|
185
|
+
"https://cdn.imglab.io/assets/image.jpeg?width=500&height=600&watermark=logo.svg"
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
And even use parameters if required:
|
|
189
|
+
|
|
190
|
+
```ruby
|
|
191
|
+
Imglab.url("assets", "image.jpeg", width: 500, height: 600, watermark: "logo.svg?width=100&format=png")
|
|
192
|
+
"https://cdn.imglab.io/assets/image.jpeg?width=500&height=600&watermark=logo.svg%3Fwidth%3D100%26format%3Dpng"
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Additionally you can use nested `Imglab.url` calls to specify these URL values:
|
|
196
|
+
|
|
197
|
+
```ruby
|
|
198
|
+
Imglab.url(
|
|
199
|
+
"assets",
|
|
200
|
+
"image.jpeg",
|
|
201
|
+
width: 500,
|
|
202
|
+
height: 600,
|
|
203
|
+
watermark: Imglab.url("assets", "logo.svg", width: 100, format: "png")
|
|
204
|
+
)
|
|
205
|
+
"https://cdn.imglab.io/assets/image.jpeg?width=500&height=600&watermark=https%3A%2F%2Fcdn.imglab.io%2Fassets%2Flogo.svg%3Fwidth%3D100%26format%3Dpng"
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
If the resource is located in a different source we can specify it using `Imglab.url`:
|
|
209
|
+
|
|
210
|
+
```ruby
|
|
211
|
+
Imglab.url(
|
|
212
|
+
"assets",
|
|
213
|
+
"image.jpeg",
|
|
214
|
+
width: 500,
|
|
215
|
+
height: 600,
|
|
216
|
+
watermark: Imglab.url("marketing", "logo.svg", width: 100, format: "png")
|
|
217
|
+
)
|
|
218
|
+
"https://cdn.imglab.io/assets/image.jpeg?width=500&height=600&watermark=https%3A%2F%2Fcdn.imglab.io%2Fmarketing%2Flogo.svg%3Fwidth%3D100%26format%3Dpng"
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Using secure sources for URLs parameter values is possible too:
|
|
222
|
+
|
|
223
|
+
```ruby
|
|
224
|
+
marketing_source = Imglab::Source.new("marketing", secure_key: "marketing-secure-key", secure_salt: "marketing-secure-salt")
|
|
225
|
+
|
|
226
|
+
Imglab.url(
|
|
227
|
+
"assets",
|
|
228
|
+
"image.jpeg",
|
|
229
|
+
width: 500,
|
|
230
|
+
height: 600,
|
|
231
|
+
watermark: Imglab.url(marketing_source, "logo.svg", width: 100, format: "png")
|
|
232
|
+
)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
`signature` query parameter will be automatically generated and attached to the nested URL value.
|
|
236
|
+
|
|
237
|
+
## Generating URLs for on-premises imglab server
|
|
238
|
+
|
|
239
|
+
For on-premises imglab server is possible to define custom sources pointing to your server location.
|
|
240
|
+
|
|
241
|
+
* `:https` - a `boolean` value specifying if the source should use https or not (default: `true`)
|
|
242
|
+
* `:host` - a `string` specifying the host where the imglab server is located. (default: `cdn.imglab.io`)
|
|
243
|
+
* `:port` - a `integer` specifying a port where the imglab server is located.
|
|
244
|
+
* `:subdomains` - a `boolean` value specifying if the source should be specified using subdomains instead of using the path. (default: `false`)
|
|
245
|
+
|
|
246
|
+
If we have our on-premises imglab server at `http://imglab.mycompany.com:8080` with a source named `web-images` we can use the following source settings to access a `logo.png` image:
|
|
247
|
+
|
|
248
|
+
```ruby
|
|
249
|
+
source = Imglab::Source.new("web-images", https: false, host: "imglab.mycompany.com", port: 8080)
|
|
250
|
+
|
|
251
|
+
Imglab.url(source, "logo.png", width: 300, height: 300, format: "png")
|
|
252
|
+
"http://imglab.mycompany.com:8080/web-images/logo.png?width=300&height=300&format=png"
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
It is possible to use secure sources too:
|
|
256
|
+
|
|
257
|
+
```ruby
|
|
258
|
+
source = Imglab::Source.new(
|
|
259
|
+
"web-images",
|
|
260
|
+
https: false,
|
|
261
|
+
host: "imglab.mycompany.com",
|
|
262
|
+
port: 8080,
|
|
263
|
+
secure_key: "web-images-secure-key",
|
|
264
|
+
secure_salt: "web-images-secure-salt"
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
Imglab.url(source, "logo.png", width: 300, height: 300, format: "png")
|
|
268
|
+
"http://imglab.mycompany.com:8080/web-images/logo.png?width=300&height=300&format=png&signature=generated-signature"
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Using sudomains sources
|
|
272
|
+
|
|
273
|
+
In the case that your on-premises imglab server is configured to use source names as subdomains you can set `subdomains` attribute to `true` to generate URLs using subdomains:
|
|
274
|
+
|
|
275
|
+
```ruby
|
|
276
|
+
source = Imglab::Source.new(
|
|
277
|
+
"web-images",
|
|
278
|
+
https: false,
|
|
279
|
+
host: "imglab.mycompany.com",
|
|
280
|
+
port: 8080,
|
|
281
|
+
subdomains: true
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
Imglab.url(source, "marketing/logo.png", width: 300, height: 300, format: "png")
|
|
285
|
+
"http://web-images.imglab.mycompany.com:8080/marketing/logo.png?width=300&height=300&format=png"
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## License
|
|
289
|
+
|
|
290
|
+
imglab source code is released under [MIT License](LICENSE).
|
data/Rakefile
ADDED
data/bin/console
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "imglab"
|
|
5
|
+
|
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
8
|
+
|
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
10
|
+
# require "pry"
|
|
11
|
+
# Pry.start
|
|
12
|
+
|
|
13
|
+
require "irb"
|
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/imglab.gemspec
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require_relative "lib/imglab/version"
|
|
2
|
+
|
|
3
|
+
Gem::Specification.new do |spec|
|
|
4
|
+
spec.name = "imglab"
|
|
5
|
+
spec.version = Imglab::VERSION
|
|
6
|
+
spec.authors = ["imglab"]
|
|
7
|
+
spec.email = ["support@imglab.io"]
|
|
8
|
+
|
|
9
|
+
spec.summary = %q{Official imglab Ruby library.}
|
|
10
|
+
spec.description = %q{Official Ruby library to integrate with imglab services.}
|
|
11
|
+
spec.homepage = "https://github.com/imglab-io/imglab-rb"
|
|
12
|
+
spec.license = "MIT"
|
|
13
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.0.0")
|
|
14
|
+
|
|
15
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
16
|
+
spec.metadata["source_code_uri"] = "https://github.com/imglab-io/imglab-rb"
|
|
17
|
+
|
|
18
|
+
# Specify which files should be added to the gem when it is released.
|
|
19
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
20
|
+
spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
|
|
21
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|.github)/}) }
|
|
22
|
+
end
|
|
23
|
+
spec.bindir = "exe"
|
|
24
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
25
|
+
spec.require_paths = ["lib"]
|
|
26
|
+
end
|
data/lib/imglab/color.rb
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
module Imglab::Color
|
|
2
|
+
extend self
|
|
3
|
+
|
|
4
|
+
COLORS = %w[
|
|
5
|
+
aliceblue
|
|
6
|
+
antiquewhite
|
|
7
|
+
aqua
|
|
8
|
+
aquamarine
|
|
9
|
+
azure
|
|
10
|
+
beige
|
|
11
|
+
bisque
|
|
12
|
+
black
|
|
13
|
+
blanchedalmond
|
|
14
|
+
blue
|
|
15
|
+
blueviolet
|
|
16
|
+
brown
|
|
17
|
+
burlywood
|
|
18
|
+
cadetblue
|
|
19
|
+
chartreuse
|
|
20
|
+
chocolate
|
|
21
|
+
coral
|
|
22
|
+
cornflowerblue
|
|
23
|
+
cornsilk
|
|
24
|
+
crimson
|
|
25
|
+
cyan
|
|
26
|
+
darkblue
|
|
27
|
+
darkcyan
|
|
28
|
+
darkgoldenrod
|
|
29
|
+
darkgray
|
|
30
|
+
darkgreen
|
|
31
|
+
darkgrey
|
|
32
|
+
darkkhaki
|
|
33
|
+
darkmagenta
|
|
34
|
+
darkolivegreen
|
|
35
|
+
darkorange
|
|
36
|
+
darkorchid
|
|
37
|
+
darkred
|
|
38
|
+
darksalmon
|
|
39
|
+
darkseagreen
|
|
40
|
+
darkslateblue
|
|
41
|
+
darkslategray
|
|
42
|
+
darkslategrey
|
|
43
|
+
darkturquoise
|
|
44
|
+
darkviolet
|
|
45
|
+
deeppink
|
|
46
|
+
deepskyblue
|
|
47
|
+
dimgray
|
|
48
|
+
dimgrey
|
|
49
|
+
dodgerblue
|
|
50
|
+
firebrick
|
|
51
|
+
floralwhite
|
|
52
|
+
forestgreen
|
|
53
|
+
fuchsia
|
|
54
|
+
gainsboro
|
|
55
|
+
ghostwhite
|
|
56
|
+
gold
|
|
57
|
+
goldenrod
|
|
58
|
+
gray
|
|
59
|
+
green
|
|
60
|
+
greenyellow
|
|
61
|
+
grey
|
|
62
|
+
honeydew
|
|
63
|
+
hotpink
|
|
64
|
+
indianred
|
|
65
|
+
indigo
|
|
66
|
+
ivory
|
|
67
|
+
khaki
|
|
68
|
+
lavender
|
|
69
|
+
lavenderblush
|
|
70
|
+
lawngreen
|
|
71
|
+
lemonchiffon
|
|
72
|
+
lightblue
|
|
73
|
+
lightcoral
|
|
74
|
+
lightcyan
|
|
75
|
+
lightgoldenrodyellow
|
|
76
|
+
lightgray
|
|
77
|
+
lightgreen
|
|
78
|
+
lightgrey
|
|
79
|
+
lightpink
|
|
80
|
+
lightsalmon
|
|
81
|
+
lightseagreen
|
|
82
|
+
lightskyblue
|
|
83
|
+
lightslategray
|
|
84
|
+
lightslategrey
|
|
85
|
+
lightsteelblue
|
|
86
|
+
lightyellow
|
|
87
|
+
lime
|
|
88
|
+
limegreen
|
|
89
|
+
linen
|
|
90
|
+
magenta
|
|
91
|
+
maroon
|
|
92
|
+
mediumaquamarine
|
|
93
|
+
mediumblue
|
|
94
|
+
mediumorchid
|
|
95
|
+
mediumpurple
|
|
96
|
+
mediumseagreen
|
|
97
|
+
mediumslateblue
|
|
98
|
+
mediumspringgreen
|
|
99
|
+
mediumturquoise
|
|
100
|
+
mediumvioletred
|
|
101
|
+
midnightblue
|
|
102
|
+
mintcream
|
|
103
|
+
mistyrose
|
|
104
|
+
moccasin
|
|
105
|
+
navajowhite
|
|
106
|
+
navy
|
|
107
|
+
oldlace
|
|
108
|
+
olive
|
|
109
|
+
olivedrab
|
|
110
|
+
orange
|
|
111
|
+
orangered
|
|
112
|
+
orchid
|
|
113
|
+
palegoldenrod
|
|
114
|
+
palegreen
|
|
115
|
+
paleturquoise
|
|
116
|
+
palevioletred
|
|
117
|
+
papayawhip
|
|
118
|
+
peachpuff
|
|
119
|
+
peru
|
|
120
|
+
pink
|
|
121
|
+
plum
|
|
122
|
+
powderblue
|
|
123
|
+
purple
|
|
124
|
+
rebeccapurple
|
|
125
|
+
red
|
|
126
|
+
rosybrown
|
|
127
|
+
royalblue
|
|
128
|
+
saddlebrown
|
|
129
|
+
salmon
|
|
130
|
+
sandybrown
|
|
131
|
+
seagreen
|
|
132
|
+
seashell
|
|
133
|
+
sienna
|
|
134
|
+
silver
|
|
135
|
+
skyblue
|
|
136
|
+
slateblue
|
|
137
|
+
slategray
|
|
138
|
+
slategrey
|
|
139
|
+
snow
|
|
140
|
+
springgreen
|
|
141
|
+
steelblue
|
|
142
|
+
tan
|
|
143
|
+
teal
|
|
144
|
+
thistle
|
|
145
|
+
tomato
|
|
146
|
+
turquoise
|
|
147
|
+
violet
|
|
148
|
+
wheat
|
|
149
|
+
white
|
|
150
|
+
whitesmoke
|
|
151
|
+
yellow
|
|
152
|
+
yellowgreen
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
# Returns a formatted color value as string.
|
|
156
|
+
#
|
|
157
|
+
# @param args [Array<Integer>, String] the RGB, RGBA components as integers or a string with a named color.
|
|
158
|
+
# @return [String] the formatted color with the specified arguments.
|
|
159
|
+
# @raise [ArgumentError] when the specified arguments are not a valid color.
|
|
160
|
+
#
|
|
161
|
+
# @example Specify a RGB color
|
|
162
|
+
# Imglab::Color.color(255, 128, 128) #=> "255,128,128"
|
|
163
|
+
# @example Specify a RGBA color
|
|
164
|
+
# Imglab::Color.color(255, 128, 128, 64) #=> "255,128,128,64"
|
|
165
|
+
# @example Specify a named color
|
|
166
|
+
# Imglab::Color.color("white") #=> "white"
|
|
167
|
+
# @example Specify an invalid RGB color (raising ArgumentError exception)
|
|
168
|
+
# Imglab::Color.color(256, 255, 255) #=> ArgumentError: Invalid color
|
|
169
|
+
# @example Specify an invalid RGBA color (raising ArgumentError exception)
|
|
170
|
+
# Imglab::Color.color(255, 255, 255, 256) #=> ArgumentError: Invalid color
|
|
171
|
+
# @example Specify an invalid named color (raising ArgumentError exception)
|
|
172
|
+
# Imglab::Color.color("blues") #=> ArgumentError: Invalid color
|
|
173
|
+
def color(*args)
|
|
174
|
+
case
|
|
175
|
+
when args.size == 1 && COLORS.include?(args[0])
|
|
176
|
+
args[0]
|
|
177
|
+
when args.size == 3 && valid_components?(*args)
|
|
178
|
+
args.join(",")
|
|
179
|
+
when args.size == 4 && valid_components?(*args)
|
|
180
|
+
args.join(",")
|
|
181
|
+
else
|
|
182
|
+
raise ArgumentError.new("Invalid color")
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
private
|
|
187
|
+
|
|
188
|
+
def valid_components?(*components)
|
|
189
|
+
components.all? { |component| valid_component?(component) }
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def valid_component?(component)
|
|
193
|
+
component.is_a?(Integer) && component >= 0 && component <= 255
|
|
194
|
+
end
|
|
195
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module Imglab::Position
|
|
2
|
+
extend self
|
|
3
|
+
|
|
4
|
+
HORIZONTAL = %w[left center right]
|
|
5
|
+
VERTICAL = %w[top middle bottom]
|
|
6
|
+
|
|
7
|
+
# Returns a formatted position value as string.
|
|
8
|
+
#
|
|
9
|
+
# @param args [Array<String>, String] the position with two directions or one single direction as strings.
|
|
10
|
+
# @return [String] the formatted position with the specified arguments.
|
|
11
|
+
# @raise [ArgumentError] when the specified arguments are not a valid position.
|
|
12
|
+
#
|
|
13
|
+
# @example Specify a double direction position (horizontal, vertical order)
|
|
14
|
+
# Imglab::Position.position("left", "bottom") #=> "left,bottom"
|
|
15
|
+
# @example Specify a double direction position (vertical, horizontal order)
|
|
16
|
+
# Imglab::Position.position("bottom", "left") #=> "bottom,left"
|
|
17
|
+
# @example Specify a single direction position
|
|
18
|
+
# Imglab::Position.position("left") #=> "left"
|
|
19
|
+
# @example Specify an invalid double direction position (raising ArgumentError exception)
|
|
20
|
+
# Imglab::Position.position("left", "center") #=> ArgumentError: Invalid position
|
|
21
|
+
# @example Specify an invalid single direction position (raising ArgumentError exception)
|
|
22
|
+
# Imglab::Position.position("lefts") #=> ArgumentError: Invalid position
|
|
23
|
+
def position(*args)
|
|
24
|
+
case
|
|
25
|
+
when args.size == 1 && valid_position?(args[0])
|
|
26
|
+
args[0]
|
|
27
|
+
when args.size == 2 && valid_position?(*args)
|
|
28
|
+
args.join(",")
|
|
29
|
+
else
|
|
30
|
+
raise ArgumentError.new("Invalid position")
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def valid_position?(*args)
|
|
37
|
+
case
|
|
38
|
+
when args.size == 1 && valid_direction?(args[0])
|
|
39
|
+
true
|
|
40
|
+
when args.size == 2 && valid_directions?(args[0], args[1])
|
|
41
|
+
true
|
|
42
|
+
else
|
|
43
|
+
false
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def valid_direction?(direction)
|
|
48
|
+
HORIZONTAL.include?(direction) || VERTICAL.include?(direction)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def valid_directions?(direction_a, direction_b)
|
|
52
|
+
(HORIZONTAL.include?(direction_a) && VERTICAL.include?(direction_b)) ||
|
|
53
|
+
(HORIZONTAL.include?(direction_b) && VERTICAL.include?(direction_a))
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require "base64"
|
|
2
|
+
require "openssl"
|
|
3
|
+
|
|
4
|
+
class Imglab::Signature
|
|
5
|
+
# Returns a generated signature for a source, path and encoded parameters.
|
|
6
|
+
#
|
|
7
|
+
# @param source [Imglab::Source] the source used to generate the signature.
|
|
8
|
+
# @param path [String] the path of the resource.
|
|
9
|
+
# @param encoded_params [String] encoded query params of the URL to generate the signature.
|
|
10
|
+
# @return [String]
|
|
11
|
+
def self.generate(source, path, encoded_params = nil)
|
|
12
|
+
decoded_secure_key = Base64.decode64(source.secure_key)
|
|
13
|
+
decoded_secure_salt = Base64.decode64(source.secure_salt)
|
|
14
|
+
|
|
15
|
+
data = "#{decoded_secure_salt}/#{path}"
|
|
16
|
+
data = encoded_params ? "#{data}?#{encoded_params}" : data
|
|
17
|
+
|
|
18
|
+
hmac = OpenSSL::HMAC.digest(OpenSSL::Digest.new("sha256"), decoded_secure_key, data)
|
|
19
|
+
|
|
20
|
+
Base64.urlsafe_encode64(hmac).tr("=", "")
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
class Imglab::Source
|
|
2
|
+
DEFAULT_HTTPS = true
|
|
3
|
+
DEFAULT_HOST = "cdn.imglab.io"
|
|
4
|
+
DEFAULT_SUBDOMAINS = false
|
|
5
|
+
|
|
6
|
+
attr_reader :name, :https, :port, :secure_key, :secure_salt, :subdomains
|
|
7
|
+
|
|
8
|
+
# Returns a Imglab::Source instance with the specified options for the source.
|
|
9
|
+
#
|
|
10
|
+
# @param name [String] the name of the source.
|
|
11
|
+
# @param host [String] the host where the imglab server is located, only for imglab on-premises.
|
|
12
|
+
# @param https [Boolean] specify if the source should use https or not.
|
|
13
|
+
# @param port [Integer] the port where the imglab server is located, only for imglab on-premises.
|
|
14
|
+
# @param secure_key [String] the source secure key.
|
|
15
|
+
# @param secure_salt [String] the source secure salt.
|
|
16
|
+
# @param subdomains [Boolean] specify if the source should use subdomains to build the host name, only for imglab on-premises.
|
|
17
|
+
# @return [Imglab::Source] with the specified options.
|
|
18
|
+
def initialize(name, host: DEFAULT_HOST, https: DEFAULT_HTTPS, port: nil, secure_key: nil, secure_salt: nil, subdomains: DEFAULT_SUBDOMAINS)
|
|
19
|
+
@name = name
|
|
20
|
+
@host = host
|
|
21
|
+
@https = https
|
|
22
|
+
@port = port
|
|
23
|
+
@secure_key = secure_key
|
|
24
|
+
@secure_salt = secure_salt
|
|
25
|
+
@subdomains = subdomains
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Returns the URI scheme to be used with the source ("http" or "https").
|
|
29
|
+
#
|
|
30
|
+
# @return [String]
|
|
31
|
+
def scheme
|
|
32
|
+
@https ? "https" : "http"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Returns the host to be used with the source.
|
|
36
|
+
#
|
|
37
|
+
# @return [String]
|
|
38
|
+
def host
|
|
39
|
+
@subdomains ? "#{@name}.#{@host}" : @host
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Returns a the path to be used with the source.
|
|
43
|
+
#
|
|
44
|
+
# @param path [String]
|
|
45
|
+
# @return [String]
|
|
46
|
+
def path(path)
|
|
47
|
+
@subdomains ? path : File.join(@name, path)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Returns if the source is secure or not.
|
|
51
|
+
#
|
|
52
|
+
# @return [Boolean]
|
|
53
|
+
def secure?
|
|
54
|
+
@secure_key && @secure_salt
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Overrided inspect method to don't show sensitive attributes like secure_key and secure_salt.
|
|
58
|
+
#
|
|
59
|
+
# @return [String]
|
|
60
|
+
def inspect
|
|
61
|
+
"#<#{self.class.name}:#{object_id} @name=#{@name.inspect}, @host=#{@host.inspect}, @https=#{@https.inspect}, @port=#{@port.inspect}, @subdomains=#{@subdomains.inspect}>"
|
|
62
|
+
end
|
|
63
|
+
end
|
data/lib/imglab/utils.rb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
class Imglab::Utils
|
|
2
|
+
NORMALIZE_PATH_PREFIX_REGEXP = Regexp.compile(/\/*$/)
|
|
3
|
+
NORMALIZE_PATH_SUFFIX_REGEXP = Regexp.compile(/\A\/*/)
|
|
4
|
+
|
|
5
|
+
# Returns a normalized path where suffix and prefix slashes are removed.
|
|
6
|
+
#
|
|
7
|
+
# @param path [String]
|
|
8
|
+
# @return [String]
|
|
9
|
+
def self.normalize_path(path)
|
|
10
|
+
path.gsub(NORMALIZE_PATH_PREFIX_REGEXP, "").gsub(NORMALIZE_PATH_SUFFIX_REGEXP, "")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Returns normalized params, transforming keys with undercores to hyphens.
|
|
14
|
+
#
|
|
15
|
+
# @param params [Hash]
|
|
16
|
+
# @return [Hash]
|
|
17
|
+
def self.normalize_params(params)
|
|
18
|
+
params.inject({}) do |normalized_params, value|
|
|
19
|
+
normalized_params[dasherize(value[0])] = value[1]
|
|
20
|
+
normalized_params
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def self.dasherize(value)
|
|
27
|
+
value.to_s.gsub("_", "-")
|
|
28
|
+
end
|
|
29
|
+
end
|
data/lib/imglab.rb
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require "imglab/version"
|
|
2
|
+
require "imglab/source"
|
|
3
|
+
require "imglab/signature"
|
|
4
|
+
require "imglab/color"
|
|
5
|
+
require "imglab/position"
|
|
6
|
+
require "imglab/utils"
|
|
7
|
+
|
|
8
|
+
module Imglab
|
|
9
|
+
# Returns a formatted URL `string` with the specified arguments.
|
|
10
|
+
#
|
|
11
|
+
# @param source_name_or_source [String, Imglab::Source] the source name or source object
|
|
12
|
+
# @param path [String] the path where the resource is located
|
|
13
|
+
# @param params [Hash] the query parameters that we want to use
|
|
14
|
+
# @return [String] the formatted URL with the specified arguments
|
|
15
|
+
# @raise [ArgumentError] when the source name or source parameter has a not expected type.
|
|
16
|
+
#
|
|
17
|
+
# @example Creating a URL specifying source name as string
|
|
18
|
+
# Imglab.url("assets", "example.jpeg", width: 500, height: 600) #=> "https://cdn.imglab.io/assets/example.jpeg?width=500&height=600"
|
|
19
|
+
# @example Creating a URL specifying a Imglab::Source
|
|
20
|
+
# Imglab.url(Imglab::Source.new("assets"), "example.jpeg", width: 500, height: 600) #=> "https://cdn.imglab.io/assets/example.jpeg?width=500&height=600"
|
|
21
|
+
def self.url(source_name_or_source, path, params = {})
|
|
22
|
+
case source_name_or_source
|
|
23
|
+
when String
|
|
24
|
+
url_for_source(Source.new(source_name_or_source), path, params)
|
|
25
|
+
when Source
|
|
26
|
+
url_for_source(source_name_or_source, path, params)
|
|
27
|
+
else
|
|
28
|
+
raise ArgumentError.new("Invalid source name or source. A string or #{Imglab::Source.name} is expected.")
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def self.url_for_source(source, path, params)
|
|
35
|
+
normalized_path = Utils.normalize_path(path)
|
|
36
|
+
normalized_params = Utils.normalize_params(params)
|
|
37
|
+
|
|
38
|
+
URI::Generic.new(
|
|
39
|
+
source.scheme,
|
|
40
|
+
nil,
|
|
41
|
+
source.host,
|
|
42
|
+
source.port,
|
|
43
|
+
nil,
|
|
44
|
+
File.join("/", source.path(normalized_path)),
|
|
45
|
+
nil,
|
|
46
|
+
encode_params(source, normalized_path, normalized_params),
|
|
47
|
+
nil
|
|
48
|
+
).to_s
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.encode_params(source, path, params)
|
|
52
|
+
return encode_empty_params(source, path) if params.empty?
|
|
53
|
+
|
|
54
|
+
if source.secure?
|
|
55
|
+
signature = Signature.generate(source, path, URI.encode_www_form(params))
|
|
56
|
+
|
|
57
|
+
URI.encode_www_form(params.merge(signature: signature))
|
|
58
|
+
else
|
|
59
|
+
URI.encode_www_form(params)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def self.encode_empty_params(source, path)
|
|
64
|
+
if source.secure?
|
|
65
|
+
signature = Signature.generate(source, path)
|
|
66
|
+
|
|
67
|
+
URI.encode_www_form(signature: signature)
|
|
68
|
+
else
|
|
69
|
+
nil
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: imglab
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- imglab
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2021-11-24 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: Official Ruby library to integrate with imglab services.
|
|
14
|
+
email:
|
|
15
|
+
- support@imglab.io
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- ".gitignore"
|
|
21
|
+
- ".yardopts"
|
|
22
|
+
- Gemfile
|
|
23
|
+
- LICENSE
|
|
24
|
+
- README.md
|
|
25
|
+
- Rakefile
|
|
26
|
+
- bin/console
|
|
27
|
+
- bin/setup
|
|
28
|
+
- imglab.gemspec
|
|
29
|
+
- lib/imglab.rb
|
|
30
|
+
- lib/imglab/color.rb
|
|
31
|
+
- lib/imglab/position.rb
|
|
32
|
+
- lib/imglab/signature.rb
|
|
33
|
+
- lib/imglab/source.rb
|
|
34
|
+
- lib/imglab/utils.rb
|
|
35
|
+
- lib/imglab/version.rb
|
|
36
|
+
homepage: https://github.com/imglab-io/imglab-rb
|
|
37
|
+
licenses:
|
|
38
|
+
- MIT
|
|
39
|
+
metadata:
|
|
40
|
+
homepage_uri: https://github.com/imglab-io/imglab-rb
|
|
41
|
+
source_code_uri: https://github.com/imglab-io/imglab-rb
|
|
42
|
+
post_install_message:
|
|
43
|
+
rdoc_options: []
|
|
44
|
+
require_paths:
|
|
45
|
+
- lib
|
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
47
|
+
requirements:
|
|
48
|
+
- - ">="
|
|
49
|
+
- !ruby/object:Gem::Version
|
|
50
|
+
version: 2.0.0
|
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
52
|
+
requirements:
|
|
53
|
+
- - ">="
|
|
54
|
+
- !ruby/object:Gem::Version
|
|
55
|
+
version: '0'
|
|
56
|
+
requirements: []
|
|
57
|
+
rubygems_version: 3.0.3
|
|
58
|
+
signing_key:
|
|
59
|
+
specification_version: 4
|
|
60
|
+
summary: Official imglab Ruby library.
|
|
61
|
+
test_files: []
|