cloudinary 1.4.0 → 1.5.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 7a96fb520a45441c0cc40d980dc03836c6b7ffc4
4
- data.tar.gz: 29924a2fa48916d03132b9bf6a080749471b558d
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ M2E1MWI3ZmRiNDA2YWE2ZTA0OTYzZDUyZmIxMmNjYWJhMjFhNzVmNA==
5
+ data.tar.gz: !binary |-
6
+ NjUzMWYwOGVkNTU0ZmE3ZDIyZjJjNzM1MGE5YWM4ZWRhYWY2OGQyYg==
5
7
  SHA512:
6
- metadata.gz: 86b21f0e18c8759c79162d2b8f2cde2f1fd8807eae924c4fa6906f698f4a92e518763328a91dc36022c62edbf8772bad6b5f3885b41e4457831d1f11dcdd0d56
7
- data.tar.gz: 94b7b4a747cb5bab25112cf0ac2041df8fa48009efbbc7f5719fe2df809ea3ae0d0f823928a3b0c6862839f4f3124db8ab3f0e6b2f2de52500d168570d118cb4
8
+ metadata.gz: !binary |-
9
+ NDFjMjgyNTE1MTkxODkxZjcyN2VmOGU3ODZiMWU2NjAyMWNiODc2YjJiMGMz
10
+ YzkxOTYyZjRkOTU4MmM2MGMxNThmMDQ3NThiMWUxOTFmZGE0OTBkNjJkYTlk
11
+ ZWI1MDdhOGVjMzUyZmE2NzMzMWQ3M2U0MTFmZWY5NmYxNDJjNWY=
12
+ data.tar.gz: !binary |-
13
+ ZDEzODU4YWJhOGExNmQxZjY2NGI5OTZkMjk1NmFiMGY1YWFiZTdkODcwZWIw
14
+ MmM5NGQ5OWIzZGJmNjBhZmE1OGViZmFhZGMzOGJmNTU4M2M2ZTAzM2FkNTlk
15
+ ZDMxNDA1MDNmOGE4ODk5M2Y5MDkyZDRhOTI4NTNlOGVhYmRjZWI=
@@ -1,4 +1,35 @@
1
1
 
2
+ 1.5.2 / 2017-02-22
3
+ ==================
4
+
5
+ * Support URL Authorization token.
6
+ * Rename auth_token.
7
+ * Support nested keys in CLOUDINARY_URL
8
+ * Support "authenticated" url without a signature.
9
+ * Add OpenStruct from ruby 2.0.
10
+ * Add specific rubyzip version for ruby 1.9
11
+
12
+ 1.5.1 / 2017-02-13
13
+ ==================
14
+ * Fix Carrierwave 1.0.0 integration: broken `remote_image_url`
15
+
16
+ 1.5.0 / 2017-02-07
17
+ ==================
18
+
19
+ New functionality and features
20
+ ------------------------------
21
+
22
+ * Access mode API
23
+
24
+ Other Changes
25
+ -------------
26
+
27
+ * Fix transformation related tests.
28
+ * Fix archive test to use `include` instead of `match_array`.
29
+ * Fix "missing folder" test
30
+ * Add specific dependency on nokogiri
31
+ * Update rspec version
32
+
2
33
  1.4.0 / 2017-01-30
3
34
  ==================
4
35
 
@@ -21,23 +21,26 @@ Gem::Specification.new do |s|
21
21
  s.require_paths = ["lib"]
22
22
 
23
23
  s.add_dependency "aws_cf_signer"
24
- s.add_development_dependency "rspec", '>=3.2'
24
+ s.add_development_dependency "rspec", '>=3.5'
25
25
  s.add_development_dependency "rspec-rails"
26
- s.add_development_dependency "rubyzip"
27
26
 
28
27
  if RUBY_VERSION > "2.0"
29
28
  s.add_dependency "rest-client"
30
29
  s.add_development_dependency "actionpack"
31
30
  s.add_development_dependency "simplecov"
32
- elsif RUBY_VERSION > "1.9"
31
+ s.add_development_dependency "rubyzip"
32
+ elsif RUBY_VERSION >= "1.9"
33
33
  s.add_dependency "rest-client", '< 2.0'
