cloudinary 1.15.0 → 1.18.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: 8c4d1942e462293cb9a3cf7d73878104de63cc041ad6c9a82ae985e9ce1c6dd7
4
- data.tar.gz: fd09572e0b1be22051a711fb7aacfc1f3f46856531b5c0a3caf50fea49eabae5
3
+ metadata.gz: c9179fe1c8105281b33780687c2bed49db41a35fca2d33ff15bd7fd4635d119f
4
+ data.tar.gz: 7b3bd01f71f1012bc7f315f707450ebb65537064e004fad615d713f489c4316c
5
5
  SHA512:
6
- metadata.gz: 66eea468a7816511382761d6ab667c39b4f90da2c765871d99a69d34091949a3fc8d4d2245cd3b53f18560a99eea59fdd2cdd0173d8e41e6ab8e040236d52dc4
7
- data.tar.gz: 65c5d8536b106b88f6bf8de3a540df6ffed3f6fed39141d6dfbf47a5ae6b11f3ec37699722e5b20e8fe35c86f452f29ec0ba9b9f5edc776ce81efebbf8cd14f8
6
+ metadata.gz: 347fc122e9a31aa241afa194c09cdc3c0d19e63c5a206cc7751f4939de7c273769d648c7fd33af2cc4e7505fba0a5be9d555f1c3db1e2b41d45cfb58082e1cc3
7
+ data.tar.gz: 4f04b59256afe8f23c27625d94d761e9802e2ccd91d3c3dc992eb263e3430a7a053467c203d416d379815e317924f042c9310ac0f2b4ca185630307268aa66ca
@@ -0,0 +1,24 @@
1
+ ### Brief Summary of Changes
2
+ <!--
3
+ Provide some context as to what was changed, from an implementation standpoint.
4
+ -->
5
+
6
+ #### What does this PR address?
7
+ [ ] Gitub issue (Add reference - #XX)
8
+ [ ] Refactoring
9
+ [ ] New feature
10
+ [ ] Bug fix
11
+ [ ] Adds more tests
12
+
13
+ #### Are tests included?
14
+ [ ] Yes
15
+ [ ] No
16
+
17
+ #### Reviewer, Please Note:
18
+ <!--
19
+ List anything here that the reviewer should pay special attention to. This might
20
+ include, for example:
21
+ • Dependence on other PRs
22
+ • Reference to other Cloudinary SDKs
23
+ • Changes that seem arbitrary without further explanations
24
+ -->
@@ -12,4 +12,8 @@ matrix:
12
12
  rvm: ruby-2.5.3
13
13
  before_install:
14
14
  - gem install bundler
15
+
16
+ before_script: >
17
+ export CLOUDINARY_URL=$(bash tools/get_test_cloud.sh);
18
+ echo cloud_name: "$(echo $CLOUDINARY_URL | cut -d'@' -f2)"
15
19
  script: bundle exec rspec
@@ -1,4 +1,61 @@
1
1
 
2
+ 1.18.0 / 2020-09-27
3
+ ===================
4
+
5
+ New functionality and features
6
+ ------------------------------
7
+ * Add `download_folder` helper
8
+ * Add support for `sources` in `video` tag
9
+ * Add structured metadata to Admin and Upload API
10
+
11
+ Other Changes
12
+ -------------
13
+ * Fix download of a raw file in ActiveStorage
14
+ * Update embedded `jquery.cloudinary.js` to fix ES5 compatibility issue
15
+
16
+ 1.17.1 / 2020-08-25
17
+ ===================
18
+
19
+ * Fix options handling issue in SassC
20
+
21
+ 1.17.0 / 2020-08-21
22
+ ===================
23
+
24
+ New functionality and features
25
+ ------------------------------
26
+
27
+ * Add support for `eval` upload parameter
28
+ * Add support for 32-char signature length
29
+
30
+ Other Changes
31
+ -------------
32
+
33
+ * Fix escaping of query string characters in CarrierWave integration
34
+ * Fix detection integration test
35
+ * Integrate with sub-account test service
36
+ * Add pull request template
37
+
38
+ 1.16.1 / 2020-07-06
39
+ ===================
40
+
41
+ * Detect data URLs with suffix in mime type
42
+ * Fix `Invalid regular expression` error in Safari
43
+
44
+ 1.16.0 / 2020-06-29
45
+ ===================
46
+
47
+ New functionality and features
48
+ ------------------------------
49
+
50
+ * Add support for uploading `StringIO`
51
+
52
+ Other Changes
53
+ -------------
54
+
55
+ * Set default cache storage to `file` in `CarrierWave`
56
+ * Fix `normalize_expression` to ignore predefined variables
57
+ * Fix sample projects
58
+
2
59
  1.15.0 / 2020-06-11
3
60
  ===================
4
61
 
data/Rakefile CHANGED
@@ -1,55 +1,13 @@
1
1
  require 'bundler'
2
2
  require 'fileutils'
3
- require 'tmpdir'
4
- require 'rest_client'
5
- require 'json'
6
- require 'rubygems/package'
7
-
8
3
  require 'rspec/core/rake_task'
4
+
9
5
  RSpec::Core::RakeTask.new(:spec)
10
6
  task :default => :spec
11
7
 
12
8
  Bundler::GemHelper.install_tasks
13
9
 
14
- namespace :cloudinary do
15
- desc "Fetch the latest JavaScript library files and create the JavaScript index files"
16
- task :fetch_assets do
17
- index_files = %w[jquery.ui.widget.js jquery.iframe-transport.js jquery.fileupload.js jquery.cloudinary.js]
18
- processing_files = %w[canvas-to-blob.min.js load-image.all.min.js jquery.fileupload-process.js jquery.fileupload-image.js jquery.fileupload-validate.js]
19
- files = index_files + processing_files
20
-
21
- release = JSON(RestClient.get("https://api.github.com/repos/cloudinary/cloudinary_js/releases/latest"))
22
-
23
- FileUtils.rm_rf 'vendor/assets'
24
- html_folder = 'vendor/assets/html'
25
- FileUtils.mkdir_p html_folder
26
- js_folder = 'vendor/assets/javascripts/cloudinary'
27
- FileUtils.mkdir_p js_folder
28
-
29
- puts "Fetching cloudinary_js version #{release["tag_name"]}\n\n"
30
- sio = StringIO.new(RestClient.get(release["tarball_url"]).body)
31
- file =Zlib::GzipReader.new(sio)
32
- tar = Gem::Package::TarReader.new(file)
33
- tar.each_entry do |entry|
34
- name = File.basename(entry.full_name)
35
- if files.include? name
36
- js_full_name = File.join(js_folder, name)
37
- puts "Adding #{js_full_name}"
38
- File.write js_full_name, entry.read
39
- elsif name == 'cloudinary_cors.html'
40
- html_full_name = File.join(html_folder, name)
41
- puts "Adding #{html_full_name}"
42
- File.write html_full_name, entry.read
43
- end
44
- end
45
- puts "Creating 'index.js' and 'processing.js' files"
46
- File.open("vendor/assets/javascripts/cloudinary/index.js", "w") do |f|
47
- index_files.each { |name| f.puts "//= require ./#{name}" }
48
- end
49
- File.open("vendor/assets/javascripts/cloudinary/processing.js", "w") do |f|
50
- processing_files.each { |name| f.puts "//= require ./#{name}" }
51
- end
52
- end
10
+ path = File.expand_path(__dir__)
11
+ Dir.glob("#{path}/lib/tasks/**/*.rake").each { |f| import f }
53
12
 
54
- end
55
13
  task :build => "cloudinary:fetch_assets"
@@ -33,6 +33,7 @@ Gem::Specification.new do |s|
33
33
 
34
34
  s.add_development_dependency "sqlite3"
35
35
  s.add_development_dependency "rspec", '>=3.5'
36
+ s.add_development_dependency "rspec-retry"
36
37
  s.add_development_dependency "rails", "~>5.2" if RUBY_VERSION >= "2.2.2"
37
38
 
38
39
  s.add_development_dependency "railties", "<= 4.2.7" if RUBY_VERSION <= "1.9.3"
@@ -1,5 +1,6 @@
1
1
  require 'active_storage/blob_key'
2
2
  require 'cloudinary/helper'
3
+ require 'net/http'
3
4
 
4
5
  unless ActiveStorage::Blob.method_defined? :original_key
5
6
  class ActiveStorage::Blob
@@ -57,7 +58,7 @@ module ActiveStorage
57
58
  url = Cloudinary::Utils.cloudinary_url(
58
59
  public_id(key),
59
60
  resource_type: resource_type(nil, key),
60
- format: ext_for_file(filename, content_type),
61
+ format: ext_for_file(key, filename, content_type),
61
62
  **@options.merge(options.symbolize_keys)
62
63
  )
63
64
 
@@ -107,8 +108,7 @@ module ActiveStorage
107
108
  end
108
109
 
109
110
  def download(key, &block)
110
- url = Cloudinary::Utils.unsigned_download_url(public_id(key), resource_type: resource_type(nil, key))
111
- uri = URI(url)
111
+ uri = URI(url(key))
112
112
  if block_given?
113
113
  instrument :streaming_download, key: key do
114
114
  Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
@@ -169,12 +169,18 @@ module ActiveStorage
169
169
  #
170
170
  # It does the best effort when original filename does not include extension, but we know the mime-type.
171
171
  #
172
+ # @param [ActiveStorage::BlobKey] key The blob key with attributes.
172
173
  # @param [ActiveStorage::Filename] filename The original filename.
173
174
  # @param [string] content_type The content type of the file.
174
175
  #
175
176
  # @return [string] The extension of the filename.
176
- def ext_for_file(filename, content_type)
177
+ def ext_for_file(key, filename, content_type)
178
+ if filename.blank?
179
+ options = key.respond_to?(:attributes) ? key.attributes : {}
180
+ filename = ActiveStorage::Filename.new(options[:filename]) if options.has_key?(:filename)
181
+ end
177
182
  ext = filename.respond_to?(:extension_without_delimiter) ? filename.extension_without_delimiter : nil
183
+
178
184
  return ext unless ext.blank?
179
185
 
180
186
  # Raw files are not convertible, no extension guessing for them
@@ -348,6 +348,144 @@ class Cloudinary::Api
348
348
  call_json_api('GET', json_url, {}, 60, {})
349
349
  end
350
350
 
351
+ # Returns a list of all metadata field definitions.
352
+ #
353
+ # @see https://cloudinary.com/documentation/admin_api#get_metadata_fields Get metadata fields API reference
354
+ #
355
+ # @param [Hash] options Additional options
356
+ # @return [Cloudinary::Api::Response]
357
+ # @raise [Cloudinary::Api::Error]
358
+ def self.list_metadata_fields(options = {})
359
+ call_metadata_api(:get, [], {}, options)
360
+ end
361
+
362
+ # Gets a metadata field by external id.
363
+ #
364
+ # @see https://cloudinary.com/documentation/admin_api#get_a_metadata_field_by_external_id Get metadata field by external ID API reference
365
+ #
366
+ # @param [String] field_external_id The ID of the metadata field to retrieve
367
+ # @param [Hash] options Additional options
368
+ # @return [Cloudinary::Api::Response]
369
+ # @raise [Cloudinary::Api::Error]
370
+ def self.metadata_field_by_field_id(field_external_id, options = {})
371
+ uri = [field_external_id]
372
+
373
+ call_metadata_api(:get, uri, {}, options)
374
+ end
375
+
376
+ # Creates a new metadata field definition.
377
+ #
378
+ # @see https://cloudinary.com/documentation/admin_api#create_a_metadata_field Create metadata field API reference
379
+ #
380
+ # @param [Hash] field The field to add
381
+ # @param [Hash] options Additional options
382
+ # @return [Cloudinary::Api::Response]
383
+ # @raise [Cloudinary::Api::Error]
384
+ def self.add_metadata_field(field, options = {})
385
+ params = only(field, :type, :external_id, :label, :mandatory, :default_value, :validation, :datasource)
386
+
387
+ call_metadata_api(:post, [], params, options)
388
+ end
389
+
390
+ # Updates a metadata field by external id.
391
+ #
392
+ # Updates a metadata field definition (partially, no need to pass the entire object) passed as JSON data.
393
+ # See https://cloudinary.com/documentation/admin_api#generic_structure_of_a_metadata_field for the generic structure
394
+ # of a metadata field.
395
+ #
396
+ # @see https://cloudinary.com/documentation/admin_api#update_a_metadata_field_by_external_id Update metadata field API reference
397
+ #
398
+ # @param [String] field_external_id The id of the metadata field to update
399
+ # @param [Hash] field The field definition
400
+ # @param [Hash] options Additional options
401
+ # @return [Cloudinary::Api::Response]
402
+ # @raise [Cloudinary::Api::Error]
403
+ def self.update_metadata_field(field_external_id, field, options = {})
404
+ uri = [field_external_id]
405
+ params = only(field, :label, :mandatory, :default_value, :validation)
406
+
407
+ call_metadata_api(:put, uri, params, options)
408
+ end
409
+
410
+ # Deletes a metadata field definition.
411
+ #
412
+ # The field should no longer be considered a valid candidate for all other endpoints.
413
+ #
414
+ # @see https://cloudinary.com/documentation/admin_api#delete_a_metadata_field_by_external_id Delete metadata field API reference
415
+ #
416
+ # @param [String] field_external_id The external id of the field to delete
417
+ # @param [Hash] options Additional options
418
+ # @return [Cloudinary::Api::Response] A hash with a "message" key. "ok" value indicates a successful deletion
419
+ # @raise [Cloudinary::Api::Error]
420
+ def self.delete_metadata_field(field_external_id, options = {})
421
+ uri = [field_external_id]
422
+
423
+ call_metadata_api(:delete, uri, {}, options)
424
+ end
425
+
426
+ # Deletes entries in a metadata field datasource.
427
+ #
428
+ # Deletes (blocks) the datasource entries for a specified metadata field definition. Sets the state of the
429
+ # entries to inactive. This is a soft delete, the entries still exist under the hood and can be activated
430
+ # again with the restore datasource entries method.
431
+ #
432
+ # @see https://cloudinary.com/documentation/admin_api#delete_entries_in_a_metadata_field_datasource Delete entries in a metadata field datasource API reference
433
+ #
434
+ # @param [String] field_external_id The id of the field to update
435
+ # @param [Array] entries_external_id The ids of all the entries to delete from the datasource
436
+ # @param [Hash] options Additional options
437
+ # @return [Cloudinary::Api::Response] The remaining datasource entries
438
+ # @raise [Cloudinary::Api::Error]
439
+ def self.delete_datasource_entries(field_external_id, entries_external_id, options = {})
440
+ uri = [field_external_id, "datasource"]
441
+ params = {:external_ids => entries_external_id }
442
+
443
+ call_metadata_api(:delete, uri, params, options)
444
+ end
445
+
446
+ # Updates a metadata field datasource.
447
+ #
448
+ # Updates the datasource of a supported field type (currently only enum and set), passed as JSON data. The
449
+ # update is partial: datasource entries with an existing external_id will be updated and entries with new
450
+ # external_id’s (or without external_id’s) will be appended.
451
+ #
452
+ # @see https://cloudinary.com/documentation/admin_api#update_a_metadata_field_datasource Update a metadata field datasource API reference
453
+ #
454
+ # @param [String] field_external_id The external id of the field to update
455
+ # @param [Array] entries_external_id
456
+ # @param [Hash] options Additional options
457
+ # @return [Cloudinary::Api::Response]
458
+ # @raise [Cloudinary::Api::Error]
459
+ def self.update_metadata_field_datasource(field_external_id, entries_external_id, options = {})
460
+ uri = [field_external_id, "datasource"]
461
+
462
+ params = entries_external_id.each_with_object({:values => [] }) do |item, hash|
463
+ item = only(item, :external_id, :value)
464
+ hash[:values ] << item if item.present?
465
+ end
466
+
467
+ call_metadata_api(:put, uri, params, options)
468
+ end
469
+
470
+ # Restores entries in a metadata field datasource.
471
+ #
472
+ # Restores (unblocks) any previously deleted datasource entries for a specified metadata field definition.
473
+ # Sets the state of the entries to active.
474
+ #
475
+ # @see https://cloudinary.com/documentation/admin_api#restore_entries_in_a_metadata_field_datasource Restore entries in a metadata field datasource API reference
476
+ #
477
+ # @param [String] field_external_id The ID of the metadata field
478
+ # @param [Array] entries_external_ids An array of IDs of datasource entries to restore (unblock)
479
+ # @param [Hash] options Additional options
480
+ # @return [Cloudinary::Api::Response]
481
+ # @raise [Cloudinary::Api::Error]
482
+ def self.restore_metadata_field_datasource(field_external_id, entries_external_ids, options = {})
483
+ uri = [field_external_id, "datasource_restore"]
484
+ params = {:external_ids => entries_external_ids }
485
+
486
+ call_metadata_api(:post, uri, params, options)
487
+ end
488
+
351
489
  protected
352
490
 
353
491
  def self.call_api(method, uri, params, options)
@@ -396,6 +534,22 @@ class Cloudinary::Api
396
534
  raise GeneralError.new("Error parsing server response (#{response.code}) - #{response.body}. Got - #{e}")
397
535
  end
398
536
 
537
+ # Protected function that assists with performing an API call to the metadata_fields part of the Admin API.
538
+ #
539
+ # @protected
540
+ # @param [Symbol] method The HTTP method. Valid methods: get, post, put, delete
541
+ # @param [Array] uri REST endpoint of the API (without 'metadata_fields')
542
+ # @param [Hash] params Query/body parameters passed to the method
543
+ # @param [Hash] options Additional options. Can be an override of the configuration, headers, etc.
544
+ # @return [Cloudinary::Api::Response]
545
+ # @raise [Cloudinary::Api::Error]
546
+ def self.call_metadata_api(method, uri, params, options)
547
+ options[:content_type] = :json
548
+ uri = ["metadata_fields", uri].reject(&:empty?).join("/")
549
+
550
+ call_api(method, uri, params, options)
551
+ end
552
+
399
553
  def self.only(hash, *keys)
400
554
  result = {}
401
555
  keys.each do |key|
@@ -9,6 +9,7 @@ module Cloudinary::CarrierWave
9
9
 
10
10
  def self.included(base)
11
11
  base.storage Cloudinary::CarrierWave::Storage
12
+ base.cache_storage = :file if base.cache_storage.blank?
12
13
  base.extend ClassMethods
13
14
  base.class_attribute :metadata
14
15
  base.class_attribute :storage_type, instance_reader: false
@@ -4,7 +4,8 @@ module Cloudinary::CarrierWave
4
4
  if respond_to?(:process_uri)
5
5
  uri = process_uri(uri)
6
6
  else # Backward compatibility with old CarrierWave
7
- uri = URI.parse(Cloudinary::Utils.smart_escape(Cloudinary::Utils.smart_unescape(uri)))
7
+ remote_url_unsafe_chars = /([^a-zA-Z0-9_.\-\/:?&=]+)/ # In addition allow query string characters: "?","&" and "="
8
+ uri = URI.parse(Cloudinary::Utils.smart_escape(Cloudinary::Utils.smart_unescape(uri), remote_url_unsafe_chars))
8
9
  end
9
10
  return if uri.to_s.blank?
10
11
  self.original_filename = @cache_id = @filename = File.basename(uri.path).gsub(/[^a-zA-Z0-9\.\-\+_]/, '')
@@ -1,7 +1,8 @@
1
1
  class Cloudinary::CarrierWave::Storage < ::CarrierWave::Storage::Abstract
2
2
 
3
3
  def store!(file)
4
- return if !uploader.enable_processing
4
+ return unless uploader.enable_processing
5
+
5
6
  if uploader.is_main_uploader?
6
7
  case file
7
8
  when Cloudinary::CarrierWave::PreloadedCloudinaryFile
@@ -438,7 +438,7 @@ begin
438
438
  # @return [::SassC::Script::Value::String]
439
439
  def cloudinary_url(public_id, sass_options = {})
440
440
  options = {}
441
- sass_options.to_h.each { |k, v| options[k.value] = v.value }
441
+ sass_options.to_h.each { |k, v| options[k.value.to_sym] = v.value }
442
442
  url = Cloudinary::Utils.cloudinary_url(public_id.value, {:type => :asset}.merge(options))
443
443
  ::SassC::Script::Value::String.new("url(#{url})")
444
444
  end
@@ -1,6 +1,6 @@
1
1
  class Cloudinary::Railtie < Rails::Railtie
2
2
  rake_tasks do
3
- Dir[File.join(File.dirname(__FILE__),'../tasks/*.rake')].each { |f| load f }
3
+ Dir[File.join(File.dirname(__FILE__),'../tasks/**/*.rake')].each { |f| load f }
4
4
  end
5
5
  config.after_initialize do |app|
6
6
  ActionView::Base.send :include, CloudinaryHelper
@@ -37,6 +37,7 @@ class Cloudinary::Uploader
37
37
  :eager_async => Cloudinary::Utils.as_safe_bool(options[:eager_async]),
38
38
  :eager_notification_url => options[:eager_notification_url],
39
39
  :exif => Cloudinary::Utils.as_safe_bool(options[:exif]),
40
+ :eval => options[:eval],
40
41
  :face_coordinates => Cloudinary::Utils.encode_double_array(options[:face_coordinates]),
41
42
  :faces => Cloudinary::Utils.as_safe_bool(options[:faces]),
42
43
  :folder => options[:folder],
@@ -64,7 +65,8 @@ class Cloudinary::Uploader
64
65
  :unique_filename => Cloudinary::Utils.as_safe_bool(options[:unique_filename]),
65
66
  :upload_preset => options[:upload_preset],
66
67
  :use_filename => Cloudinary::Utils.as_safe_bool(options[:use_filename]),
67
- :accessibility_analysis => Cloudinary::Utils.as_safe_bool(options[:accessibility_analysis])
68
+ :accessibility_analysis => Cloudinary::Utils.as_safe_bool(options[:accessibility_analysis]),
69
+ :metadata => Cloudinary::Utils.encode_context(options[:metadata])
68
70
  }
