cloudinary 1.7.0 → 1.8.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
  SHA1:
3
- metadata.gz: 154b7abbf7ae3c462f1277f49dfed3cfdd4ef60c
4
- data.tar.gz: 56a961c58cc636da0830ba4e1db3f486fe1dffd4
3
+ metadata.gz: 7614adaac6c80b6af25b391e1224a492c609fde8
4
+ data.tar.gz: 726d2520ec2adbb6d91b5ec6b1bceb46071d58e9
5
5
  SHA512:
6
- metadata.gz: 7fc132e944b87f3bd829f3e45a16609c7e4743a8fa331fad6d62a490a6f1625c533ea26b9eb50d1da71123abc5948166d5b606694b4765562877d255f54f93e9
7
- data.tar.gz: 5a1ca4908473ea368ed4a2c1753852879fc75bf3dba22819f44637728de83c6a69f0a48482b5729e46d9f582a3c70748deeaad5dc683a8a2fa831ec784c3b302
6
+ metadata.gz: 760b8e34412a2e4272edc42032aa6abf92cb655472dccd4c35e39126adbce44660b44a0210956ebf28708bc5bb234504f17799023b7062bf6812c257d7aca072
7
+ data.tar.gz: 9b8869009e05cb9ee5b100cd5652a49e653aedd992dc350a55375a527dff0b5209d0fdadce221d8c7253a3c4b7dca66d2e5b8bcfcd1fb469b8bfa692d562604a
@@ -1,4 +1,18 @@
1
1
 