34
34
  s.add_dependency 'json', '~> 1.8'
35
35
  s.add_development_dependency "actionpack", '< 5.0'
36
36
  s.add_development_dependency "simplecov"
37
+ s.add_development_dependency "nokogiri", "<1.7.0"
38
+ s.add_development_dependency "rubyzip", '<1.2.1'
37
39
  else
38
40
  s.add_dependency "i18n", "<0.7.0"
39
41
  s.add_dependency "rest-client", "<=1.6.8"
40
42
  s.add_development_dependency "actionpack", "~>3.2.0"
43
+ s.add_development_dependency "nokogiri", "<1.6.0"
41
44
  end
42
45
 
43
46
  end
@@ -1,5 +1,10 @@
1
1
  # Copyright Cloudinary
2
- require "ostruct"
2
+ if RUBY_VERSION > "2"
3
+ require "ostruct"
4
+ else
5
+ require "cloudinary/ostruct2"
6
+ end
7
+
3
8
  require "pathname"
4
9
  require "yaml"
5
10
  require "uri"
@@ -15,15 +20,15 @@ module Cloudinary
15
20
  autoload :Downloader, "cloudinary/downloader"
16
21
  autoload :Blob, "cloudinary/blob"
17
22
  autoload :PreloadedFile, "cloudinary/preloaded_file"
18
- autoload :Static, "cloudinary/static"
19
- autoload :CarrierWave, "cloudinary/carrier_wave"
20
-
21
- CF_SHARED_CDN = "d3jpl91pxevbkh.cloudfront.net"
22
- AKAMAI_SHARED_CDN = "res.cloudinary.com"
23
+ autoload :Static, "cloudinary/static"
24
+ autoload :CarrierWave, "cloudinary/carrier_wave"
25
+
26
+ CF_SHARED_CDN = "d3jpl91pxevbkh.cloudfront.net"
27
+ AKAMAI_SHARED_CDN = "res.cloudinary.com"
23
28
  OLD_AKAMAI_SHARED_CDN = "cloudinary-a.akamaihd.net"
24
- SHARED_CDN = AKAMAI_SHARED_CDN
25
-
26
- USER_AGENT = "CloudinaryRuby/" + VERSION
29
+ SHARED_CDN = AKAMAI_SHARED_CDN
30
+
31
+ USER_AGENT = "CloudinaryRuby/" + VERSION
27
32
  @@user_platform = ""
28
33
 
29
34
  # Add platform information to the USER_AGENT header
@@ -46,27 +51,27 @@ module Cloudinary
46
51
 
47
52
  FORMAT_ALIASES = {
48
53
  "jpeg" => "jpg",
49
- "jpe" => "jpg",
50
- "tif" => "tiff",
51
- "ps" => "eps",
52
- "ept" => "eps"
54
+ "jpe" => "jpg",
55
+ "tif" => "tiff",
56
+ "ps" => "eps",
57
+ "ept" => "eps"
53
58
  }
54
-
59
+
55
60
  @@config = nil
56
-
61
+
57
62
  def self.config(new_config=nil)
58
63
  first_time = @@config.nil?
59
- @@config ||= OpenStruct.new((YAML.load(ERB.new(IO.read(config_dir.join("cloudinary.yml"))).result)[config_env] rescue {}))
60
-
64
+ @@config ||= OpenStruct.new((YAML.load(ERB.new(IO.read(config_dir.join("cloudinary.yml"))).result)[config_env] rescue {}))
65
+
61
66
  # Heroku support
62
67
  if first_time && ENV["CLOUDINARY_CLOUD_NAME"]