69
71
  params
70
72
  end
@@ -78,6 +80,9 @@ class Cloudinary::Uploader
78
80
  params = build_upload_params(options)
79
81
  if file.is_a?(Pathname)
80
82
  params[:file] = File.open(file, "rb")
83
+ elsif file.is_a?(StringIO)
84
+ file.rewind
85
+ params[:file] = Cloudinary::Blob.new(file.read, options)
81
86
  elsif file.respond_to?(:read) || Cloudinary::Utils.is_remote?(file)
82
87
  params[:file] = file
83
88
  else
@@ -268,6 +273,29 @@ class Cloudinary::Uploader
268
273
  return self.call_tags_api(nil, "remove_all", public_ids, options)
269
274
  end
270
275
 
276
+ # Populates metadata fields with the given values. Existing values will be overwritten.
277
+ #
278
+ # Any metadata-value pairs given are merged with any existing metadata-value pairs
279
+ # (an empty value for an existing metadata field clears the value).
280
+ #
281
+ # @param [Hash] metadata A list of custom metadata fields (by external_id) and the values to assign to each of them.
282
+ # @param [Array] public_ids An array of Public IDs of assets uploaded to Cloudinary.
283
+ # @param [Hash] options
284
+ # @option options [String] :resource_type The type of file. Default: image. Valid values: image, raw, video.
285
+ # @option options [String] :type The storage type. Default: upload. Valid values: upload, private, authenticated
286
+ # @return mixed a list of public IDs that were updated
287
+ # @raise [Cloudinary::Api:Error]
288
+ def self.update_metadata(metadata, public_ids, options = {})
289
+ self.call_api("metadata", options) do
290
+ {
291
+ timestamp: (options[:timestamp] || Time.now.to_i),
292
+ type: options[:type],
293
+ public_ids: Cloudinary::Utils.build_array(public_ids),
294
+ metadata: Cloudinary::Utils.encode_context(metadata)
295
+ }
296
+ end
297
+ end
298
+
271
299
  private
