cloudinary 1.11.1 → 1.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +42 -0
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +21 -0
  4. data/.github/pull_request_template.md +24 -0
  5. data/.gitignore +6 -1
  6. data/.travis.yml +15 -5
  7. data/CHANGELOG.md +149 -0
  8. data/Rakefile +3 -45
  9. data/cloudinary.gemspec +20 -4
  10. data/lib/active_storage/blob_key.rb +20 -0
  11. data/lib/active_storage/service/cloudinary_service.rb +229 -0
  12. data/lib/cloudinary.rb +31 -22
  13. data/lib/cloudinary/api.rb +173 -5
  14. data/lib/cloudinary/auth_token.rb +6 -4
  15. data/lib/cloudinary/carrier_wave.rb +3 -1
  16. data/lib/cloudinary/carrier_wave/remote.rb +3 -2
  17. data/lib/cloudinary/carrier_wave/storage.rb +2 -1
  18. data/lib/cloudinary/cloudinary_controller.rb +2 -4
  19. data/lib/cloudinary/helper.rb +30 -3
  20. data/lib/cloudinary/railtie.rb +7 -3
  21. data/lib/cloudinary/uploader.rb +35 -6
  22. data/lib/cloudinary/utils.rb +112 -40
  23. data/lib/cloudinary/version.rb +1 -1
  24. data/lib/cloudinary/video_helper.rb +96 -22
  25. data/lib/tasks/cloudinary/fetch_assets.rake +48 -0
  26. data/lib/tasks/{cloudinary.rake → cloudinary/sync_static.rake} +0 -0
  27. data/tools/allocate_test_cloud.sh +9 -0
  28. data/tools/get_test_cloud.sh +9 -0
  29. data/tools/update_version +29 -11
  30. data/vendor/assets/javascripts/cloudinary/jquery.cloudinary.js +48 -14
  31. data/vendor/assets/javascripts/cloudinary/jquery.fileupload.js +24 -4
  32. data/vendor/assets/javascripts/cloudinary/jquery.ui.widget.js +741 -561
  33. data/vendor/assets/javascripts/cloudinary/load-image.all.min.js +1 -1
  34. metadata +62 -67
  35. data/spec/access_control_spec.rb +0 -102
  36. data/spec/api_spec.rb +0 -567
  37. data/spec/archive_spec.rb +0 -129
  38. data/spec/auth_token_spec.rb +0 -77
  39. data/spec/cache_spec.rb +0 -109
  40. data/spec/cloudinary_helper_spec.rb +0 -325
  41. data/spec/cloudinary_spec.rb +0 -32
  42. data/spec/data/sync_static/app/assets/javascripts/1.coffee +0 -1
  43. data/spec/data/sync_static/app/assets/javascripts/1.js +0 -1
  44. data/spec/data/sync_static/app/assets/stylesheets/1.css +0 -3
  45. data/spec/docx.docx +0 -0
  46. data/spec/favicon.ico +0 -0
  47. data/spec/image_spec.rb +0 -107
  48. data/spec/logo.png +0 -0
  49. data/spec/rake_spec.rb +0 -160
  50. data/spec/sample_asset_file.tsv +0 -4
  51. data/spec/search_spec.rb +0 -109
  52. data/spec/spec_helper.rb +0 -266
  53. data/spec/storage_spec.rb +0 -44
  54. data/spec/streaminig_profiles_api_spec.rb +0 -74
  55. data/spec/support/helpers/temp_file_helpers.rb +0 -22
  56. data/spec/support/shared_contexts/rake.rb +0 -19
  57. data/spec/uploader_spec.rb +0 -392
  58. data/spec/utils_methods_spec.rb +0 -54
  59. data/spec/utils_spec.rb +0 -970
  60. data/spec/video_tag_spec.rb +0 -253
  61. data/spec/video_url_spec.rb +0 -185
@@ -29,8 +29,8 @@ module Cloudinary
29
29
  OLD_AKAMAI_SHARED_CDN = "cloudinary-a.akamaihd.net"
30
30
  SHARED_CDN = AKAMAI_SHARED_CDN
31
31
 
32
- USER_AGENT = "CloudinaryRuby/" + VERSION
33
- @@user_platform = ""
32
+ USER_AGENT = "CloudinaryRuby/#{VERSION} (Ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL})"
33
+ @@user_platform = defined?(Rails.version) ? "Rails/#{Rails.version}" : ""
34
34
 
