cloudinary 1.4.0 → 1.5.2

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,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