272
300
 
273
301
  def self.call_tags_api(tag, command, public_ids = [], options = {})
@@ -139,7 +139,10 @@ class Cloudinary::Utils
139
139
  zoom
140
140
  ].map(&:to_sym)
141
141
 
142
- REMOTE_URL_REGEX = %r(^ftp:|^https?:|^s3:|^gs:|^data:([\w-]+\/[\w-]+)?(;[\w-]+=[\w-]+)*;base64,([a-zA-Z0-9\/+\n=]+)$)
142
+ REMOTE_URL_REGEX = %r(^ftp:|^https?:|^s3:|^gs:|^data:([\w-]+\/[\w-]+(\+[\w-]+)?)?(;[\w-]+=[\w-]+)*;base64,([a-zA-Z0-9\/+\n=]+)$)
143
+
144
+ LONG_URL_SIGNATURE_LENGTH = 32
145
+ SHORT_URL_SIGNATURE_LENGTH = 8
143
146
 
144
147
  def self.extract_config_params(options)
145
148
  options.select{|k,v| URL_KEYS.include?(k)}
@@ -306,7 +309,7 @@ class Cloudinary::Utils
306
309
  "if_" + normalize_expression(if_value) unless if_value.to_s.empty?
307
310
  end
308
311
 
309
- EXP_REGEXP = Regexp.new(PREDEFINED_VARS.keys.join("|")+'|('+CONDITIONAL_OPERATORS.keys.reverse.map { |k| Regexp.escape(k) }.join('|')+')(?=[ _])')
312
+ EXP_REGEXP = Regexp.new('(?<!\$)('+PREDEFINED_VARS.keys.join("|")+')'+'|('+CONDITIONAL_OPERATORS.keys.reverse.map { |k| Regexp.escape(k) }.join('|')+')(?=[ _])')
310
313
  EXP_REPLACEMENT = PREDEFINED_VARS.merge(CONDITIONAL_OPERATORS)