2
+ 1.8.0 / 2017-05-01
3
+ ==================
4
+
5
+ New functionality and features
6
+ ------------------------------
7
+
8
+ * Add Search API
9
+ * Sync static for non image assets (#241) fixes #27
10
+
11
+ Other Changes
12
+ -------------
13
+
14
+ * Fix Carrierwave signed URL.
15
+
2
16
  1.7.0 / 2017-04-09
3
17
  ==================
4
18
 
@@ -23,6 +23,7 @@ Gem::Specification.new do |s|
23
23
  s.add_dependency "aws_cf_signer"
24
24
  s.add_development_dependency "rspec", '>=3.5'
25
25
  s.add_development_dependency "rspec-rails"
26
+ s.add_development_dependency "rake"
26
27
 
27
28
  if RUBY_VERSION > "2.0"
28
29
  s.add_dependency "rest-client"
@@ -22,6 +22,7 @@ module Cloudinary
22
22
  autoload :PreloadedFile, "cloudinary/preloaded_file"
23
23
  autoload :Static, "cloudinary/static"
24
24
  autoload :CarrierWave, "cloudinary/carrier_wave"
25
+ autoload :Search, "cloudinary/search"
25
26
 
26
27
  CF_SHARED_CDN = "d3jpl91pxevbkh.cloudfront.net"
27
28
  AKAMAI_SHARED_CDN = "res.cloudinary.com"
@@ -1,4 +1,5 @@
1
1
  require 'rest_client'
2
+ require 'json'
2
3
 
3
4
  class Cloudinary::Api
4
5
  class Error < CloudinaryException; end
@@ -316,7 +317,14 @@ class Cloudinary::Api
316
317
  # Add authentication
317
318
  api_url.sub!(%r(^(https?://)), "\\1#{api_key}:#{api_secret}@")
318
319
 
319
- RestClient::Request.execute(:method => method, :url => api_url, :payload => params.reject { |k, v| v.nil? || v=="" }, :timeout => timeout, :headers => { "User-Agent" => Cloudinary::USER_AGENT }) do
320
+ headers = { "User-Agent" => Cloudinary::USER_AGENT }
321
+ if options[:content_type]== :json
322
+ payload = params.to_json
323
+ headers.merge!("Content-Type"=> 'application/json', "Accept"=> 'application/json')
324
+ else
325
+ payload = params.reject { |k, v| v.nil? || v=="" }
326
+ end
327
+ RestClient::Request.execute(:method => method, :url => api_url, :payload => payload, :timeout => timeout, :headers => headers) do
320
328
  |response, request, tmpresult|
321
329
  return Response.new(response) if response.code == 200
322
330
  exception_class = case response.code
@@ -44,8 +44,9 @@ module Cloudinary::CarrierWave
44
44
  public_id = self.default_public_id
45
45
  return nil if public_id.nil?
46
46
  else
47
- public_id = options.include?(:version) ? self.my_public_id : self.full_public_id
48
- end
47
+ public_id = self.my_public_id
48
+ options[:version] ||= self.stored_version
49
+ end
49
50
  options = self.transformation.merge(options) if self.version_name.present?
50
51
 
51
52
  Cloudinary::Utils.cloudinary_url(public_id, {:format=>self.format, :resource_type=>self.resource_type, :type=>self.storage_type}.merge(options))
@@ -0,0 +1,55 @@
1
+ class Cloudinary::Search
2
+ def initialize
3
+ @query_hash = {
4
+ :sort_by => [],
5
+ :aggregate => [],
6
+ :with_field => []
7
+ }
8
+ end
9
+
10
+ ## implicitly generate an instance delegate the method
11
+ def self.method_missing(method_name, *arguments)
12
+ instance = new
13
+ instance.send(method_name, *arguments)
14
+ end
15
+
16
+ def expression(value)
17
+ @query_hash[:expression] = value
18
+ self
19
+ end
20
+
21
+ def max_results(value)
22
+ @query_hash[:max_results] = value
23
+ self
24
+ end
25
+
26
+ def next_cursor(value)
27
+ @query_hash[:next_cursor] = value
28
+ self
29
+ end
30
+
31
+ def sort_by(field_name, dir = 'desc')
32
+ @query_hash[:sort_by].push(field_name => dir)
33
+ self
34
+ end
35
+
36
+ def aggregate(value)
37
+ @query_hash[:aggregate].push(value)
38
+ self
39
+ end
40
+
41
+ def with_field(value)
42
+ @query_hash[:with_field].push(value)
43
+ self
44
+ end
45
+
46
+ def to_h
47
+ @query_hash.select { |_, value| !value.nil? && !(value.is_a?(Array) && value.empty?) }
48
+ end
49
+
50
+ def execute(options = {})
51
+ options[:content_type] = :json
52
+ uri = 'resources/search'
53
+ Cloudinary::Api.call_api(:post, uri, to_h, options)
54
+ end
55
+ end
@@ -3,126 +3,236 @@ require 'time'
3
3
  require 'set'
4
4
  class Cloudinary::Static
5
5
  IGNORE_FILES = [".svn", "CVS", "RCS", ".git", ".hg"]
6
- SUPPORTED_IMAGES = [/\.(gif|jpe?g|png|bmp|ico|webp|wdp|jxr|jp2|svg|pdf)$/i]
7
- STATIC_IMAGE_DIRS = ["app/assets/images", "lib/assets/images", "vendor/assets/images", "public/images"]
6
+ DEFAULT_IMAGE_DIRS = ["app/assets/images", "lib/assets/images", "vendor/assets/images", "public/images"]
7
+ DEFAULT_IMAGE_EXTENSION_MASK = 'gif|jpe?g|png|bmp|ico|webp|wdp|jxr|jp2|svg|pdf'
8
8
  METADATA_FILE = ".cloudinary.static"
9
9
  METADATA_TRASH_FILE = ".cloudinary.static.trash"
10
-
11
- def self.discover
12
- ignore_files = Cloudinary.config.ignore_files || IGNORE_FILES
13
- relative_dirs = Cloudinary.config.static_image_dirs || STATIC_IMAGE_DIRS
14
- dirs = relative_dirs.map{|dir| self.root.join(dir)}.select(&:exist?)
15
- dirs.each do
16
- |dir|
17
- dir.find do
18
- |path|
19
- file = path.basename.to_s
20
- if ignore_files.any?{|pattern| pattern.is_a?(String) ? pattern == file : file.match(pattern)}
21
- Find.prune
22
- next
23
- elsif path.directory?
10
+
11
+ class << self
12
+ def sync(options={})
13
+ options = options.clone
14
+ delete_missing = options.delete(:delete_missing)
15
+ found_paths = Set.new
16
+ found_public_paths = {}
17
+ found_public_ids = Set.new
18
+ metadata = build_metadata
19
+ metadata_lines = []
20
+ counts = { :not_changed => 0, :uploaded => 0, :deleted => 0, :not_found => 0}
21
+ discover_all do |path, public_path|
22
+ next if found_paths.include?(path)
23
+ if found_public_paths[public_path]
24
+ print "Warning: duplicate #{public_path} in #{path} - already taken from #{found_public_paths[public_path]}\n"
24
25
  next
25
- elsif SUPPORTED_IMAGES.none?{|pattern| pattern.is_a?(String) ? pattern == file : file.match(pattern)}
26
- next
26
+ end
27
+ found_paths << path
28
+ found_public_paths[public_path] = path
29
+ data = root.join(path).read(:mode=>"rb")
30
+ ext = path.extname
31
+ format = ext[1..-1]
32
+ md5 = Digest::MD5.hexdigest(data)
33
+ public_id = "#{public_path.basename(ext)}-#{md5}"
34
+ found_public_ids << public_id
35
+ item_metadata = metadata.delete(public_path.to_s)
36
+ if item_metadata && item_metadata["public_id"] == public_id # Signature match
37
+ counts[:not_changed] += 1
38
+ print "#{public_path} - #{public_id} - Not changed\n"
39
+ result = item_metadata
27
40
  else
28
- relative_path = path.relative_path_from(self.root)
29
- public_path = path.relative_path_from(dir.dirname)
30
- yield(relative_path, public_path)
41
+ counts[:uploaded] += 1
42
+ print "#{public_path} - #{public_id} - Uploading\n"
43
+ result = Cloudinary::Uploader.upload(Cloudinary::Blob.new(data, :original_filename=>path.to_s),
44
+ options.merge(:format=>format, :public_id=>public_id, :type=>:asset, :resource_type=>resource_type(path.to_s))
45
+ ).merge("upload_time"=>Time.now)
31
46
  end
47
+ metadata_lines << [public_path, public_id, result["upload_time"].to_i, result["version"], result["width"], result["height"]].join("\t")+"\n"
32
48
  end
49
+ File.open(metadata_file_path, "w"){|f| f.print(metadata_lines.join)}
50
+ metadata.to_a.each do |path, info|
51
+ counts[:not_found] += 1
52
+ print "#{path} - #{info["public_id"]} - Not found\n"
53
+ end
54
+ # Files no longer needed
55
+ trash = metadata.to_a + build_metadata(metadata_trash_file_path, false).reject{|public_path, info| found_public_ids.include?(info["public_id"])}
56
+
57
+ if delete_missing
58
+ trash.each do
59
+ |path, info|
60
+ counts[:deleted] += 1
61
+ print "#{path} - #{info["public_id"]} - Deleting\n"
62
+ Cloudinary::Uploader.destroy(info["public_id"], options.merge(:type=>:asset))
63
+ end
64
+ FileUtils.rm_f(metadata_trash_file_path)
65
+ else
66
+ # Add current removed file to the trash file.
67
+ metadata_lines = trash.map do
68
+ |public_path, info|
69
+ [public_path, info["public_id"], info["upload_time"].to_i, info["version"], info["width"], info["height"]].join("\t")+"\n"
70
+ end
71
+ File.open(metadata_trash_file_path, "w"){|f| f.print(metadata_lines.join)}
72
+ end
73
+
74
+ print "\nCompleted syncing static resources to Cloudinary\n"
75
+ print counts.sort.reject{|k,v| v == 0}.map{|k,v| "#{v} #{k.to_s.gsub('_', ' ').capitalize}"}.join(", ") + "\n"
33
76
  end
34
- end
35
77
 
36
- def self.root
37
- Cloudinary.app_root
38
- end
78
+ # ## Cloudinary::Utils support ###
79
+ def public_id_and_resource_type_from_path(path)
80
+ metadata = build_metadata
81
+ path = path.sub(/^\//, '')
82
+ data = metadata[path]
83
+ unless data
84
+ public_prefixes.each do |prefix|
85
+ if metadata[File.join(prefix, path)]
86
+ data = metadata[File.join(prefix, path)]
87
+ break
88
+ end
89
+ end
90
+ end
91
+ return unless data
92
+ [data['public_id'], resource_type(path)]
93
+ end
39
94
 
40
- def self.metadata_file_path
41
- self.root.join(METADATA_FILE)
42
- end
95
+ private
96
+ def root
97
+ Cloudinary.app_root
98
+ end
43
99
 
44
- def self.metadata_trash_file_path
45
- self.root.join(METADATA_TRASH_FILE)
46
- end
47
-
48
- def self.metadata(metadata_file = metadata_file_path, hash=true)
49
- metadata = []
50
- if File.exist?(metadata_file)
51
- IO.foreach(metadata_file) do
100
+ def metadata_file_path
101
+ root.join(METADATA_FILE)
102
+ end
103
+
104
+ def metadata_trash_file_path
105
+ root.join(METADATA_TRASH_FILE)
106
+ end
107
+
108
+ def build_metadata(metadata_file = metadata_file_path, hash = true)
109
+ metadata = []
110
+ if File.exist?(metadata_file)
111
+ IO.foreach(metadata_file) do
52
112
  |line|
53
- line.strip!
54
- next if line.blank?
55
- path, public_id, upload_time, version, width, height = line.split("\t")
56
- metadata << [path, {
57
- "public_id" => public_id,
58
- "upload_time" => Time.at(upload_time.to_i).getutc,
59
- "version" => version,
60
- "width" => width.to_i,
61
- "height" => height.to_i
62
- }]
113
+ line.strip!
114
+ next if line.blank?
115
+ path, public_id, upload_time, version, width, height = line.split("\t")
116
+ metadata << [path, {
117
+ "public_id" => public_id,
118
+ "upload_time" => Time.at(upload_time.to_i).getutc,
119
+ "version" => version,
120
+ "width" => width.to_i,
121
+ "height" => height.to_i
122
+ }]
123
+ end
63
124
  end
125
+ hash ? Hash[*metadata.flatten] : metadata
64
126
  end
65
- hash ? Hash[*metadata.flatten] : metadata
66
- end
67
127
 
68
- def self.sync(options={})
69
- options = options.clone
70
- delete_missing = options.delete(:delete_missing)
71
- metadata = self.metadata
72
- found_paths = Set.new
73
- found_public_ids = Set.new
74
- metadata_lines = []
75
- counts = { :not_changed => 0, :uploaded => 0, :deleted => 0, :not_found => 0}
76
- self.discover do
77
- |path, public_path|
78
- next if found_paths.include?(path)
79
- found_paths << path
80
- data = self.root.join(path).read(:mode=>"rb")
81
- ext = path.extname
82
- format = ext[1..-1]
83
- md5 = Digest::MD5.hexdigest(data)
84
- public_id = "#{public_path.basename(ext)}-#{md5}"
85
- found_public_ids << public_id
86
- current_metadata = metadata.delete(public_path.to_s)
87
- if current_metadata && current_metadata["public_id"] == public_id # Signature match
88
- counts[:not_changed] += 1
89
- $stderr.print "#{public_path} - #{public_id} - Not changed\n"
90
- result = current_metadata
91
- else
92
- counts[:uploaded] += 1
93
- $stderr.print "#{public_path} - #{public_id} - Uploading\n"
94
- result = Cloudinary::Uploader.upload(Cloudinary::Blob.new(data, :original_filename=>path.to_s),
95
- options.merge(:format=>format, :public_id=>public_id, :type=>:asset)
96
- ).merge("upload_time"=>Time.now)
128
+ def discover_all(&block)
129
+ static_file_config.each do |group, data|
130
+ print "-> Syncing #{group}...\n"
131
+ discover(absolutize(data['dirs']), extension_matcher_for(group), &block)
132
+ print "=========================\n"
133
+ end
134
+ end
135
+
136
+ def discover(dirs, matcher)
137
+ return unless matcher
138
+
139
+ dirs.each do |dir|
140
+ print "Scanning #{dir.relative_path_from(root)}...\n"
141
+ dir.find do |path|
142
+ file = path.basename.to_s
143
+ if ignore_file?(file)
144
+ Find.prune
145
+ next
146
+ elsif path.directory? || !matcher.call(path.to_s)
147
+ next
148
+ else
149
+ relative_path = path.relative_path_from(root)
150
+ public_path = path.relative_path_from(dir.dirname)
151
+ yield(relative_path, public_path)
152
+ end
153
+ end
97
154
  end
98
- metadata_lines << [public_path, public_id, result["upload_time"].to_i, result["version"], result["width"], result["height"]].join("\t")+"\n"
99
155
  end
100
- File.open(self.metadata_file_path, "w"){|f| f.print(metadata_lines.join)}
101
- metadata.to_a.each do |path, info|
102
- counts[:not_found] += 1
103
- $stderr.print "#{path} - #{info["public_id"]} - Not found\n"
156
+
157
+ def ignore_file?(file)
158
+ matches?(file, Cloudinary.config.ignore_files || IGNORE_FILES)
104
159
  end
105
- # Files no longer needed
106
- trash = metadata.to_a + self.metadata(metadata_trash_file_path, false).reject{|public_path, info| found_public_ids.include?(info["public_id"])}
107
-
108
- if delete_missing
109
- trash.each do
110
- |path, info|
111
- counts[:deleted] += 1
112
- $stderr.print "#{path} - #{info["public_id"]} - Deleting\n"
113
- Cloudinary::Uploader.destroy(info["public_id"], options.merge(:type=>:asset))
160
+
161
+ # Test for matching either strings or regexps
162
+ def matches?(target, patterns)
163
+ Array(patterns).any? {|pattern| pattern.is_a?(String) ? pattern == target : target.match(pattern)}
164
+ end
165
+
166
+ def extension_matcher_for(group)
167
+ group = group.to_s
168
+ return unless static_file_config[group]
169
+ @matchers = {}
170
+ @matchers[group] ||= ->(target) do
171
+ !!target.match(extension_mask_to_regex(static_file_config[group]['file_mask']))
114
172
  end
115
- FileUtils.rm_f(self.metadata_trash_file_path)
116
- else
117
- # Add current removed file to the trash file.
118
- metadata_lines = trash.map do
119
- |public_path, info|
120
- [public_path, info["public_id"], info["upload_time"].to_i, info["version"], info["width"], info["height"]].join("\t")+"\n"
173
+ end
174
+
175
+ def static_file_config
176
+ @static_file_config ||= begin
177
+ config = Cloudinary.config.static_files || {}
178
+
179
+ # Default
180
+ config['images'] ||= {}
181
+ config['images']['dirs'] ||= Cloudinary.config.static_image_dirs # Backwards compatibility
182
+ config['images']['dirs'] ||= DEFAULT_IMAGE_DIRS
183
+ config['images']['file_mask'] ||= DEFAULT_IMAGE_EXTENSION_MASK
184
+ # Validate
185
+ config.each do |group, data|
186
+ unless data && data['dirs'] && data['file_mask']
187
+ print "In config, static_files group '#{group}' needs to have both 'dirs' and 'file_mask' defined.\n"
188
+ exit
189
+ end
190
+ end
191
+
192
+ config
121
193
  end
122
- File.open(self.metadata_trash_file_path, "w"){|f| f.print(metadata_lines.join)}
194
+ end
195
+
196
+ def reset_static_file_config!
197
+ @static_file_config = nil
198
+ end
199
+
200
+ def image?(path)
201
+ extension_matcher_for(:images).call(path)
202
+ end
203
+
204
+ def resource_type(path)
205
+ if image?(path)
206
+ :image
207
+ else
208
+ :raw
209
+ end
210
+ end
211
+
212
+ def extension_mask_to_regex(extension_mask)
213
+ extension_mask && /\.(?:#{extension_mask})$/i
214
+ end
215
+
216
+ def public_prefixes
217
+ @public_prefixes ||= static_file_config.reduce([]) do |result, (group, data)|
218
+ result << data['dirs'].map { |dir| Pathname.new(dir).basename.to_s }
219
+ end.flatten.uniq
220
+ end
221
+
222
+ def absolutize(dirs)
223
+ dirs.map do |relative_dir|
224
+ absolute_dir = root.join(relative_dir)
225
+ if absolute_dir.exist?
226
+ absolute_dir
227
+ else
228
+ print "Skipping #{relative_dir} (does not exist)\n"
229
+ nil
230
+ end
231
+ end.compact
123
232
  end
124
233
 
125
- $stderr.print "\nCompleted syncing static resources to Cloudinary\n"
126
- $stderr.print counts.sort.reject{|k,v| v == 0}.map{|k,v| "#{v} #{k.to_s.gsub('_', ' ').capitalize}"}.join(", ") + "\n"
234
+ def print(s)
235
+ $stderr.print(s)
236
+ end
127
237
  end
128
238
  end
@@ -376,21 +376,20 @@ class Cloudinary::Utils
376
376
  source = source.to_s
377
377
  if !force_remote
378
378
  return original_source if (type.nil? || type == "asset") && source.match(%r(^https?:/)i)
379
- if source.start_with?("/")
379
+ if type == "asset"
380
+ # config.static_image_support left for backwards compatibility
381
+ if (Cloudinary.config.static_file_support || Cloudinary.config.static_image_support) && defined?(Cloudinary::Static)
382
+ source, resource_type = Cloudinary::Static.public_id_and_resource_type_from_path(source)
383
+ end
384
+ return original_source unless source
385
+ source += File.extname(original_source) if !format
386
+ elsif source.start_with?("/")
380
387
  if source.start_with?("/images/")
381
388
  source = source.sub(%r(/images/), '')
382
389
  else
383
390
  return original_source
384
391
  end
385
392
  end
386
- @metadata ||= defined?(Cloudinary::Static) ? Cloudinary::Static.metadata : {}
387
- if type == "asset" && @metadata["images/#{source}"]
388
- return original_source if !Cloudinary.config.static_image_support
389
- source = @metadata["images/#{source}"]["public_id"]
390
- source += File.extname(original_source) if !format
391
- elsif type == "asset"
392
- return original_source # requested asset, but no metadata - probably local file. return.
393
- end
394
393
  end
395
394
 
396
395
  resource_type, type = finalize_resource_type(resource_type, type, url_suffix, use_root_path, shorten)
@@ -1,4 +1,4 @@
1
1
  # Copyright Cloudinary
2
2
  module Cloudinary
3
- VERSION = "1.7.0"
3
+ VERSION = "1.8.0"
4
4
  end
@@ -1,7 +1,9 @@
1
- namespace :cloudinary do
2
- desc "Sync static resources with cloudinary"
3
- task :sync_static => :environment do
4
- delete_missing = ENV["DELETE_MISSING"] == 'true' || ENV["DELETE_MISSING"] == '1'
5
- Cloudinary::Static.sync(:delete_missing=>delete_missing)
1
+ unless Rake::Task.task_defined?('cloudinary:sync_static') # prevent double-loading/execution
2
+ namespace :cloudinary do
3
+ desc "Sync static resources with cloudinary"
4
+ task :sync_static do
5
+ delete_missing = ENV['DELETE_MISSING'] == 'true' || ENV['DELETE_MISSING'] == '1'
6
+ Cloudinary::Static.sync(:delete_missing => delete_missing)
7
+ end
6
8
  end
7
- end
9
+ end
@@ -10,14 +10,15 @@ describe Cloudinary::Api do
10
10
  test_id_1 = "#{prefix}_1"
11
11
  test_id_2 = "#{prefix}_2"
12
12
  test_id_3 = "#{prefix}_3"
13
+ test_key = "test_key_#{SUFFIX}"
13
14
  before(:all) do
14
15
 
15
16
  @api = Cloudinary::Api
16
17
  Cloudinary::Uploader.upload(TEST_IMG, :public_id => test_id_1, :tags => [TEST_TAG, TIMESTAMP_TAG], :context => "key=value", :eager =>[:width =>TEST_WIDTH, :crop =>:scale])
17
18
  Cloudinary::Uploader.upload(TEST_IMG, :public_id => test_id_2, :tags => [TEST_TAG, TIMESTAMP_TAG], :context => "key=value", :eager =>[:width =>TEST_WIDTH, :crop =>:scale])
18
19
  Cloudinary::Uploader.upload(TEST_IMG, :public_id => test_id_3, :tags => [TEST_TAG, TIMESTAMP_TAG], :context => "key=value", :eager =>[:width =>TEST_WIDTH, :crop =>:scale])
19
- Cloudinary::Uploader.upload(TEST_IMG, :public_id => test_id_1, :tags => [TEST_TAG, TIMESTAMP_TAG], :context => "test-key=test", :eager =>[:width =>TEST_WIDTH, :crop =>:scale])
20
- Cloudinary::Uploader.upload(TEST_IMG, :public_id => test_id_3, :tags => [TEST_TAG, TIMESTAMP_TAG], :context => "test-key=tasty", :eager =>[:width =>TEST_WIDTH, :crop =>:scale])
20
+ Cloudinary::Uploader.upload(TEST_IMG, :public_id => test_id_1, :tags => [TEST_TAG, TIMESTAMP_TAG], :context => "#{test_key}=test", :eager =>[:width =>TEST_WIDTH, :crop =>:scale])
21
+ Cloudinary::Uploader.upload(TEST_IMG, :public_id => test_id_3, :tags => [TEST_TAG, TIMESTAMP_TAG], :context => "#{test_key}=tasty", :eager =>[:width =>TEST_WIDTH, :crop =>:scale])
21
22
  end
22
23
 
23
24
  after(:all) do
@@ -79,9 +80,9 @@ describe Cloudinary::Api do
79
80
  end
80
81
 
81
82
  it "should allow listing resources by context" do
82
- resources = @api.resources_by_context('test-key')["resources"]
83
+ resources = @api.resources_by_context(test_key)["resources"]
83
84
  expect(resources.count).to eq(2)
84
- resources = @api.resources_by_context('test-key','test')["resources"]
85
+ resources = @api.resources_by_context(test_key,'test')["resources"]
85
86
  expect(resources.count).to eq(1)
86
87
  end
87
88
 
@@ -0,0 +1 @@
1
+ coffeescript = true
@@ -0,0 +1 @@
1
+ { alert("I am javascript!");}
@@ -0,0 +1,3 @@
1
+ p#css {
2
+ color: red;
3
+ }
@@ -0,0 +1,141 @@
1
+ require 'spec_helper'
2
+ require 'cloudinary'
3
+
4
+ require 'tmpdir'
5
+ require 'pathname'
6
+
7
+ describe 'cloudinary:sync_static' do
8
+ include_context 'rake' # context needs to have the exact rake task as name
9
+ include Helpers::TempFileHelpers
10
+
11
+ before(:all) do
12
+ copy_root_to_temp('spec/data/sync_static/')
13
+
14
+ # Reuse some existing spec assets so as not to add weight to the project
15
+ copy_file_to_temp('spec/logo.png', 'app/assets/images/logo1.png')
16
+ copy_file_to_temp('spec/logo.png', 'app/assets/images/logo2.png')
17
+ copy_file_to_temp('samples/basic/lake.jpg', 'app/assets/images/lake1.jpg')
18
+ copy_file_to_temp('samples/basic/lake.jpg', 'public/images/lake2.jpg')
19
+ end
20
+
21
+ after(:all) do
22
+ clean_up_temp_files!
23
+ end
24
+
25
+ before (:each) do
26
+ allow(Cloudinary).to receive(:app_root).and_return(Pathname.new(temp_root))
27
+ Cloudinary::Static.send(:reset_static_file_config!)
28
+ end
29
+
30
+ after (:each) do
31
+ # destroy all uploaded assets
32
+ Cloudinary::Static.send(:build_metadata).each do |_, data|
33
+ Cloudinary::Uploader.destroy(data['public_id'], {:type => :asset})
34
+ end
35
+ # delete metadata_file_path
36
+ FileUtils.rm_f(Cloudinary::Static.send(:metadata_file_path))
37
+ FileUtils.rm_f(Cloudinary::Static.send(:metadata_trash_file_path))
38
+ end
39
+
40
+ it 'should find correct file when running in default configuration' do
41
+ # with default settings, 4 images only will be uploaded
42
+ subject.invoke
43
+ expect(Cloudinary::Static.send(:build_metadata).size).to eq 4
44
+ end
45
+
46
+ it 'should respect deprecated static_image_dirs config setting' do
47
+ allow(Cloudinary.config).to receive(:static_image_dirs).and_return(['public/images'])
48
+ subject.invoke
49
+ expect(Cloudinary::Static.send(:build_metadata).size).to eq 1
50
+ end
51
+
52
+ it 'should allow to specify only dirs in images group, taking the file_mask from default' do
53
+ allow(Cloudinary.config).to receive(:static_files).and_return({
54
+ 'images' => {
55
+ 'dirs' => ['public/images']
56
+ }
57
+ })
58
+ subject.invoke
59
+ expect(Cloudinary::Static.send(:build_metadata).size).to eq 1
60
+ end
61
+
62
+ it 'should allow to specify only file_mask in images group, taking the dirs from default' do
63
+ allow(Cloudinary.config).to receive(:static_files).and_return({
64
+ 'images' => {
65
+ 'file_mask' => 'png'
66
+ }
67
+ })
68
+ subject.invoke
69
+ expect(Cloudinary::Static.send(:build_metadata).size).to eq 2
70
+ end
71
+
72
+ it 'should allow to specify regex expressions in file_mask' do
73
+ allow(Cloudinary.config).to receive(:static_files).and_return({
74
+ 'images' => {
75
+ 'file_mask' => 'p.?g|jp.?'
76
+ },
77
+ 'javascripts' => {
78
+ 'dirs' => ['app/assets/javascripts'],
79
+ 'file_mask' => 'js'
80
+ },
81
+ 'stylesheets' => {
82
+ 'dirs' => ['app/assets/stylesheets'],
83
+ 'file_mask' => 'css'
84
+ }
85
+ })
86
+ subject.invoke
87
+ expect(Cloudinary::Static.send(:build_metadata).size).to eq 6
88
+ end
89
+
90
+ context 'Cloudinary::Utils.cloudinary_url' do
91
+ def all_asset_forms_of(public_id)
92
+ [public_id, "/#{public_id}", public_id.split('/').last]
93
+ end
94
+
95
+ RSpec::Matchers.define :be_asset_mapped_by_cloudinary_url_to do |expected|
96
+ match do |actual|
97
+ actual.all? do |public_path|
98
+ Cloudinary::Utils.cloudinary_url(public_path, :cloud_name => 'test', :type => 'asset') == expected
99
+ end
100
+ end
101
+ end
102
+
103
+ before(:each) do
104
+ allow(Cloudinary.config).to receive(:static_files).and_return({
105
+ 'images' => {
106
+ 'dirs' => ['app/assets/images'],
107
+ 'file_mask' => 'p.?g|jp.?'
108
+ },
109
+ 'javascripts' => {
110
+ 'dirs' => ['app/assets/javascripts'],
111
+ 'file_mask' => 'js'
112
+ },
113
+ 'stylesheets' => {
114
+ 'dirs' => ['app/assets/stylesheets'],
115
+ 'file_mask' => 'css'
116
+ }
117
+ })
118
+
119
+ end
120
+
121
+ it 'should return Cloudinary asset urls for assets when Cloudinary.config.static_file_support is true' do
122
+ allow(Cloudinary.config).to receive(:static_file_support).and_return(true)
123
+ subject.invoke
124
+
125
+ expect(all_asset_forms_of('images/logo1.png')).to be_asset_mapped_by_cloudinary_url_to('http://res.cloudinary.com/test/image/asset/logo1-7dc60722d4653261648038b579fdb89e.png')
126
+ expect(all_asset_forms_of('javascripts/1.js')).to be_asset_mapped_by_cloudinary_url_to('http://res.cloudinary.com/test/raw/asset/1-b01de57adb485efdde843154d030644e.js')
127
+ expect(all_asset_forms_of('stylesheets/1.css')).to be_asset_mapped_by_cloudinary_url_to('http://res.cloudinary.com/test/raw/asset/1-f24cc6123afd401ab86d8596cabc619f.css')
128
+
129
+ # without :type => 'asset'
130
+ expect(Cloudinary::Utils.cloudinary_url('logo1.png')).not_to include('7dc60722d4653261648038b579fdb89e')
131
+ end
132
+
133
+ it 'should return Cloudinary asset urls for assets when Cloudinary.config.static_image_support is true (backwards compatibility)' do
134
+ allow(Cloudinary.config).to receive(:static_image_support).and_return(true)
135
+ subject.invoke
136
+
137
+ expect(all_asset_forms_of('images/logo1.png')).to be_asset_mapped_by_cloudinary_url_to('http://res.cloudinary.com/test/image/asset/logo1-7dc60722d4653261648038b579fdb89e.png')
138
+ end
139
+ end
140
+
141
+ end
@@ -0,0 +1,109 @@
1
+ require 'spec_helper'
2
+ require 'cloudinary'
3
+
4
+ describe Cloudinary::Search do
5
+ context 'unit' do
6
+ it 'should create empty json' do
7
+ query_hash = Cloudinary::Search.to_h
8
+ expect(query_hash).to eq({})
9
+ end
10
+
11
+ it 'should always return same object in fluent interface' do
12
+ instance = Cloudinary::Search.new
13
+ %w(expression sort_by max_results next_cursor aggregate with_field).each do |method|
14
+ same_instance = instance.send(method, 'emptyarg')
15
+ expect(instance).to eq(same_instance)
16
+ end
17
+ end
18
+
19
+ it 'should add expression to query' do
20
+ query = Cloudinary::Search.expression('format:jpg').to_h
21
+ expect(query).to eq(expression: 'format:jpg')
22
+ end
23
+
24
+ it 'should add sort_by to query' do
25
+ query = Cloudinary::Search.sort_by('created_at', 'asc').sort_by('updated_at', 'desc').to_h
26
+ expect(query).to eq(sort_by: [{ 'created_at' => 'asc' }, { 'updated_at' => 'desc' }])
27
+ end
28
+
29
+ it 'should add max_results to query' do
30
+ query = Cloudinary::Search.max_results('format:jpg').to_h
31
+ expect(query).to eq(max_results: 'format:jpg')
32
+ end
33
+
34
+ it 'should add next_cursor to query' do
35
+ query = Cloudinary::Search.next_cursor('format:jpg').to_h
36
+ expect(query).to eq(next_cursor: 'format:jpg')
37
+ end
38
+
39
+ it 'should add aggregations arguments as array to query' do
40
+ query = Cloudinary::Search.aggregate('format').aggregate('size_category').to_h
41
+ expect(query).to eq(aggregate: %w(format size_category))
42
+ end
43
+
44
+ it 'should add with_field to query' do
45
+ query = Cloudinary::Search.with_field('context').with_field('tags').to_h
46
+ expect(query).to eq(with_field: %w(context tags))
47
+ end
48
+ end
49
+
50
+ context 'integration' do
51
+ SEARCH_TAG = TIMESTAMP_TAG + "_search"
52
+ include_context 'cleanup', SEARCH_TAG
53
+ prefix = "api_test_#{SUFFIX}"
54
+ test_id_1 = "#{prefix}_1"
55
+ test_id_2 = "#{prefix}_2"
56
+ test_id_3 = "#{prefix}_3"
57
+ before(:all) do
58
+ Cloudinary::Uploader.upload(TEST_IMG, public_id: test_id_1, tags: [TEST_TAG, TIMESTAMP_TAG, SEARCH_TAG], context: 'stage=in_review')
59
+ Cloudinary::Uploader.upload(TEST_IMG, public_id: test_id_2, tags: [TEST_TAG, TIMESTAMP_TAG, SEARCH_TAG], context: 'stage=new')
60
+ Cloudinary::Uploader.upload(TEST_IMG, public_id: test_id_3, tags: [TEST_TAG, TIMESTAMP_TAG, SEARCH_TAG], context: 'stage=validated')
61
+ sleep(3)
62
+ end
63
+
64
+ it "should return all images tagged with #{SEARCH_TAG}" do
65
+ results = Cloudinary::Search.expression("tags:#{SEARCH_TAG}").execute
66
+ expect(results['resources'].count).to eq 3
67
+ end
68
+
69
+ it "should return resource #{test_id_1}" do
70
+ results = Cloudinary::Search.expression("public_id:#{test_id_1}").execute
71
+ expect(results['resources'].count).to eq 1
72
+ end
73
+
74
+ it 'should paginate resources limited by tag and ordered by ascending public_id' do
75
+ results = Cloudinary::Search.max_results(1).expression("tags:#{SEARCH_TAG}").sort_by('public_id', 'asc').execute
76
+ expect(results['resources'].count).to eq 1
77
+ expect(results['resources'][0]['public_id']).to eq test_id_1
78
+ expect(results['total_count']).to eq 3
79
+
80
+ results = Cloudinary::Search.max_results(1).expression("tags:#{SEARCH_TAG}").sort_by('public_id', 'asc').next_cursor(results['next_cursor']).execute
81
+ expect(results['resources'].count).to eq 1
82
+ expect(results['resources'][0]['public_id']).to eq test_id_2
83
+ expect(results['total_count']).to eq 3
84
+
85
+ results = Cloudinary::Search.max_results(1).expression("tags:#{SEARCH_TAG}").sort_by('public_id', 'asc').next_cursor(results['next_cursor']).execute
86
+ expect(results['resources'].count).to eq 1
87
+ expect(results['resources'][0]['public_id']).to eq test_id_3
88
+ expect(results['total_count']).to eq 3
89
+ expect(results['next_cursor']).to be_nil
90
+ end
91
+
92
+ it 'should include context' do
93
+ results = Cloudinary::Search.expression("tags:#{SEARCH_TAG}").with_field('context').execute
94
+ expect(results['resources'].count).to eq 3
95
+ results['resources'].each do |res|
96
+ expect(res['context'].keys).to eq ['stage']
97
+ end
98
+ end
99
+ it 'should include context, tags and image_metadata' do
100
+ results = Cloudinary::Search.expression("tags:#{SEARCH_TAG}").with_field('context').with_field('tags').with_field('image_metadata').execute
101
+ expect(results['resources'].count).to eq 3
102
+ results['resources'].each do |res|
103
+ expect(res['context'].keys).to eq ['stage']
104
+ expect(res.key?('image_metadata')).to eq true
105
+ expect(res['tags'].count).to eq 3
106
+ end
107
+ end
108
+ end
109
+ end
@@ -16,6 +16,14 @@ KEY = "00112233FF99"
16
16
  ALT_KEY = "CCBB2233FF00"
17
17
 
18
18
 
19
+ Dir[File.join(File.dirname(__FILE__), '/support/**/*.rb')].each {|f| require f}
20
+
21
+ module RSpec
22
+ def self.project_root
23
+ File.join(File.dirname(__FILE__), '..')
24
+ end
25
+ end
26
+
19
27
  # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
20
28
  RSpec.configure do |config|
21
29
  unless RSpec::Version::STRING.match( /^3/)
@@ -136,7 +144,13 @@ end
136
144
  RSpec::Matchers.define :be_served_by_cloudinary do
137
145
  match do |url|
138
146
  if url.is_a? Array
139
- url = Cloudinary::Utils.cloudinary_url( url[0], url[1])
147
+ url, options = url
148
+ url = Cloudinary::Utils.cloudinary_url(url, options.clone)
149
+ if Cloudinary.config.upload_prefix
150
+ res_prefix_uri = URI.parse(Cloudinary.config.upload_prefix)
151
+ res_prefix_uri.path = '/res'
152
+ url.gsub!(/https?:\/\/res.cloudinary.com/, res_prefix_uri.to_s)
153
+ end
140
154
  end
141
155
  code = 0
142
156
  @url = url
@@ -0,0 +1,22 @@
1
+ module Helpers
2
+ module TempFileHelpers
3
+ def clean_up_temp_files!
4
+ FileUtils.remove_entry temp_root
5
+ end
6
+
7
+ def temp_root
8
+ @temp_root ||= Dir.mktmpdir 'test_root'
9
+ end
10
+
11
+ def copy_root_to_temp(source)
12
+ source = File.join(RSpec.project_root, source) unless Pathname.new(source).directory?
13
+ FileUtils.copy_entry source, temp_root
14
+ end
15
+
16
+ def copy_file_to_temp(source, dest)
17
+ dest_path = File.join(temp_root, dest)
18
+ FileUtils.mkdir_p(File.dirname(dest_path))
19
+ FileUtils.copy_entry(File.join(RSpec.project_root, source), dest_path)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ require "rake"
2
+
3
+ # From https://robots.thoughtbot.com/test-rake-tasks-like-a-boss
4
+ shared_context "rake" do
5
+ let(:rake) { Rake::Application.new }
6
+ let(:task_name) { self.class.top_level_description }
7
+ let(:task_path) { "lib/tasks/#{task_name.split(":").first}" }
8
+ subject { rake[task_name] }
9
+
10
+ def loaded_files_excluding_current_rake_file
11
+ $".reject {|file| file == Pathname.new(RSpec.project_root).join("#{task_path}.rake").to_s }
12
+ end
13
+
14
+ before do
15
+ Rake.application = rake
16
+ Rake.application.rake_require(task_path, [RSpec.project_root], loaded_files_excluding_current_rake_file)
17
+ Rake::Task.define_task(:environment)
18
+ end
19
+ end
@@ -15,5 +15,4 @@ describe Utils do
15
15
  it "should parse a percent range value" do
16
16
  expect(Utils.instance_eval { norm_range_value("20p") }).to eq("20p")
17
17
  end
18
-
19
18
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cloudinary
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.0
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nadav Soferman
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2017-04-09 00:00:00.000000000 Z
13
+ date: 2017-04-30 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: aws_cf_signer
@@ -54,6 +54,20 @@ dependencies:
54
54
  - - ">="
55
55
  - !ruby/object:Gem::Version
56
56
  version: '0'
57
+ - !ruby/object:Gem::Dependency
58
+ name: rake
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ type: :development
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
57
71
  - !ruby/object:Gem::Dependency
58
72
  name: rest-client
59
73
  requirement: !ruby/object:Gem::Requirement
@@ -148,6 +162,7 @@ files:
148
162
  - lib/cloudinary/ostruct2.rb
149
163
  - lib/cloudinary/preloaded_file.rb
150
164
  - lib/cloudinary/railtie.rb
165
+ - lib/cloudinary/search.rb
151
166
  - lib/cloudinary/static.rb
152
167
  - lib/cloudinary/uploader.rb
153
168
  - lib/cloudinary/utils.rb
@@ -159,11 +174,18 @@ files:
159
174
  - spec/auth_token_spec.rb
160
175
  - spec/cloudinary_helper_spec.rb
161
176
  - spec/cloudinary_spec.rb
177
+ - spec/data/sync_static/app/assets/javascripts/1.coffee
178
+ - spec/data/sync_static/app/assets/javascripts/1.js
179
+ - spec/data/sync_static/app/assets/stylesheets/1.css
162
180
  - spec/docx.docx
163
181
  - spec/favicon.ico
164
182
  - spec/logo.png
183
+ - spec/rake_spec.rb
184
+ - spec/search_spec.rb
165
185
  - spec/spec_helper.rb
166
186
  - spec/streaminig_profiles_api_spec.rb
187
+ - spec/support/helpers/temp_file_helpers.rb
188
+ - spec/support/shared_contexts/rake.rb
167
189
  - spec/uploader_spec.rb
168
190
  - spec/utils_methods_spec.rb
169
191
  - spec/utils_spec.rb
@@ -211,11 +233,18 @@ test_files:
211
233
  - spec/auth_token_spec.rb
212
234
  - spec/cloudinary_helper_spec.rb
213
235
  - spec/cloudinary_spec.rb
236
+ - spec/data/sync_static/app/assets/javascripts/1.coffee
237
+ - spec/data/sync_static/app/assets/javascripts/1.js
238
+ - spec/data/sync_static/app/assets/stylesheets/1.css
214
239
  - spec/docx.docx
215
240
  - spec/favicon.ico
216
241
  - spec/logo.png
242
+ - spec/rake_spec.rb
243
+ - spec/search_spec.rb
217
244
  - spec/spec_helper.rb
218
245
  - spec/streaminig_profiles_api_spec.rb
246
+ - spec/support/helpers/temp_file_helpers.rb
247
+ - spec/support/shared_contexts/rake.rb
219
248
  - spec/uploader_spec.rb
220
249
  - spec/utils_methods_spec.rb
221
250
  - spec/utils_spec.rb