63
68
  set_config(
64
- "cloud_name" => ENV["CLOUDINARY_CLOUD_NAME"],
65
- "api_key" => ENV["CLOUDINARY_API_KEY"],
66
- "api_secret" => ENV["CLOUDINARY_API_SECRET"],
69
+ "cloud_name" => ENV["CLOUDINARY_CLOUD_NAME"],
70
+ "api_key" => ENV["CLOUDINARY_API_KEY"],
71
+ "api_secret" => ENV["CLOUDINARY_API_SECRET"],
67
72
  "secure_distribution" => ENV["CLOUDINARY_SECURE_DISTRIBUTION"],
68
- "private_cdn" => ENV["CLOUDINARY_PRIVATE_CDN"].to_s == 'true',
69
- "secure" => ENV["CLOUDINARY_SECURE"].to_s == 'true'
73
+ "private_cdn" => ENV["CLOUDINARY_PRIVATE_CDN"].to_s == 'true',
74
+ "secure" => ENV["CLOUDINARY_SECURE"].to_s == 'true'
70
75
  )
71
76
  elsif first_time && ENV["CLOUDINARY_URL"]
72
77
  config_from_url(ENV["CLOUDINARY_URL"])
@@ -75,26 +80,50 @@ module Cloudinary
75
80
  set_config(new_config) if new_config
76
81
  yield(@@config) if block_given?
77
82
 
78
- @@config
83
+ @@config
79
84
  end
80
-
85
+
81
86
  def self.config_from_url(url)
82
87
  @@config ||= OpenStruct.new
83
- uri = URI.parse(url)
88
+ uri = URI.parse(url)
84
89
  set_config(
85
- "cloud_name" => uri.host,
86
- "api_key" => uri.user,
87
- "api_secret" => uri.password,
88
- "private_cdn" => !uri.path.blank?,
90
+ "cloud_name" => uri.host,
91
+ "api_key" => uri.user,
92
+ "api_secret" => uri.password,
93
+ "private_cdn" => !uri.path.blank?,
89
94
  "secure_distribution" => uri.path[1..-1]
90
95
  )
91
96
  uri.query.to_s.split("&").each do
92
- |param|
97
+ |param|
93
98
  key, value = param.split("=")
94
- set_config(key=>URI.decode(value))
95
- end
99
+ if isNestedKey? key
100
+ putNestedKey key, value
101
+ else
102
+ set_config(key => URI.decode(value))
103
+ end
104
+ end
96
105
  end
97
-
106
+
107
+ def self.putNestedKey(key, value)
108
+ chain = key.split(/[\[\]]+/).reject { |i| i.empty? }
109
+ outer = @@config
110
+ lastKey = chain.pop()
111
+ chain.each do |innerKey|
112
+ inner = outer[innerKey]
113
+ if inner.nil?
114
+ inner = OpenStruct.new
115
+ outer[innerKey] = inner
116
+ end
117
+ outer = inner
118
+ end
119
+ outer[lastKey] = value
120
+ end
121
+
122
+
123
+ def self.isNestedKey?(key)
124
+ /\w+\[\w+\]/ =~ key
125
+ end
126
+
98
127
  def self.app_root
99
128
  if defined? Rails::root
100
129
  # Rails 2.2 return String for Rails.root
@@ -121,10 +150,9 @@ module Cloudinary
121
150
  new_config.each{|k,v| @@config.send(:"#{k}=", v) if !v.nil?}
122
151
  end
123
152
  end
124
-
125
- # Prevent require loop if included after Rails is already initialized.
126
- require "cloudinary/helper" if defined?(::ActionView::Base)
127
- require "cloudinary/controller" if defined?(::ActionController::Base)
128
- require "cloudinary/railtie" if defined?(Rails) && defined?(Rails::Railtie)
129
- require "cloudinary/engine" if defined?(Rails) && defined?(Rails::Engine)
153
+ # Prevent require loop if included after Rails is already initialized.
154
+ require "cloudinary/helper" if defined?(::ActionView::Base)
155
+ require "cloudinary/controller" if defined?(::ActionController::Base)
156
+ require "cloudinary/railtie" if defined?(Rails) && defined?(Rails::Railtie)
157
+ require "cloudinary/engine" if defined?(Rails) && defined?(Rails::Engine)
130
158
 
@@ -267,6 +267,42 @@ class Cloudinary::Api
267
267
  call_api(:put, "streaming_profiles/#{name}", params, options)
268
268
  end
269
269
 