311
314
 
312
315
  def self.normalize_expression(expression)
@@ -497,6 +500,7 @@ class Cloudinary::Utils
497
500
  url_suffix = options.delete(:url_suffix)
498
501
  use_root_path = config_option_consume(options, :use_root_path)
499
502
  auth_token = config_option_consume(options, :auth_token)
503
+ long_url_signature = config_option_consume(options, :long_url_signature)
500
504
  unless auth_token == false
501
505
  auth_token = Cloudinary::AuthToken.merge_auth_token(Cloudinary.config.auth_token, auth_token)
502
506
  end
@@ -541,7 +545,7 @@ class Cloudinary::Utils
541
545
  raise(CloudinaryException, "Must supply api_secret") if (secret.nil? || secret.empty?)
542
546
  to_sign = [transformation, sign_version && version, source_to_sign].reject(&:blank?).join("/")
543
547
  to_sign = fully_unescape(to_sign)
544
- signature = 's--' + Base64.urlsafe_encode64(Digest::SHA1.digest(to_sign + secret))[0,8] + '--'
548
+ signature = compute_signature(to_sign, secret, long_url_signature)
545
549
  end
546
550
 
547
551
  prefix = unsigned_download_url_prefix(source, cloud_name, private_cdn, cdn_subdomain, secure_cdn_subdomain, cname, secure, secure_distribution)
