cloudinary 1.29.0 → 2.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0ec56a8224fa9a3318d7e0d3e7ce7747c888767a4b365a752310e38b7747f4a3
4
- data.tar.gz: 58f0148449592fb70e8a87bc292ddeea43a09b0f879fca9c62c1594b3ff487cc
3
+ metadata.gz: 1244ab3ce91f6fda0b07e5438ed79f896cde743306efb9c3ac58e5a5f1d4a6dc
4
+ data.tar.gz: a36510b3d2a2e1b788c2455e5def6f314b78b0b94def573a75a4750e96498b6c
5
5
  SHA512:
6
- metadata.gz: 44e6c4cefaebd3853f8da9c40123d05205b6fa57fd58eb22e1a8a419bad658b3184eb2bd068f79b8d54601793295c333e6a02720d6a5bdbcb699b234432603e2
7
- data.tar.gz: de7032533e031e15c51ccc17ff8e77a2aabc6f9aea4958a58d2313b30aeadc8b14574e36090c99a9e58788e3a376bc9330cfdd0e8d77fcb96d3d4deea7cbfd57
6
+ metadata.gz: 7ba876181927174ec445d3abd04c79cc03af3837713c234ea9a89651131b6dd4c4be6e3ec02d155596d4f2ce821b269600adda74210408af066b04e0671169fb
7
+ data.tar.gz: 8bcbb47be56d985537d237fcd5989979555ba0d622bf8a6a3f60484e8b31ffd7d0682353ce4768e4f732edb3fe1a909e001640226fc8fe085ff35cc321b662c6
data/.travis.yml CHANGED
@@ -1,7 +1,6 @@
1
1
  dist: jammy
2
2
  language: ruby
3
3
  rvm:
4
- - 2.7.8
5
4
  - 3.1.4
6
5
  - 3.2.2
7
6
  - 3.3.0
data/CHANGELOG.md CHANGED
@@ -1,3 +1,97 @@
1
+ 2.3.0 / 2025-02-20
2
+ ==================
3
+
4
+ New functionality and features
5
+ ------------------------------
6
+
7
+ * Add support for `429 Too Many Requests` HTTP status code
8
+ * Add support for `allow_dynamic_list_values` parameter in `MetadataField`
9
+ * Add support for `config` Admin API
10
+
11
+ 2.2.0 / 2024-09-08
12
+ ==================
13
+
14
+ New functionality and features
15
+ ------------------------------
16
+
17
+ * Add support for ActiveStorage model service configuration
18
+
19
+ Other Changes
20
+ -------------
21
+
22
+ * Fix asset type detection in ActiveStorage
23
+ * Add explicit `ostruct` dependency
24
+
25
+ 2.1.2 / 2024-08-18
26
+ ==================
27
+
28
+ * Fix ActiveStorage type detection for email files
29
+ * Add `changelog_uri` to `gemspec`
30
+
31
+ 2.1.1 / 2024-05-28
32
+ ==================
33
+
34
+ * Fix regression in `cloudinary_url` generation
35
+
36
+ 2.1.0 / 2024-05-27
37
+ ==================
38
+
39
+ New functionality and features
40
+ ------------------------------
41
+
42
+ * Add support for `rename_folder` Admin API
43
+ * Add support for `delete_access_key` in Provisioning API
44
+
45
+ Other Changes
46
+ -------------
47
+
48
+ * Fix detection of base64 encoded files with complex MIME type
49
+ * Fix `fetch` url generation with Active Storage `folder` configuration
50
+ * Fix usage of deprecated `File.exists?` method
51
+
52
+ 2.0.2 / 2024-04-09
53
+ ==================
54
+
55
+ * Fix CarrierWave `store!` method
56
+
57
+ 2.0.1 / 2024-04-09
58
+ ==================
59
+
60
+ * Fix rake tasks
61
+
62
+ 2.0.0 / 2024-04-08
63
+ ==================
64
+
65
+ Breaking Changes
66
+ ----------------
67
+
68
+ * Set minimal Ruby version to `3.0`
69
+ * For older Rubies use version `1.x` of this library
70
+ * Replace `rest-client` with `faraday`
71
+ * Set config `secure` to `true` by default
72
+ * All delivery URLs are set to be `https://` by default
73
+ * To disable - set `secure` to `false` in config/options
74
+ * Add support for URL Analytics
75
+ * Adds analytics signature query parameter to the delivery URLs
76
+ * To disable - set `analytics` to `false` in config/options
77
+ * Remove deprecated methods
78
+ * `Cloudinary::Utils.unsigned_download_url`
79
+ * use `Cloudinary::Utils.cloudinary_url` instead
80
+ * `Cloudinary::Utils.signed_download_url`
81
+ * use `Cloudinary::Utils.cloudinary_url` instead
82
+ * `Cloudinary::Utils.zip_download_url`
83
+ * use `Cloudinary::Utils.download_zip_url` instead
84
+ * `cl_zip_download_url`
85
+ * use `cl_download_zip_url` instead
86
+ * Remove deprecated constants
87
+
88
+ New functionality and features
89
+ ------------------------------
90
+
91
+ * Add support for `analyze` API
92
+ * Support chunked uploads with CarrierWave
93
+ * Filter users by last login in `users` Provisioning API
94
+
1
95
  1.29.0 / 2024-02-26