270
+ # Update resources access mode. Resources are selected by the prefix
271
+ # @param [String] access_mode the access mode to set the resources to
272
+ # @param [String] prefix The prefix by which to filter applicable resources
273
+ # @param [Object] options additional options
274
+ # @option options [String] :resource_type ("image") the type of resources to modify
275
+ # @option options [Fixnum] :max_results (nil) the maximum resources to process in a single invocation
276
+ # @option options [String] :next_cursor (nil) provided by a previous call to the method
277
+ def self.update_resources_access_mode_by_prefix(access_mode, prefix, options = {})
278
+
279
+ update_resources_access_mode(access_mode, :prefix, prefix, options)
280
+ end
281
+
282
+ # Update resources access mode. Resources are selected by the tag
283
+ # @param [String] access_mode the access mode to set the resources to
284
+ # @param [String] tag the tag by which to filter applicable resources
285
+ # @param [Object] options additional options
286
+ # @option options [String] :resource_type ("image") the type of resources to modify
287
+ # @option options [Fixnum] :max_results (nil) the maximum resources to process in a single invocation
288
+ # @option options [String] :next_cursor (nil) provided by a previous call to the method
289
+ def self.update_resources_access_mode_by_tag(access_mode, tag, options = {})
290
+
291
+ update_resources_access_mode(access_mode, :tag, tag, options)
292
+ end
293
+
294
+ # Update resources access mode. Resources are selected by the provided public_ids
295
+ # @param [String] access_mode the access mode to set the resources to
296
+ # @param [Array<String>] public_ids The prefix by which to filter applicable resources
297
+ # @param [Object] options additional options
298
+ # @option options [String] :resource_type ("image") the type of resources to modify
299
+ # @option options [Fixnum] :max_results (nil) the maximum resources to process in a single invocation
300
+ # @option options [String] :next_cursor (nil) provided by a previous call to the method
301
+ def self.update_resources_access_mode_by_ids(access_mode, public_ids, options = {})
302
+
303
+ update_resources_access_mode(access_mode, :public_ids, public_ids, options)
304
+ end
305
+
270
306
  protected
271
307
 
272
308
  def self.call_api(method, uri, params, options)
@@ -320,4 +356,14 @@ class Cloudinary::Api
320
356
  def self.transformation_string(transformation)
321
357
  transformation.is_a?(String) ? transformation : Cloudinary::Utils.generate_transformation_string(transformation.clone)
322
358
  end
359
+
360
+ def self.update_resources_access_mode(access_mode, by_key, value, options = {})
361
+ resource_type = options[:resource_type] || "image"
362
+ type = options[:type] || "upload"
363
+ params = only(options, :next_cursor)
364
+ params[:access_mode] = access_mode
365
+ params[by_key] = value
366
+ call_api("post", "resources/#{resource_type}/#{type}/update_access_mode", params, options)
367
+ end
368
+
323
369
  end