@@ -732,6 +736,18 @@ class Cloudinary::Utils
732
736
  download_archive_url(options.merge(:target_format => "zip"))
733
737
  end
734
738
 
739
+ # Creates and returns a URL that when invoked creates an archive of a folder.
740
+ #
741
+ # @param [Object] folder_path Full path (from the root) of the folder to download.
742
+ # @param [Hash] options Additional options.
743
+ #
744
+ # @return [String]
745
+ def self.download_folder(folder_path, options = {})
746
+ resource_type = options[:resource_type] || "all"
747
+
748
+ download_archive_url(options.merge(:resource_type => resource_type, :prefixes => folder_path))
749
+ end
750
+
735
751
  def self.signed_download_url(public_id, options = {})
736
752
  aws_private_key_path = options[:aws_private_key_path] || Cloudinary.config.aws_private_key_path
737
753
  if aws_private_key_path
@@ -1132,4 +1148,24 @@ class Cloudinary::Utils
1132
1148
  def self.is_remote?(url)
1133
1149
  REMOTE_URL_REGEX === url
1134
1150
  end
1151
+
1152
+ # Computes a short or long signature based on a message and secret
1153
+ # @param [String] message The string to sign
1154
+ # @param [String] secret A secret that will be added to the message when signing
1155
+ # @param [Boolean] long_signature Whether to create a short or long signature
1156
+ # @return [String] Properly formatted signature
1157
+ def self.compute_signature(message, secret, long_url_signature)
1158
+ combined_message_secret = message + secret
1159
+
1160
+ algo, signature_length =
1161
+ if long_url_signature
1162
+ [Digest::SHA256, LONG_URL_SIGNATURE_LENGTH]
1163
+ else
1164
+ [Digest::SHA1, SHORT_URL_SIGNATURE_LENGTH]
1165
+ end
1166
+
1167
+ "s--#{Base64.urlsafe_encode64(algo.digest(combined_message_secret))[0, signature_length]}--"
1168
+ end
1169
+
1170
+ private_class_method :compute_signature
1135
1171
  end
@@ -1,4 +1,4 @@
1
1
  # Copyright Cloudinary
2
2
  module Cloudinary
3
- VERSION = "1.15.0"
3
+ VERSION = "1.18.0"
4
4
  end
@@ -3,12 +3,33 @@ module CloudinaryHelper
3
3
  DEFAULT_POSTER_OPTIONS = { :format => 'jpg', :resource_type => 'video' }
4
4
  DEFAULT_SOURCE_TYPES = %w(webm mp4 ogv)
5
5
  DEFAULT_VIDEO_OPTIONS = { :resource_type => 'video' }
6
+ DEFAULT_SOURCES = [
7
+ {
8
+ :type => "mp4",
9
+ :codecs => "hev1",
10
+ :transformations => { :video_codec => "h265" }
11
+ },
12
+ {
13
+ :type => "webm",
14
+ :codecs => "vp9",
15
+ :transformations => { :video_codec => "vp9" }
16
+ },
17
+ {
18
+ :type => "mp4",
19
+ :transformations => { :video_codec => "auto" }
20
+ },
21
+ {
22
+ :type => "webm",
23
+ :transformations => { :video_codec => "auto" }
24
+ }
25
+ ]
6
26
 
7
27
  # Creates an HTML video tag for the provided +source+
8
28
  #
9
29
  # ==== Options
10
30
  # * <tt>:source_types</tt> - Specify which source type the tag should include. defaults to webm, mp4 and ogv.
11
31
  # * <tt>:source_transformation</tt> - specific transformations to use for a specific source type.
32
+ # * <tt>:sources</tt> - list of sources (overrides :source_types when present)
12
33
  # * <tt>:poster</tt> - override default thumbnail:
13
34
  # * url: provide an ad hoc url
14
35
  # * options: with specific poster transformations and/or Cloudinary +:public_id+
@@ -20,6 +41,17 @@ module CloudinaryHelper
20
41
  # cl_video_tag("mymovie.webm", :source_types => [:webm, :mp4], :poster => {:effect => 'sepia'}) do
21
42
  # content_tag( :span, "Cannot present video!")
22
43
  # end
44
+ # cl_video_tag("mymovie", :sources => [
45
+ # {
46
+ # :type => "mp4",
47
+ # :codecs => "hev1",
48
+ # :transformations => { :video_codec => "h265" }
49
+ # },
50
+ # {
51
+ # :type => "webm",
52
+ # :transformations => { :video_codec => "auto" }
53
+ # }
54
+ # ])
23
55
  def cl_video_tag(source, options = {}, &block)