35
35
  # Add platform information to the USER_AGENT header
36
36
  # This is intended for platform information and not individual applications!
@@ -44,7 +44,7 @@ module Cloudinary
44
44
 
45
45
  def self.USER_AGENT
46
46
  if @@user_platform.empty?
47
- "#{USER_AGENT}"
47
+ USER_AGENT
48
48
  else
49
49
  "#{@@user_platform} #{USER_AGENT}"
50
50
  end
@@ -64,19 +64,7 @@ module Cloudinary
64
64
  first_time = @@config.nil?
65
65
  @@config ||= OpenStruct.new((YAML.load(ERB.new(IO.read(config_dir.join("cloudinary.yml"))).result)[config_env] rescue {}))
66
66
 
67
- # Heroku support
68
- if first_time && ENV["CLOUDINARY_CLOUD_NAME"]
69
- set_config(
70
- "cloud_name" => ENV["CLOUDINARY_CLOUD_NAME"],
71
- "api_key" => ENV["CLOUDINARY_API_KEY"],
72
- "api_secret" => ENV["CLOUDINARY_API_SECRET"],
73
- "secure_distribution" => ENV["CLOUDINARY_SECURE_DISTRIBUTION"],
74
- "private_cdn" => ENV["CLOUDINARY_PRIVATE_CDN"].to_s == 'true',
75
- "secure" => ENV["CLOUDINARY_SECURE"].to_s == 'true'
76
- )
77
- elsif first_time && ENV["CLOUDINARY_URL"]
78
- config_from_url(ENV["CLOUDINARY_URL"])
79
- end
67
+ config_from_env if first_time
80
68
 
81
69
  set_config(new_config) if new_config
82
70
  yield(@@config) if block_given?
@@ -86,7 +74,12 @@ module Cloudinary
86
74
 
87
75
  def self.config_from_url(url)
88
76
  @@config ||= OpenStruct.new