@@ -0,0 +1,68 @@
1
+ require 'openssl'
2
+ if RUBY_VERSION > "2"
3
+ require "ostruct"
4
+ else
5
+ require "cloudinary/ostruct2"
6
+ end
7
+
8
+
9
+ module Cloudinary
10
+ module AuthToken
11
+ SEPARATOR = '~'
12
+
13
+ def self.generate(options = {})
14
+ key = options[:key]
15
+ raise "Missing auth token key configuration" unless key
16
+ name = options[:token_name] || "__cld_token__"
17
+ start = options[:start_time]
18
+ expiration = options[:expiration]
19
+ ip = options[:ip]
20
+ acl = options[:acl]
21
+ duration = options[:duration]
22
+ url = options[:url]
23
+ start = Time.new.getgm.to_i if start == 'now'
24
+ if expiration.nil? || expiration == 0
25
+ if !(duration.nil? || duration == 0)
26
+ expiration = (start || Time.new.getgm.to_i) + duration
27
+ else
28
+ raise 'Must provide either expiration or duration'
29
+ end
30
+ end
31
+
32
+ token = []
33
+ token << "ip=#{ip}" if ip
34
+ token << "st=#{start}" if start
35
+ token << "exp=#{expiration}"
36
+ token << "acl=#{escape_to_lower(acl)}" if acl
37
+ to_sign = token.clone
38
+ to_sign << "url=#{escape_to_lower(url)}" if url
39
+ auth = digest(to_sign.join(SEPARATOR), key)
40
+ token << "hmac=#{auth}"
41
+ "#{name}=#{token.join(SEPARATOR)}"
42
+ end
43
+
44
+
45
+ # Merge token2 to token1 returning a new
46
+ # Requires to support Ruby 1.9
47
+ def self.merge_auth_token(token1, token2)
48
+ token1 = token1 || {}
49
+ token2 = token2 || {}
50
+ token1 = token1.respond_to?( :to_h) ? token1.to_h : token1
51
+ token2 = token2.respond_to?( :to_h) ? token2.to_h : token2
52
+ token1.merge(token2)
53
+ end
54
+
55
+ private
56
+
57
+ # escape URI pattern using lowercase hex. For example "/" -> "%2f".
58
+ def self.escape_to_lower(url)
59
+ CGI::escape(url).gsub(/%../) { |h| h.downcase }
60
+ end
61
+
62
+ def self.digest(message, key)
63
+ bin_key = Array(key).pack("H*")
64
+ digest = OpenSSL::Digest::SHA256.new
65
+ OpenSSL::HMAC.hexdigest(digest, bin_key, message)
66
+ end
67
+ end
68
+ end
@@ -1,5 +1,5 @@
1
1
  module Cloudinary::CarrierWave
2
- def download!(uri)
2
+ def download!(uri, *args)
3
3
  return super if !self.cloudinary_should_handle_remote?
4
4
  if respond_to?(:process_uri)
5
5
  uri = process_uri(uri)
@@ -17,9 +17,9 @@ module Cloudinary::CarrierWave
17
17
  @uri = uri
18
18
  @original_filename = filename
19
19
  end
20
-
20
+
21
21
  def delete
22
22
  # Do nothing. This is a virtual file.
23
23
  end
24
24
  end