24
56
  source = strip_known_ext(source)
25
57
  video_attributes = [:autoplay,:controls,:loop,:muted,:poster, :preload]
@@ -48,30 +80,12 @@ module CloudinaryHelper
48
80
  video_options[:poster] = cl_video_thumbnail_path(source, options)
49
81
  end
50
82
 
51
- source_transformation = options.delete(:source_transformation) || {}
52
- source_types = Array(options.delete(:source_types))
53
- fallback = (capture(&block) if block_given?) || options.delete(:fallback_content)
83
+ fallback = (capture(&block) if block_given?) || options.delete(:fallback_content)
54
84
 
55
- if source_types.size > 1
56
- cloudinary_tag(source, options) do |_source, tag_options|
57
- content_tag('video', tag_options.merge(video_options)) do
58
- source_tags = source_types.map do |type|
59
- transformation = source_transformation[type.to_sym] || {}
60
- cloudinary_tag("#{source}.#{type}", options.merge(transformation)) do |url, _tag_options|
61
- mime_type = "video/#{(type == 'ogv' ? 'ogg' : type)}"
62
- tag("source", :src => url, :type => mime_type)
63
- end
64
- end
65
- source_tags.push(fallback.html_safe) unless fallback.blank?
66
- safe_join(source_tags)
67
- end
68
- end
85
+ if options[:sources]
86
+ video_tag_from_sources(source, options, video_options, fallback)
69
87
  else
70
- transformation = source_transformation[source_types.first.to_sym] || {}
71
- video_options[:src] = cl_video_path("#{source}.#{source_types.first.to_sym}", transformation.merge(options))
72
- cloudinary_tag(source, options) do |_source, tag_options|
73
- content_tag('video', fallback, tag_options.merge(video_options))
74
- end
88
+ video_tag_from_source_types(source, options, video_options, fallback)
75
89
  end
76
90
  end
77
91
 