2
96
  ==================
3
97
 
data/README.md CHANGED
@@ -40,11 +40,13 @@ the [Ruby on Rails SDK Guide](https://cloudinary.com/documentation/rails_integra
40
40
 
41
41
  | SDK Version | Ruby 1.9.3 | Ruby 2.x | Ruby 3.x |
42
42
  |-------------|------------|----------|----------|
43
- | 1.x | v | v | v |
43
+ | 1.x | | | |
44
+ | 2.x | ✘ | ✘ | ✔ |
44
45
 
45
46
  | SDK Version | Rails 5.x | Rails 6.x | Rails 7.x |
46
47
  |-------------|-----------|-----------|-----------|
47
- | 1.x | v | v | v |
48
+ | 1.x | | | |
49
+ | 2.x | ✘ | ✔ | ✔ |
48
50
 
49
51
  ## Installation
50
52
 
@@ -64,7 +66,7 @@ require 'cloudinary'
64
66
  - [See full documentation](https://cloudinary.com/documentation/rails_image_manipulation).
65
67
 
66
68
  ```ruby
67
- cl_image_tag("sample.jpg", :width => 100, :height => 150, :crop => :fill)
69
+ cl_image_tag("sample.jpg", width: 100, height: 150, crop: "fill")
68
70
  ```
69
71
 
70
72
  ### Upload
data/cloudinary.gemspec CHANGED
@@ -7,63 +7,38 @@ Gem::Specification.new do |s|
7
7
  s.version = Cloudinary::VERSION
8
8
  s.authors = ["Nadav Soferman","Itai Lahan","Tal Lev-Ami"]
9
9
  s.email = ["nadav.soferman@cloudinary.com","itai.lahan@cloudinary.com","tal.levami@cloudinary.com"]
10
- s.homepage = "http://cloudinary.com"
10
+ s.homepage = "https://cloudinary.com"
11
11
  s.license = "MIT"
12
12
 
13
13
  s.summary = %q{Client library for easily using the Cloudinary service}
14
14
  s.description = %q{Client library for easily using the Cloudinary service}
15
15
 
16
- s.rubyforge_project = "cloudinary"
16
+ s.metadata = {
17
+ "changelog_uri" => "https://github.com/cloudinary/cloudinary_gem/blob/master/CHANGELOG.md"
18
+ }
17
19
 
18
- s.files = `git ls-files`.split("\n").select { |f| !f.start_with?("test", "spec", "features", "samples") } + Dir.glob("vendor/assets/javascripts/*/*") + Dir.glob("vendor/assets/html/*")
20
+ s.files = `git ls-files`.split("\n").select { |f| !f.start_with?("test", "spec", "features", "samples") } +
21
+ Dir.glob("vendor/assets/javascripts/*/*") + Dir.glob("vendor/assets/html/*")
19
22
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
23
  s.require_paths = ["lib"]
21
24
 
22
- s.add_dependency "aws_cf_signer"
23
-
24
- if RUBY_VERSION >= "2.0.0"
25
- s.add_dependency "rest-client", ">= 2.0.0"
26
- else
27
- s.add_dependency "rest-client"
28
- end
29
-
30
- if RUBY_VERSION >= "3.0.0"
31
- s.add_development_dependency "rexml"
32
- end
33
-
34
- s.add_development_dependency "actionpack"
35
- s.add_development_dependency "nokogiri"
36
-
37
- if RUBY_VERSION >= "2.2.0"
38
- s.add_development_dependency "rake", ">= 13.0.1"
39
- else
40
- s.add_development_dependency "rake", "<= 12.2.1"
41
- end
42
- if RUBY_VERSION >= "3.0.0"
43
- s.add_development_dependency "sqlite3"
44
- else
45
- s.add_development_dependency "sqlite3", "< 1.6.0"
46
- end
47
-
48
- s.add_development_dependency "rspec", '>=3.5'
49
- s.add_development_dependency "rspec-retry"
50
-
51
- if RUBY_VERSION >= "3.0.0"
52
- s.add_development_dependency "rails", "~> 6.0.3"
53
- elsif RUBY_VERSION >= "2.2.2"
54
- s.add_development_dependency "rails", "~> 5.2"
55
- end
56
-
57
- s.add_development_dependency "railties", "<= 4.2.7" if RUBY_VERSION <= "1.9.3"
58
- s.add_development_dependency "rspec-rails"
59
-
60
- s.add_development_dependency "rubyzip"
61
-
62
- if RUBY_VERSION <= "2.4.0"
63
- s.add_development_dependency "simplecov", "<= 0.17.1" # support testing Ruby 1.9
64
- s.add_development_dependency 'loofah', '~>2.19.1'
65
- s.add_development_dependency "rails-html-sanitizer", "<1.5.0"
66
- else
67
- s.add_development_dependency "simplecov", "> 0.18.0"
68
- end
25
+ s.required_ruby_version = '~> 3'
26
+
27
+ s.add_dependency "faraday", ">= 2.0.1", "< 3.0.0"
28
+ s.add_dependency "faraday-multipart", "~> 1.0", ">= 1.0.4"
29
+ s.add_dependency 'faraday-follow_redirects', '~> 0.3.0'
30
+ s.add_dependency "ostruct"
31
+
32
+ s.add_development_dependency "rails", ">= 6.1.7", "< 8.0.0"
33
+ s.add_development_dependency "rexml", ">= 3.2.5", "< 4.0.0"
34
+ s.add_development_dependency "actionpack", ">= 6.1.7", "< 8.0.0"
35
+ s.add_development_dependency "nokogiri", ">= 1.12.5", "< 2.0.0"
36
+ s.add_development_dependency "rake", ">= 13.0.6", "< 14.0.0"
37
+ s.add_development_dependency "sqlite3", ">= 1.4.2", "< 2.0.0"
38
+ s.add_development_dependency "rspec", ">= 3.11.2", "< 4.0.0"
39
+ s.add_development_dependency "rspec-retry", ">= 0.6.2", "< 1.0.0"
40
+ s.add_development_dependency "railties", ">= 6.0.4", "< 8.0.0"
41
+ s.add_development_dependency "rspec-rails", ">= 6.0.4", "< 7.0.0"
42
+ s.add_development_dependency "rubyzip", ">= 2.3.0", "< 3.0.0"
43
+ s.add_development_dependency "simplecov", ">= 0.21.2", "< 1.0.0"
69
44
  end
@@ -17,7 +17,13 @@ module CloudinaryHelper
17
17
  alias cloudinary_url_internal_original cloudinary_url_internal
18
18
 
19
19
  def cloudinary_url_internal(source, options = {})
20
- source = ActiveStorage::Blob.service.public_id(source) if defined? ActiveStorage::Blob.service.public_id
20
+ service_instance, options = ActiveStorage::Service::CloudinaryService.fetch_service_instance_and_config(source, options)
21
+ service_instance ||= ActiveStorage::Blob.service
22
+
23
+ if defined?(service_instance.public_id) && options.fetch(:type, "").to_s != "fetch"
24
+ source = service_instance.public_id(source)
25
+ end
26
+
21
27
  cloudinary_url_internal_original(source, options)
22
28
  end
23
29
  end
@@ -39,10 +45,12 @@ module ActiveStorage
39
45
  begin
40
46
  extra_headers = checksum.nil? ? {} : {Headers::CONTENT_MD5 => checksum}
41
47
  options = @options.merge(options)
48
+ resource_type = resource_type(io, key)
49
+ options[:format] = ext_for_file(key) if resource_type == "raw"
42
50
  Cloudinary::Uploader.upload_large(
43
51
  io,
44
52
  public_id: public_id_internal(key),
45
- resource_type: resource_type(io, key),
53
+ resource_type: resource_type,
46
54
  context: {active_storage_key: key, checksum: checksum},
47
55
  extra_headers: extra_headers,
48
56
  **options
@@ -54,9 +62,10 @@ module ActiveStorage
54
62
  end
55
63
 
56
64
  def url(key, filename: nil, content_type: '', **options)
65
+ key = find_blob_or_use_key(key)
57
66
  instrument :url, key: key do |payload|
58
67
  url = Cloudinary::Utils.cloudinary_url(
59
- full_public_id_internal(key),
68
+ full_public_id_internal(key, options),
60
69
  resource_type: resource_type(nil, key, content_type),
61
70
  format: ext_for_file(key, filename, content_type),
62
71
  **@options.merge(options.symbolize_keys)
@@ -97,6 +106,7 @@ module ActiveStorage
97
106
  end
98
107
 
99
108
  def delete(key)
109
+ key = find_blob_or_use_key(key)
100
110
  instrument :delete, key: key do
101
111
  options = {
102
112
  resource_type: resource_type(nil, key),
@@ -113,6 +123,7 @@ module ActiveStorage
113
123
  end
114
124
 
115
125
  def exist?(key)
126
+ key = find_blob_or_use_key(key)
116
127
  instrument :exist, key: key do |payload|
117
128
  begin
118
129
  options = {
@@ -149,8 +160,7 @@ module ActiveStorage
149
160
 
150
161
  # Return the partial content in the byte +range+ of the file at the +key+.
151
162
  def download_chunk(key, range)
152
- url = Cloudinary::Utils.unsigned_download_url(public_id(key), resource_type: resource_type(nil, key))
153
- uri = URI(url)
163
+ uri = URI(url(key))
154
164
  instrument :download, key: key do
155
165
  req = Net::HTTP::Get.new(uri)
156
166
  range_end = case
@@ -186,6 +196,33 @@ module ActiveStorage
186
196
  full_public_id_internal(public_id)
187
197
  end
188
198
 
199
+ def self.fetch_service_instance_and_config(source, options)
200
+ return [nil, options] unless defined?(ActiveStorage::BlobKey) && source.is_a?(ActiveStorage::BlobKey) &&
201
+ source.respond_to?(:attributes) && source.attributes.key?(:service_name)
202
+
203
+ service_name = source.attributes[:service_name]
204
+
205
+ begin
206
+ service_instance = ActiveStorage::Blob.services.fetch(service_name.to_sym)
207
+
208
+ unless service_instance.instance_of?(ActiveStorage::Service::CloudinaryService)
209
+ Rails.logger.error "Expected service instance #{service_instance.class.name} to be of type ActiveStorage::Service::CloudinaryService."
210
+ return [nil, options]
211
+ end
212
+
213
+ service_config = Rails.application.config.active_storage.service_configurations.fetch(service_name)
214
+ options = service_config.merge(options)
215
+ rescue NameError => e
216
+ Rails.logger.error "Failed to retrieve the service instance for #{service_name}: #{e.message}"
217
+ return [nil, options]
218
+ rescue => e
219
+ Rails.logger.error "An unexpected error occurred: #{e.message}"
220
+ return [nil, options]
221
+ end
222
+
223
+ [service_instance, options]
224
+ end
225
+
189
226
  private
190
227
 
191
228
  def api_uri(action, options)
@@ -229,10 +266,12 @@ module ActiveStorage
229
266
  end
230
267
 
231
268
  # Returns the full public id including folder.
232
- def full_public_id_internal(key)
269
+ def full_public_id_internal(key, options = {})
233
270
  public_id = public_id_internal(key)
234
271
 
235
- return public_id unless @options[:folder]
272
+ options = @options.merge(options)
273
+
274
+ return public_id if !options[:folder] || options.fetch(:type, "").to_s == "fetch"
236
275
 
237
276
  File.join(@options.fetch(:folder), public_id)
238
277
  end
@@ -249,7 +288,7 @@ module ActiveStorage
249
288
  case type
250
289
  when 'video', 'audio'
251
290
  'video'
252
- when 'text'
291
+ when 'text', 'message'
253
292
  'raw'
254
293
  when 'application'
255
294
  case subtype
@@ -272,5 +311,19 @@ module ActiveStorage
272
311
  end
273
312
  content_type_to_resource_type(content_type)
274
313
  end
314
+
315
+ def find_blob_or_use_key(key)
316
+ if key.is_a?(ActiveStorage::BlobKey)
317
+ key
318
+ else
319
+ begin
320
+ blob = ActiveStorage::Blob.find_by(key: key)
321
+ blob ? ActiveStorage::BlobKey.new(blob.attributes.as_json) : key
322
+ rescue ActiveRecord::StatementInvalid => e
323
+ # Return the original key if an error occurs
324
+ key
325
+ end
326
+ end
327
+ end
275
328
  end
276
329
  end
@@ -127,18 +127,30 @@ class Cloudinary::AccountApi
127
127
  call_account_api(:get, ['users', user_id], {}, options.merge(content_type: :json))
128
128
  end
129
129
 
130
- # Lists users in the account.
131
- # @param [Boolean] pending Limit results to pending users (true), users that are not pending (false), or all users (nil, the default)
132
- # @param [Array<String>] user_ids A list of up to 100 user IDs. When provided, other parameters are ignored.
133
- # @param [String] prefix Returns users where the name or email address begins with the specified case-insensitive string.
134
- # @param [String] sub_account_id Only returns users who have access to the specified account.
135
- # @param [Object] options additional options
130
+ # Get a list of the users according to filters.
131
+ #
132
+ # @param [Boolean] pending Optional. Limit results to pending users (true), users that are not pending (false), or all users (null)
133
+ # @param [Array<String>] user_ids Optional. List of user IDs. Up to 100
134
+ # @param [String] prefix Optional. Search by prefix of the user's name or email. Case-insensitive
135
+ # @param [String] sub_account_id Optional. Return only users who have access to the given sub-account
136
+ # @param [Object] options Generic advanced options map, see online documentation.
137
+ # @option options [Boolean] :last_login Optional. Return only users that last logged in in the specified range of dates (true),
138
+ # users that didn't last logged in in that range (false), or all users (null).
139
+ # @option options [Date] :from Optional. Last login start date.
140
+ # @option options [Date] :to Optional. Last login end date.
141
+ #
142
+ # @return [Cloudinary::Api::Response] the users' details.
143
+ #
144
+ # @raise [Cloudinary::Api::Error] If the request fails.
136
145
  def self.users(pending = nil, user_ids = [], prefix = nil, sub_account_id = nil, options = {})
137
146
  params = {
138
147
  ids: user_ids,
139
148
  prefix: prefix,
140
149
  sub_account_id: sub_account_id,
141
- pending: pending
150
+ pending: pending,
151
+ last_login: options[:last_login].to_s,
152
+ from: Cloudinary::Utils.to_usage_api_date_format(options[:from]),
153
+ to: Cloudinary::Utils.to_usage_api_date_format(options[:to])
142
154
  }
143
155
 
144
156
  call_account_api(:get, 'users', params, options.merge(content_type: :json))
@@ -248,16 +260,35 @@ class Cloudinary::AccountApi
248
260
  call_account_api(:put, ['sub_accounts', sub_account_id, 'access_keys', api_key], params, options.merge(content_type: :json))
249
261
  end
250
262
 
263
+ # Deletes access key.
264
+ #
265
+ # @param [String] sub_account_id The ID of the sub-account.
266
+ # @param [String, nil] api_key The API key.
267
+ # @param [String, nil] name The display name as shown in the management console.
268
+ # @param [Object] options Additional options.
269
+ def self.delete_access_key(sub_account_id, api_key = nil, name = nil, options = {})
270
+ uri = ['sub_accounts', sub_account_id, 'access_keys']
271
+ unless api_key.blank?
272
+ uri.append(api_key)
273
+ end
274
+
275
+ params = {
276
+ name: name,
277
+ }
278
+ call_account_api(:delete, uri, params, options.merge(content_type: :json))
279
+ end
280
+
251
281
  def self.call_account_api(method, uri, params, options)
252
282
  account_id = options[:account_id] || Cloudinary.account_config.account_id || raise('Must supply account_id')
253
283
  api_key = options[:provisioning_api_key] || Cloudinary.account_config.provisioning_api_key || raise('Must supply provisioning api_key')
254
284
  api_secret = options[:provisioning_api_secret] || Cloudinary.account_config.provisioning_api_secret || raise('Must supply provisioning api_secret')
285
+ api_version = options[:api_version] || Cloudinary.config.api_version || 'v1_1'
255
286
 
256
287
  params.reject! { |_, v| v.nil? }
257
288
  auth = { :key => api_key, :secret => api_secret }
258
289
 
259
290
  call_cloudinary_api(method, uri, auth, params, options) do |cloudinary, inner_uri|
260
- [cloudinary, 'v1_1', 'provisioning', 'accounts', account_id, inner_uri]
291
+ [cloudinary, api_version, 'provisioning', 'accounts', account_id, inner_uri]
261
292
  end
262
293
  end
263
294
 
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cloudinary
4
+ module Analytics
5
+ extend self
6
+
7
+ QUERY_KEY = '_a'
8
+ ALGO_VERSION = 'B' # The version of the algorithm
9
+ SDK_CODE = 'C' # Cloudinary Ruby SDK
10
+
11
+ @product = 'A' # Official SDK. Set to 'B' for integrations.
12
+ @sdk_code = SDK_CODE
13
+ @sdk_version = Cloudinary::VERSION
14
+ @tech_version = "#{RUBY_VERSION[/\d+\.\d+/]}"
15
+
16
+ CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
17
+ BINARY_PAD_SIZE = 6
18
+
19
+ @char_codes = nil
20
+ @signature = nil
21
+
22
+ # Gets the SDK analytics signature query parameter.
23
+ #
24
+ # @return [String] The SDK signature query parameter.
25
+ def sdk_analytics_query_param
26
+ "#{QUERY_KEY}=#{self.sdk_analytics_signature}"
27
+ end
28
+
29
+ # Gets the SDK signature by encoding the SDK version and tech version.
30
+ #
31
+ # @return [String] The SDK signature.
32
+ def sdk_analytics_signature
33
+ return @signature unless @signature.nil?
34
+
35
+ begin
36
+ @signature = ALGO_VERSION + @product + @sdk_code + encode_version(@sdk_version) + encode_version(@tech_version)
37
+ rescue RangeError
38
+ @signature = 'E'
39
+ end
40
+
41
+ @signature
42
+ end
43
+
44
+ # Sets the product code.
45
+ #
46
+ # Used for integrations.
47
+ #
48
+ # @param [String] product The product code to set. 'A' is for the official SDK. 'B' for integrations.
49
+ #
50
+ # @return [void]
51
+ #
52
+ # @internal
53
+ def product(product)
54
+ @product = product
55
+ end
56
+
57
+ # Sets the SDK code.
58
+ #
59
+ # Used for integrations.
60
+ #
61
+ # @param [String] sdk_code The SDK code to set.
62
+ #
63
+ # @return [void]
64
+ #
65
+ # @internal
66
+ def sdk_code(sdk_code)
67
+ @sdk_code = sdk_code
68
+ end
69
+
70
+ # Sets the SDK version.
71
+ #
72
+ # Used for integrations.
73
+ #
74
+ # @param [String] sdk_version The SDK version to set (MAJOR.MINOR.PATCH), for example: "1.0.0".
75
+ #
76
+ # @return [void]
77
+ #
78
+ # @internal
79
+ def sdk_version(sdk_version)
80
+ @sdk_version = sdk_version
81
+ end
82
+
83
+ # Sets the tech version.
84
+ #
85
+ # Used for integrations.
86
+ #
87
+ # @param [String] tech_version The tech version to set (MAJOR.MINOR), for example: "1.0".
88
+ #
89
+ # @return [void]
90
+ #
91
+ # @internal
92
+ def tech_version(tech_version)
93
+ @tech_version = tech_version.split('.').first(2).join('.')
94
+ end
95
+
96
+ # Encodes a semVer-like version string.
97
+ #
98
+ # Example:
99
+ # input: '1.24.0'
100
+ # explode: ['1','24','0']
101
+ # pad: ['01','24','00']
102
+ # reverse: ['00', '24', '01']
103
+ # implode: '002401'
104
+ # int: 2401
105
+ # binary: '100101100001'
106
+ # padded: '000000100101100001'
107
+ # str_split: ['000000', '100101', '100001']
108
+ # getKey: ['A', 'l', 'h']
109
+ # implode: 'Alh'
110
+ #
111
+ # @param [String] version Can be either x.y.z or x.y
112
+ #
113
+ # @return [String] A string built from 3 characters of the base64 table
114
+ #
115
+ # @raise [RangeError] when version is larger than 43.21.26
116
+ def encode_version(version)
117
+ parts = version.split('.')
118
+
119
+ padded_parts = parts.map { |part| part.rjust(2, '0') }
120
+ number = padded_parts.reverse.join.to_i
121
+ padded_binary = int_to_padded_bin(number, parts.length * BINARY_PAD_SIZE)
122
+
123
+ raise RangeError, 'Version must be smaller than 43.21.26' if padded_binary.length % BINARY_PAD_SIZE != 0
124
+
125
+ encoded_chars = padded_binary.chars.each_slice(BINARY_PAD_SIZE).map { |slice| get_key(slice.join) }
126
+
127
+ encoded_chars.join
128
+ end
129
+
130
+ # Gets the key for binary value.
131
+ #
132
+ # @param [String] binary_value The value.
133
+ #
134
+ # @return [Array, Object] The key for the binary value.
135
+ def get_key(binary_value)
136
+ @char_codes ||= initialize_char_codes
137
+
138
+ @char_codes[binary_value] || ''
139
+ end
140
+
141
+ def initialize_char_codes
142
+ char_codes = {}
143
+ CHARS.chars.each_with_index { |char, idx| char_codes[int_to_padded_bin(idx, BINARY_PAD_SIZE)] = char }
144
+ char_codes
145
+ end
146
+
147
+ # Converts integer to left padded binary string.
148
+ #
149
+ # @param [Integer] integer The input.
150
+ # @param [Integer] pad_num The num of padding chars.
151
+ #
152
+ # @return [String] The padded binary string.
153
+ def int_to_padded_bin(integer, pad_num)
154
+ integer.to_s(2).rjust(pad_num, '0')
155
+ end
156
+ end
157
+ end