25
- end
25
+ end
@@ -0,0 +1,284 @@
1
+ #
2
+ # = ostruct.rb: OpenStruct implementation
3
+ #
4
+ # Author:: Yukihiro Matsumoto
5
+ # Documentation:: Gavin Sinclair
6
+ #
7
+ # OpenStruct allows the creation of data objects with arbitrary attributes.
8
+ # See OpenStruct for an example.
9
+ #
10
+
11
+ #
12
+ # An OpenStruct is a data structure, similar to a Hash, that allows the
13
+ # definition of arbitrary attributes with their accompanying values. This is
14
+ # accomplished by using Ruby's metaprogramming to define methods on the class
15
+ # itself.
16
+ #
17
+ # == Examples:
18
+ #
19
+ # require 'ostruct'
20
+ #
21
+ # person = OpenStruct.new
22
+ # person.name = "John Smith"
23
+ # person.age = 70
24
+ # person.pension = 300
25
+ #
26
+ # puts person.name # -> "John Smith"
27
+ # puts person.age # -> 70
28
+ # puts person.address # -> nil
29
+ #
30
+ # An OpenStruct employs a Hash internally to store the methods and values and
31
+ # can even be initialized with one:
32
+ #
33
+ # australia = OpenStruct.new(:country => "Australia", :population => 20_000_000)
34
+ # p australia # -> <OpenStruct country="Australia" population=20000000>
35
+ #
36
+ # Hash keys with spaces or characters that would normally not be able to use for
37
+ # method calls (e.g. ()[]*) will not be immediately available on the
38
+ # OpenStruct object as a method for retrieval or assignment, but can be still be
39
+ # reached through the Object#send method.
40
+ #
41
+ # measurements = OpenStruct.new("length (in inches)" => 24)
42
+ # measurements.send("length (in inches)") # -> 24
43
+ #
44
+ # data_point = OpenStruct.new(:queued? => true)
45
+ # data_point.queued? # -> true
46
+ # data_point.send("queued?=",false)
47
+ # data_point.queued? # -> false
48
+ #
49
+ # Removing the presence of a method requires the execution the delete_field
50
+ # method as setting the property value to +nil+ will not remove the method.
51
+ #
52
+ # first_pet = OpenStruct.new(:name => 'Rowdy', :owner => 'John Smith')
53
+ # first_pet.owner = nil
54
+ # second_pet = OpenStruct.new(:name => 'Rowdy')
55
+ #
56
+ # first_pet == second_pet # -> false
57
+ #
58
+ # first_pet.delete_field(:owner)
59
+ # first_pet == second_pet # -> true
60
+ #
61
+ #
62
+ # == Implementation:
63
+ #
64
+ # An OpenStruct utilizes Ruby's method lookup structure to and find and define
65
+ # the necessary methods for properties. This is accomplished through the method
66
+ # method_missing and define_method.
67
+ #
68
+ # This should be a consideration if there is a concern about the performance of
69
+ # the objects that are created, as there is much more overhead in the setting
70
+ # of these properties compared to using a Hash or a Struct.
71
+ #
72
+ class OpenStruct
73
+ #
74
+ # Creates a new OpenStruct object. By default, the resulting OpenStruct
75
+ # object will have no attributes.
76
+ #
77
+ # The optional +hash+, if given, will generate attributes and values
78
+ # (can be a Hash, an OpenStruct or a Struct).
79
+ # For example:
80
+ #
81
+ # require 'ostruct'
82
+ # hash = { "country" => "Australia", :population => 20_000_000 }
83
+ # data = OpenStruct.new(hash)
84
+ #
85
+ # p data # -> <OpenStruct country="Australia" population=20000000>
86
+ #
87
+ def initialize(hash=nil)
88
+ @table = {}
89
+ if hash
90
+ hash.each_pair do |k, v|
91
+ k = k.to_sym
92
+ @table[k] = v
93
+ new_ostruct_member(k)
94
+ end
95
+ end
96
+ end
97
+
98
+ # Duplicate an OpenStruct object members.
99
+ def initialize_copy(orig)
100
+ super
101
+ @table = @table.dup
102
+ @table.each_key{|key| new_ostruct_member(key)}
103
+ end
104
+
105
+ #
106
+ # Converts the OpenStruct to a hash with keys representing
107
+ # each attribute (as symbols) and their corresponding values
108
+ # Example:
109
+ #
110
+ # require 'ostruct'
111
+ # data = OpenStruct.new("country" => "Australia", :population => 20_000_000)
112
+ # data.to_h # => {:country => "Australia", :population => 20000000 }
113
+ #
114
+ def to_h
115
+ @table.dup
116
+ end
117
+
118
+ #
119
+ # Yields all attributes (as a symbol) along with the corresponding values
120
+ # or returns an enumerator if not block is given.
121
+ # Example:
122
+ #
123
+ # require 'ostruct'
124
+ # data = OpenStruct.new("country" => "Australia", :population => 20_000_000)
125
+ # data.each_pair.to_a # => [[:country, "Australia"], [:population, 20000000]]
126
+ #
127
+ def each_pair
128
+ return to_enum __method__ unless block_given?
129
+ @table.each_pair{|p| yield p}
130
+ end
131
+
132
+ #
133
+ # Provides marshalling support for use by the Marshal library.
134
+ #
135
+ def marshal_dump
136
+ @table
137
+ end
138
+
139
+ #
140
+ # Provides marshalling support for use by the Marshal library.
141
+ #
142
+ def marshal_load(x)
143
+ @table = x
144
+ @table.each_key{|key| new_ostruct_member(key)}
145
+ end
146
+
147
+ #
148
+ # Used internally to check if the OpenStruct is able to be
149
+ # modified before granting access to the internal Hash table to be modified.
150
+ #
151
+ def modifiable
152
+ begin
153
+ @modifiable = true
154
+ rescue
155
+ raise TypeError, "can't modify frozen #{self.class}", caller(3)
156
+ end
157
+ @table
158
+ end
159
+ protected :modifiable
160
+
161
+ #
162
+ # Used internally to defined properties on the
163
+ # OpenStruct. It does this by using the metaprogramming function
164
+ # define_singleton_method for both the getter method and the setter method.
165
+ #
166
+ def new_ostruct_member(name)
167
+ name = name.to_sym
168
+ unless respond_to?(name)
169
+ define_singleton_method(name) { @table[name] }
170
+ define_singleton_method("#{name}=") { |x| modifiable[name] = x }
171
+ end
172
+ name
173
+ end
174
+ protected :new_ostruct_member
175
+
176
+ def method_missing(mid, *args) # :nodoc:
177
+ mname = mid.id2name
178
+ len = args.length
179
+ if mname.chomp!('=')
180
+ if len != 1
181
+ raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1)
182
+ end
183
+ modifiable[new_ostruct_member(mname)] = args[0]
184
+ elsif len == 0
185
+ @table[mid]
186
+ else
187
+ raise NoMethodError, "undefined method `#{mid}' for #{self}", caller(1)
188
+ end
189
+ end
190
+
191
+ # Returns the value of a member.
192
+ #
193
+ # person = OpenStruct.new('name' => 'John Smith', 'age' => 70)
194
+ # person[:age] # => 70, same as ostruct.age
195
+ #
196
+ def [](name)
197
+ @table[name.to_sym]
198
+ end
199
+
200
+ #
201
+ # Sets the value of a member.
202
+ #
203
+ # person = OpenStruct.new('name' => 'John Smith', 'age' => 70)
204
+ # person[:age] = 42 # => equivalent to ostruct.age = 42
205
+ # person.age # => 42
206
+ #
207
+ def []=(name, value)
208
+ modifiable[new_ostruct_member(name)] = value
209
+ end
210
+
211
+ #
212
+ # Remove the named field from the object. Returns the value that the field
213
+ # contained if it was defined.
214
+ #
215
+ # require 'ostruct'
216
+ #
217
+ # person = OpenStruct.new('name' => 'John Smith', 'age' => 70)
218
+ #
219
+ # person.delete_field('name') # => 'John Smith'
220
+ #
221
+ def delete_field(name)
222
+ sym = name.to_sym
223
+ singleton_class.__send__(:remove_method, sym, "#{name}=")
224
+ @table.delete sym
225
+ end
226
+
227
+ InspectKey = :__inspect_key__ # :nodoc:
228
+
229
+ #
230
+ # Returns a string containing a detailed summary of the keys and values.
231
+ #
232
+ def inspect
233
+ str = "#<#{self.class}"
234
+
235
+ ids = (Thread.current[InspectKey] ||= [])
236
+ if ids.include?(object_id)
237
+ return str << ' ...>'
238
+ end
239
+
240
+ ids << object_id
241
+ begin
242
+ first = true
243
+ for k,v in @table
244
+ str << "," unless first
245
+ first = false
246
+ str << " #{k}=#{v.inspect}"
247
+ end
248
+ return str << '>'
249
+ ensure
250
+ ids.pop
251
+ end
252
+ end
253
+ alias :to_s :inspect
254
+
255
+ attr_reader :table # :nodoc:
256
+ protected :table
257
+
258
+ #
259
+ # Compares this object and +other+ for equality. An OpenStruct is equal to
260
+ # +other+ when +other+ is an OpenStruct and the two objects' Hash tables are
261
+ # equal.
262
+ #
263
+ def ==(other)
264
+ return false unless other.kind_of?(OpenStruct)
265
+ @table == other.table
266
+ end
267
+
268
+ #
269
+ # Compares this object and +other+ for equality. An OpenStruct is eql? to
270
+ # +other+ when +other+ is an OpenStruct and the two objects' Hash tables are
271
+ # eql?.
272
+ #
273
+ def eql?(other)
274
+ return false unless other.kind_of?(OpenStruct)
275
+ @table.eql?(other.table)
276
+ end
277
+
278
+ # Compute a hash-code for this OpenStruct.
279
+ # Two hashes with the same content will have the same hash code
280
+ # (and will be eql?).
281
+ def hash
282
+ @table.hash
283
+ end
284
+ end