@@ -96,6 +110,66 @@ module CloudinaryHelper
96
110
  name.sub(/\.(#{DEFAULT_SOURCE_TYPES.join("|")})$/, '')
97
111
  end
98
112
 
113
+ private
114
+
115
+ def video_tag_from_source_types(source_name, options, video_options, fallback)
116
+ source_transformation = options.delete(:source_transformation) || {}
117
+ source_types = Array(options.delete(:source_types))
118
+
119
+ if source_types.size > 1
120
+ sources = source_types.map do |type|
121
+ {
122
+ :type => type,
123
+ :transformations => source_transformation[type.to_sym] || {}
124
+ }
125
+ end
126
+
127
+ generate_tag_from_sources(:source_name => source_name,
128
+ :sources => sources,
129
+ :options => options,
130
+ :video_options => video_options,
131
+ :fallback => fallback)
132
+ else
133
+ transformation = source_transformation[source_types.first.to_sym] || {}
134
+ video_options[:src] = cl_video_path("#{source_name}.#{source_types.first.to_sym}", transformation.merge(options))
135
+ cloudinary_tag(source_name, options) do |_source, tag_options|
136
+ content_tag('video', fallback, tag_options.merge(video_options))
137
+ end
138
+ end
139
+ end
140
+
141
+ def video_tag_from_sources(source_name, options, video_options, fallback)
142
+ sources = options.delete(:sources)
143
+
144
+ generate_tag_from_sources(:source_name => source_name,
145
+ :sources => sources,
146
+ :options => options,
147
+ :video_options => video_options,
148
+ :fallback => fallback)
149
+ end
150
+
151
+ def generate_tag_from_sources(params)
152
+ source_name, sources, options, video_options, fallback = params.values_at(:source_name, :sources, :options, :video_options, :fallback)
153
+
154
+ cloudinary_tag(source_name, options) do |_source, tag_options|
155
+ content_tag('video', tag_options.merge(video_options)) do
156
+ source_tags = sources.map do |source|
157
+ type = source[:type]
158
+ transformation = source[:transformations] || {}
159
+ cloudinary_tag("#{source_name}.#{type}", options.merge(transformation)) do |url, _tag_options|
160
+ mime_type = "video/#{(type == 'ogv' ? 'ogg' : type)}"
161
+ if source[:codecs]
162
+ codecs = source[:codecs].is_a?(Array) ? source[:codecs].join(", ") : source[:codecs]
163
+ mime_type = "#{mime_type}; codecs=#{codecs}"
164
+ end
165
+ tag("source", :src => url, :type => mime_type)
166
+ end
167
+ end
168
+ source_tags.push(fallback.html_safe) unless fallback.blank?
169
+ safe_join(source_tags)
170
+ end
171
+ end
172
+ end
99
173
  end
100
174
 
101
175
 
@@ -0,0 +1,48 @@
1
+ require 'tmpdir'
2
+ require 'rest_client'
3
+ require 'json'
4
+ require 'rubygems/package'
5
+
6
+ unless Rake::Task.task_defined?('cloudinary:fetch_assets') # prevent double-loading/execution
7
+ namespace :cloudinary do
8
+ desc "Fetch the latest JavaScript library files and create the JavaScript index files"
9
+ task :fetch_assets do
10
+ index_files = %w[jquery.ui.widget.js jquery.iframe-transport.js jquery.fileupload.js jquery.cloudinary.js]
11
+ processing_files = %w[canvas-to-blob.min.js load-image.all.min.js jquery.fileupload-process.js jquery.fileupload-image.js jquery.fileupload-validate.js]
12
+ files = index_files + processing_files
13
+
14
+ release = JSON(RestClient.get("https://api.github.com/repos/cloudinary/cloudinary_js/releases/latest"))
15
+
16
+ FileUtils.rm_rf 'vendor/assets'
17
+ html_folder = 'vendor/assets/html'
18
+ FileUtils.mkdir_p html_folder
19
+ js_folder = 'vendor/assets/javascripts/cloudinary'
20
+ FileUtils.mkdir_p js_folder
21
+
22
+ puts "Fetching cloudinary_js version #{release["tag_name"]}\n\n"
23
+ sio = StringIO.new(RestClient.get(release["tarball_url"]).body)
24
+ file = Zlib::GzipReader.new(sio)
25
+ tar = Gem::Package::TarReader.new(file)
26
+ tar.each_entry do |entry|
27
+ name = File.basename(entry.full_name)
28
+ if files.include? name
29
+ js_full_name = File.join(js_folder, name)
30
+ puts "Adding #{js_full_name}"
31
+ File.write js_full_name, entry.read
32
+ elsif name == 'cloudinary_cors.html'
33
+ html_full_name = File.join(html_folder, name)
34
+ puts "Adding #{html_full_name}"
35
+ File.write html_full_name, entry.read
36
+ end
37
+ end
38
+ puts "Creating 'index.js' and 'processing.js' files"
39
+ File.open("vendor/assets/javascripts/cloudinary/index.js", "w") do |f|
40
+ index_files.each { |name| f.puts "//= require ./#{name}" }
41
+ end
42
+ File.open("vendor/assets/javascripts/cloudinary/processing.js", "w") do |f|
43
+ processing_files.each { |name| f.puts "//= require ./#{name}" }
44
+ end
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+
3
+ API_ENDPOINT="https://sub-account-testing.cloudinary.com/create_sub_account"
4
+
5
+ SDK_NAME="${1}"
6
+
7
+ CLOUD_DETAILS=$(curl -sS -d "{\"prefix\" : \"${SDK_NAME}\"}" "${API_ENDPOINT}")
8
+
9
+ echo "${CLOUD_DETAILS}" | ruby -e "require 'json'; c=JSON.parse(ARGF.read)['payload']; puts 'cloudinary://' + c['cloudApiKey'] + ':'+ c['cloudApiSecret'] + '@' + c['cloudName']"
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+
3
+ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
4
+
5
+ RUBY_VER=$(ruby -v | head -n 1 | cut -d ' ' -f 2);
6
+ SDK_VER=$(grep -oiP '(?<=VERSION = ")([a-zA-Z0-9\-.]+)(?=")' lib/cloudinary/version.rb)
7
+
8
+
9
+ bash "${DIR}"/allocate_test_cloud.sh "Ruby ${RUBY_VER} SDK ${SDK_VER}"
@@ -12,6 +12,8 @@ QUOTE=
12
12
 
13
13
  NEW_VERSION=
14
14
 
15
+ UPDATE_ONLY=false
16
+
15
17
  function echo_err
16
18
  {
17
19
  echo "$@" 1>&2;
@@ -23,6 +25,7 @@ function usage
23
25
  echo " -v | --version <version> set a new version"
24
26
  echo " -c | --current show current version"
25
27
  echo " -d | --dry-run print the commands without executing them"
28
+ echo " -u | --update-only only update the version"
26
29
  echo " -h | --help print this information and exit"
27
30
  echo
28
31
  echo "For example: $0 -v 1.2.3"
@@ -30,7 +33,7 @@ function usage
30
33
 
31
34
  function process_arguments
32
35
  {
33
- while [ "$1" != "" ]; do
36
+ while [[ "$1" != "" ]]; do
34
37
  case $1 in
35
38
  -v | --version )
36
39
  shift
@@ -53,6 +56,11 @@ function process_arguments
53
56
  echo "Dry Run"
54
57
  echo ""
55
58
  ;;
59
+ -u | --update-only )
60
+ UPDATE_ONLY=true
61
+ echo "Only update version"
62
+ echo ""
63
+ ;;
56
64
  -h | --help )
57
65
  usage; return 0
58
66
  ;;
@@ -72,7 +80,7 @@ function pushd
72
80
  # Intentionally make popd silent
73
81
  function popd
74
82
  {
75
- command popd "$@" > /dev/null
83
+ command popd > /dev/null
76
84
  }
77
85
 
78
86
  # Check if one version is less than or equal than other
@@ -82,7 +90,7 @@ function popd
82
90
  # ver_lte 1.2.4 1.2.3 && echo "yes" || echo "no" # no
83
91
  function ver_lte
84
92
  {
85
- [ "$1" = "`echo -e "$1\n$2" | sort -V | head -n1`" ]
93
+ [[ "$1" = "`echo -e "$1\n$2" | sort -V | head -n1`" ]]
86
94
  }
87
95
 
88
96
  # Extract the last entry or entry for a given version
@@ -108,6 +116,10 @@ function verify_dependencies
108
116
  return 1
109
117
  fi
110
118
 
119
+ if [[ "${UPDATE_ONLY}" = true ]]; then
120
+ return 0;
121
+ fi
122
+
111
123
  if [[ -z "$(type -t git-changelog)" ]]
112
124
  then
113
125
  echo_err "git-extras packages is not installed."
@@ -127,25 +139,26 @@ function safe_replace
127
139
 
128
140
  grep -q "${old}" "${file}" || { echo_err "${old} was not found in ${file}"; return 1; }
129
141
 
130
- ${CMD_PREFIX} sed -E -i '.bak' "${QUOTE}s/${old}/${new}/${QUOTE}" "${file}"
142
+ ${CMD_PREFIX} sed -i.bak -e "${QUOTE}s/${old}/${new}/${QUOTE}" -- "${file}" && rm -- "${file}.bak"
131
143
  }
132
144
 
133
145
  function current_version
134
146
  {
135
- grep -oiP '(?<=VERSION = ")([0-9.]+[^ ]*)(?=")' lib/cloudinary/version.rb
147
+ grep -oiP '(?<=VERSION = ")([a-zA-Z0-9\-.]+)(?=")' lib/cloudinary/version.rb
136
148
  }
137
149
 
138
150
  function update_version
139
151
  {
140
- if [ -z "${NEW_VERSION}" ]; then
152
+ if [[ -z "${NEW_VERSION}" ]]; then
141
153
  usage; return 1
142
154
  fi
143
155
 
144
156
  # Enter git root
145
157
  pushd $(git rev-parse --show-toplevel)
158
+
146
159
  local current_version=$(current_version)
147
160
 
148
- if [ -z "${current_version}" ]; then
161
+ if [[ -z "${current_version}" ]]; then
149
162
  echo_err "Failed getting current version, please check directory structure and/or contact developer"
150
163
  return 1
151
164
  fi
@@ -166,12 +179,17 @@ function update_version
166
179
  lib/cloudinary/version.rb\
167
180
  || return 1
168
181
 
169
- ${CMD_PREFIX} git changelog -t ${NEW_VERSION} || true
182
+ if [[ "${UPDATE_ONLY}" = true ]]; then
183
+ popd;
184
+ return 0;
185
+ fi
186
+
187
+ ${CMD_PREFIX} git changelog -t "${NEW_VERSION}" || true
170
188
 
171
189
  echo ""
172
190
  echo "# After editing CHANGELOG.md, optionally review changes and issue these commands:"
173
191
  echo git add lib/cloudinary/version.rb CHANGELOG.md
174
- echo git commit -m \"Version ${NEW_VERSION}\"
192
+ echo git commit -m "\"Version ${NEW_VERSION}\""
175
193
  echo sed -e "'1,/^${NEW_VERSION//./\\.}/d'" \
176
194
  -e "'/^=/d'" \
177
195
  -e "'/^$/d'" \
@@ -180,7 +198,7 @@ function update_version
180
198
  \| git tag -a "'${NEW_VERSION}'" --file=-
181
199
 
182
200
  # Don't run those commands on dry run
183
- [ -n "${CMD_PREFIX}" ] && { popd; return 0; }
201
+ [[ -n "${CMD_PREFIX}" ]] && { popd; return 0; }
184
202
 
185
203
  echo ""
186
204
  read -p "Run the above commands automatically? (y/N): " confirm && [[ ${confirm} == [yY] || ${confirm} == [yY][eE][sS] ]] || { popd; return 0; }
@@ -197,6 +215,6 @@ function update_version
197
215
  popd
198
216
  }