89
- uri = URI.parse(url)
77
+ return unless url && !url.empty?
78
+ uri = URI.parse(url)
79
+ if !uri.scheme || "cloudinary" != uri.scheme.downcase
80
+ raise(CloudinaryException,
81
+ "Invalid CLOUDINARY_URL scheme. Expecting to start with 'cloudinary://'")
82
+ end
90
83
  set_config(
91
84
  "cloud_name" => uri.host,
92
85
  "api_key" => uri.user,
@@ -100,7 +93,7 @@ module Cloudinary
100
93
  if isNestedKey? key
101
94
  putNestedKey key, value
102
95
  else
103
- set_config(key => URI.decode(value))
96
+ set_config(key => Utils.smart_unescape(value))
104
97
  end
105
98
  end
106
99
  end
@@ -135,18 +128,34 @@ module Cloudinary
135
128
  end
136
129
 
137
130
  private
138
-
131
+
132
+ def self.config_from_env
133
+ # Heroku support
134
+ if ENV["CLOUDINARY_CLOUD_NAME"]
135
+ config_keys = ENV.keys.select! { |key| key.start_with? "CLOUDINARY_" }
136
+ config_keys -= ["CLOUDINARY_URL"] # ignore it when explicit options are passed
137
+ config_keys.each do |full_key|
138
+ conf_key = full_key["CLOUDINARY_".length..-1].downcase # convert "CLOUDINARY_CONFIG_NAME" to "config_name"
139
+ conf_val = ENV[full_key]
140
+ conf_val = conf_val == 'true' if %w[true false].include?(conf_val) # cast relevant boolean values
141
+ set_config(conf_key => conf_val)
142
+ end
143
+ elsif ENV["CLOUDINARY_URL"]
144
+ config_from_url(ENV["CLOUDINARY_URL"])
145
+ end
146
+ end
147
+
139
148
  def self.config_env
140
149
  return ENV["CLOUDINARY_ENV"] if ENV["CLOUDINARY_ENV"]
141
150
  return Rails.env if defined? Rails::env
142
151
  nil
143
152
  end
144
-
153
+
145
154
  def self.config_dir
146
- return Pathname.new(ENV["CLOUDINARY_CONFIG_DIR"]) if ENV["CLOUDINARY_CONFIG_DIR"]
155
+ return Pathname.new(ENV["CLOUDINARY_CONFIG_DIR"]) if ENV["CLOUDINARY_CONFIG_DIR"]
147
156
  self.app_root.join("config")
148
157
  end
149
-
158
+
150
159
  def self.set_config(new_config)
151
160
  new_config.each{|k,v| @@config.send(:"#{k}=", v) if !v.nil?}
152
161
  end
@@ -78,8 +78,9 @@ class Cloudinary::Api
78
78
  resource_type = options[:resource_type] || "image"
79
79
  type = options[:type] || "upload"
80
80
  uri = "resources/#{resource_type}/#{type}/#{public_id}"
81
- call_api(:get, uri,
82
- only(options,
81
+ call_api(:get, uri,
82
+ only(options,
83
+ :cinemagraph_analysis,
83
84
  :colors,
84
85
  :coordinates,
85
86
  :exif,
@@ -88,7 +89,9 @@ class Cloudinary::Api
88
89
  :max_results,
89
90
  :pages,
90
91
  :phash,
91
- :quality_analysis
92
+ :quality_analysis,
93
+ :derived_next_cursor,
94
+ :accessibility_analysis
92
95
  ), options)
93
96
  end
94
97
 
@@ -226,11 +229,21 @@ class Cloudinary::Api
226
229
  end
227
230
 
228
231
  def self.root_folders(options={})
229
- call_api(:get, "folders", {}, options)
232
+ params = only(options, :max_results, :next_cursor)
233
+ call_api(:get, "folders", params, options)
230
234
  end
231
235
 
232
236
  def self.subfolders(of_folder_path, options={})
233
- call_api(:get, "folders/#{of_folder_path}", {}, options)
237
+ params = only(options, :max_results, :next_cursor)
238
+ call_api(:get, "folders/#{of_folder_path}", params, options)
239
+ end
240
+
241
+ def self.delete_folder(path, options={})
242
+ call_api(:delete, "folders/#{path}", {}, options)
243
+ end
244
+
245
+ def self.create_folder(folder_name, options={})
246
+ call_api(:post, "folders/#{folder_name}", {}, options)
234
247
  end
235
248
 
236
249
  def self.upload_mappings(options={})
@@ -335,6 +348,144 @@ class Cloudinary::Api
335
348
  call_json_api('GET', json_url, {}, 60, {})
336
349
  end
337
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
+
338
489
  protected
339
490
 
340
491
  def self.call_api(method, uri, params, options)
@@ -343,6 +494,7 @@ class Cloudinary::Api
343
494
  api_key = options[:api_key] || Cloudinary.config.api_key || raise("Must supply api_key")
344
495
  api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise("Must supply api_secret")
345
496
  timeout = options[:timeout] || Cloudinary.config.timeout || 60
497
+ uri = Cloudinary::Utils.smart_escape(uri)
346
498
  api_url = [cloudinary, "v1_1", cloud_name, uri].join("/")
347
499
  # Add authentication
348
500
  api_url.sub!(%r(^(https?://)), "\\1#{api_key}:#{api_secret}@")
@@ -382,6 +534,22 @@ class Cloudinary::Api
382
534
  raise GeneralError.new("Error parsing server response (#{response.code}) - #{response.body}. Got - #{e}")
383
535
  end
384
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
+
385
553
  def self.only(hash, *keys)
386
554
  result = {}
387
555
  keys.each do |key|
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'openssl'
2
4
  if RUBY_VERSION > "2"
3
5
  require "ostruct"
@@ -10,6 +12,7 @@ module Cloudinary
10
12
  module AuthToken
11
13
  SEPARATOR = '~'
12
14
  UNSAFE = /[ "#%&\'\/:;<=>?@\[\\\]^`{\|}~]/
15
+ EMPTY_TOKEN = {}.freeze
13
16
 
14
17
  def self.generate(options = {})
15
18
  key = options[:key]
@@ -42,12 +45,11 @@ module Cloudinary
42
45
  "#{name}=#{token.join(SEPARATOR)}"
43
46
  end
44
47
 
45
-
46
48
  # Merge token2 to token1 returning a new
47
49
  # Requires to support Ruby 1.9
48
50
  def self.merge_auth_token(token1, token2)
49
- token1 = token1 || {}
50
- token2 = token2 || {}
51
+ token1 = token1 || EMPTY_TOKEN
52
+ token2 = token2 || EMPTY_TOKEN
51
53
  token1 = token1.respond_to?( :to_h) ? token1.to_h : token1
52
54
  token2 = token2.respond_to?( :to_h) ? token2.to_h : token2
53
55
  token1.merge(token2)
@@ -69,4 +71,4 @@ module Cloudinary
69
71
  OpenSSL::HMAC.hexdigest(digest, bin_key, message)
70
72
  end
71
73
  end
72
- end
74
+ end
@@ -9,8 +9,10 @@ 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
- base.class_attribute :storage_type, :metadata
14
+ base.class_attribute :metadata
15
+ base.class_attribute :storage_type, instance_reader: false
14
16
  override_in_versions(base, :blank?, :full_public_id, :my_public_id, :all_versions_processors, :stored_version)
15
17
  end
16
18
 
@@ -1,10 +1,11 @@
1
1
  module Cloudinary::CarrierWave
2
2
  def download!(uri, *args)
3
- return super if !self.cloudinary_should_handle_remote?
3
+ return super unless self.cloudinary_should_handle_remote?
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(URI.escape(URI.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
@@ -1,13 +1,11 @@
1
1
  module Cloudinary::CloudinaryController
2
2
  protected
3
-
3
+
4
4
  def valid_cloudinary_response?
5
5
  received_signature = request.query_parameters[:signature]
6
6
  calculated_signature = Cloudinary::Utils.api_sign_request(
7
7
  request.query_parameters.select{|key, value| [:public_id, :version].include?(key.to_sym)},
8
8
  Cloudinary.config.api_secret)
9
9
  return received_signature == calculated_signature
10
- end
10
+ end
11
11
  end
12
-
13
- ActionController::Base.send :include, Cloudinary::CloudinaryController
@@ -286,7 +286,7 @@ module CloudinaryHelper
286
286
  Cloudinary::Utils.private_download_url(public_id, format, options)
287
287
  end
288
288
 
289
- # Helper method that uses the deprecated ZIP download API.
289
+ # Helper method that uses the deprecated ZIP download API.
290
290
  # Replaced by cl_download_zip_url that uses the more advanced and robust archive generation and download API
291
291
  # @deprecated
292
292
  def cl_zip_download_url(tag, options = {})
@@ -296,7 +296,7 @@ module CloudinaryHelper
296
296
  # @see {Cloudinary::Utils.download_archive_url}
297
297
  def cl_download_archive_url(options = {})
298
298
  Cloudinary::Utils.download_archive_url(options)
299
- end
299
+ end
300
300
 
301
301
  # @see {Cloudinary::Utils.download_zip_url}
302
302
  def cl_download_zip_url(options = {})
@@ -318,7 +318,7 @@ module CloudinaryHelper
318
318
  if Cloudinary.config.enhance_image_tag
319
319
  alias_method :image_tag, :image_tag_with_cloudinary
320
320
  alias_method :image_path, :image_path_with_cloudinary
321
- end
321
+ end
322
322
  end
323
323
  end
324
324
 
@@ -419,3 +419,30 @@ rescue LoadError
419
419
  # no sass support. Ignore.
420
420
  end
421
421
 
422
+ begin
423
+ require 'sassc'
424
+ require 'sassc/script/functions'
425
+ module SassC::Script::Functions
426
+ # Helper method for generating cloudinary_url in scss files.
427
+ #
428
+ # As opposed to sass(deprecated), optional named arguments are not supported, use hash map instead.
429
+ #
430
+ # Example:
431
+ # Sass: cloudinary-url("sample", $quality: "auto", $fetch_format: "auto");
432
+ # becomes
433
+ # SassC: cloudinary-url("sample", ("quality": "auto", "fetch_format": "auto"));
434
+ #
435
+ # @param [::SassC::Script::Value::String] public_id The public ID of the resource
436
+ # @param [::SassC::Script::Value::Map] sass_options Additional options
437
+ #
438
+ # @return [::SassC::Script::Value::String]
439
+ def cloudinary_url(public_id, sass_options = {})
440
+ options = {}
441
+ sass_options.to_h.each { |k, v| options[k.value.to_sym] = v.value }
442
+ url = Cloudinary::Utils.cloudinary_url(public_id.value, {:type => :asset}.merge(options))
443
+ ::SassC::Script::Value::String.new("url(#{url})")
444
+ end
445
+ end
446
+ rescue LoadError
447
+ # no sassc support. Ignore.
448
+ end