199
217
 
218
+ process_arguments "$@"
200
219
  verify_dependencies
201
- process_arguments $*
202
220
  update_version
@@ -1403,7 +1403,8 @@ var slice = [].slice,
1403
1403
  "*": "mul",
1404
1404
  "/": "div",
1405
1405
  "+": "add",
1406
- "-": "sub"
1406
+ "-": "sub",
1407
+ "^": "pow",
1407
1408
  };
1408
1409
 
1409
1410
 
@@ -1472,30 +1473,33 @@ var slice = [].slice,
1472
1473
  return new this(expressionStr);
1473
1474
  };
1474
1475
 
1475
-
1476
1476
  /**
1477
1477
  * Normalize a string expression
1478
1478
  * @function Cloudinary#normalize
1479
1479
  * @param {string} expression a expression, e.g. "w gt 100", "width_gt_100", "width > 100"
1480
1480
  * @return {string} the normalized form of the value expression, e.g. "w_gt_100"
1481
1481
  */
1482
-
1483
1482
  Expression.normalize = function(expression) {
1484
- var operators, pattern, replaceRE;
1483
+ var operators, operatorsPattern, operatorsReplaceRE, predefinedVarsPattern, predefinedVarsReplaceRE;
1485
1484
  if (expression == null) {
1486
1485
  return expression;
1487
1486
  }
1488
1487
  expression = String(expression);
1489
- operators = "\\|\\||>=|<=|&&|!=|>|=|<|/|-|\\+|\\*";
1490
- pattern = "((" + operators + ")(?=[ _])|" + Object.keys(Expression.PREDEFINED_VARS).join("|") + ")";
1491
- replaceRE = new RegExp(pattern, "g");
1492
- expression = expression.replace(replaceRE, function(match) {
1493
- return Expression.OPERATORS[match] || Expression.PREDEFINED_VARS[match];
1494
- });
1488
+ operators = "\\|\\||>=|<=|&&|!=|>|=|<|/|-|\\+|\\*|\\^";
1489
+
1490
+ // operators
1491
+ operatorsPattern = "((" + operators + ")(?=[ _]))";
1492
+ operatorsReplaceRE = new RegExp(operatorsPattern, "g");
1493
+ expression = expression.replace(operatorsReplaceRE, match => Expression.OPERATORS[match]);
1494
+
1495
+ // predefined variables
1496
+ predefinedVarsPattern = "(" + Object.keys(Expression.PREDEFINED_VARS).join("|") + ")";
1497
+ predefinedVarsReplaceRE = new RegExp(predefinedVarsPattern, "g");
1498
+ expression = expression.replace(predefinedVarsReplaceRE, (match, p1, offset) => (expression[offset - 1] === '$' ? match : Expression.PREDEFINED_VARS[match]));
1499
+
1495
1500
  return expression.replace(/[ _]+/g, '_');
1496
1501
  };
1497
1502
 
1498
-
1499
1503
  /**
1500
1504
  * Serialize the expression
1501
1505
  * @return {string} the expression as a string
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cloudinary
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.15.0
4
+ version: 1.18.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nadav Soferman
8
8
  - Itai Lahan
9
9
  - Tal Lev-Ami
10
- autorequire:
10
+ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2020-06-11 00:00:00.000000000 Z
13
+ date: 2020-09-27 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: aws_cf_signer
@@ -110,6 +110,20 @@ dependencies:
110
110
  - - ">="
111
111
  - !ruby/object:Gem::Version
112
112
  version: '3.5'
113
+ - !ruby/object:Gem::Dependency
114
+ name: rspec-retry
115
+ requirement: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ type: :development
121
+ prerelease: false
122
+ version_requirements: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
113
127
  - !ruby/object:Gem::Dependency
114
128
  name: rails
115
129
  requirement: !ruby/object:Gem::Requirement
@@ -177,6 +191,7 @@ extra_rdoc_files: []
177
191
  files:
178
192
  - ".github/ISSUE_TEMPLATE/bug_report.md"
179
193
  - ".github/ISSUE_TEMPLATE/feature_request.md"
194
+ - ".github/pull_request_template.md"
180
195
  - ".gitignore"
181
196
  - ".rspec"
182
197
  - ".travis.yml"
@@ -220,7 +235,10 @@ files:
220
235
  - lib/cloudinary/utils.rb
221
236
  - lib/cloudinary/version.rb
222
237
  - lib/cloudinary/video_helper.rb
223
- - lib/tasks/cloudinary.rake
238
+ - lib/tasks/cloudinary/fetch_assets.rake
239
+ - lib/tasks/cloudinary/sync_static.rake
240
+ - tools/allocate_test_cloud.sh
241
+ - tools/get_test_cloud.sh
224
242
  - tools/update_version
225
243
  - vendor/assets/html/cloudinary_cors.html
226
244
  - vendor/assets/javascripts/cloudinary/canvas-to-blob.min.js
@@ -238,7 +256,7 @@ homepage: http://cloudinary.com
238
256
  licenses:
239
257
  - MIT
240
258
  metadata: {}
241
- post_install_message:
259
+ post_install_message:
242
260
  rdoc_options: []
243
261
  require_paths:
244
262
  - lib
@@ -254,7 +272,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
254
272
  version: '0'
255
273
  requirements: []
256
274
  rubygems_version: 3.1.4
257
- signing_key:
275
+ signing_key:
258
276
  specification_version: 4
259
277
  summary: Client library for easily using the Cloudinary service
260
278
  test_files: []